feat(handlers): authz authrequest authelia url (#5181)
This adjusts the AuthRequest Authz implementation behave similarly to the other implementations in as much as Authelia can return the relevant redirection to the proxy and the proxy just utilizes it if possible. In addition it swaps the HAProxy examples over to the ForwardAuth implementation as that's now supported. Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>pull/5192/head
parent
fa250ea7dd
commit
2dcfc0b04c
105
api/openapi.yml
105
api/openapi.yml
|
@ -111,6 +111,12 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/handlers.StateResponse'
|
$ref: '#/components/schemas/handlers.StateResponse'
|
||||||
|
{{- $redir := "https://auth.example.com/?rd=https%3A%2F%2Fexample.com&rm=GET" }}
|
||||||
|
{{- if .Domain }}
|
||||||
|
{{- $redir = printf "%s?rd=%s&rm=GET" .BaseURL (urlquery (printf "https://%s" .Domain)) }}
|
||||||
|
{{- else if .BaseURL }}
|
||||||
|
{{- $redir = printf "%s?rd=%s&rm=GET" .BaseURL (urlquery .BaseURL) }}
|
||||||
|
{{- end }}
|
||||||
{{- range $name, $config := .EndpointsAuthz }}
|
{{- range $name, $config := .EndpointsAuthz }}
|
||||||
{{- $uri := printf "/api/authz/%s" $name }}
|
{{- $uri := printf "/api/authz/%s" $name }}
|
||||||
{{- if (eq $name "legacy") }}{{ $uri = "/api/verify" }}{{ end }}
|
{{- if (eq $name "legacy") }}{{ $uri = "/api/verify" }}{{ end }}
|
||||||
|
@ -188,8 +194,37 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: admin,devs
|
example: admin,devs
|
||||||
|
set-cookie:
|
||||||
|
description: Sets a new cookie value
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"302":
|
||||||
|
description: Found
|
||||||
|
headers:
|
||||||
|
location:
|
||||||
|
description: Redirect Location for user authorization
|
||||||
|
example: {{ $redir }}
|
||||||
|
set-cookie:
|
||||||
|
description: Sets a new cookie value
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"303":
|
||||||
|
description: See Other
|
||||||
|
headers:
|
||||||
|
location:
|
||||||
|
description: Redirect Location for user authorization
|
||||||
|
example: {{ $redir }}
|
||||||
|
set-cookie:
|
||||||
|
description: Sets a new cookie value
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
"401":
|
"401":
|
||||||
description: Unauthorized
|
description: Unauthorized
|
||||||
|
headers:
|
||||||
|
set-cookie:
|
||||||
|
description: Sets a new cookie value
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
@ -232,6 +267,32 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: admin,devs
|
example: admin,devs
|
||||||
|
set-cookie:
|
||||||
|
description: Sets a new cookie value
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"302":
|
||||||
|
description: Found
|
||||||
|
headers:
|
||||||
|
location:
|
||||||
|
description: Redirect Location for user authorization
|
||||||
|
example: {{ $redir }}
|
||||||
|
set-cookie:
|
||||||
|
description: Sets a new cookie value
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"303":
|
||||||
|
description: See Other
|
||||||
|
headers:
|
||||||
|
location:
|
||||||
|
description: Redirect Location for user authorization
|
||||||
|
example: {{ $redir }}
|
||||||
|
set-cookie:
|
||||||
|
description: Sets a new cookie value
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
"401":
|
"401":
|
||||||
description: Unauthorized
|
description: Unauthorized
|
||||||
security:
|
security:
|
||||||
|
@ -275,6 +336,32 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: admin,devs
|
example: admin,devs
|
||||||
|
set-cookie:
|
||||||
|
description: Sets a new cookie value
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"302":
|
||||||
|
description: Found
|
||||||
|
headers:
|
||||||
|
location:
|
||||||
|
description: Redirect Location for user authorization
|
||||||
|
example: {{ $redir }}
|
||||||
|
set-cookie:
|
||||||
|
description: Sets a new cookie value
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"303":
|
||||||
|
description: See Other
|
||||||
|
headers:
|
||||||
|
location:
|
||||||
|
description: Redirect Location for user authorization
|
||||||
|
example: {{ $redir }}
|
||||||
|
set-cookie:
|
||||||
|
description: Sets a new cookie value
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
"401":
|
"401":
|
||||||
description: Unauthorized
|
description: Unauthorized
|
||||||
security:
|
security:
|
||||||
|
@ -316,8 +403,22 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: admin,devs
|
example: admin,devs
|
||||||
|
set-cookie:
|
||||||
|
description: Sets a new cookie value
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
"401":
|
"401":
|
||||||
description: Unauthorized
|
description: Unauthorized
|
||||||
|
headers:
|
||||||
|
location:
|
||||||
|
description: Redirect Location for user authorization
|
||||||
|
example: {{ $redir }}
|
||||||
|
set-cookie:
|
||||||
|
description: Sets a new cookie value
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
@ -1904,7 +2005,7 @@ components:
|
||||||
properties:
|
properties:
|
||||||
appidExclude:
|
appidExclude:
|
||||||
type: string
|
type: string
|
||||||
example: https://auth.example.com
|
example: {{ .BaseURL }}
|
||||||
webauthn.PublicKeyCredentialRequestOptions:
|
webauthn.PublicKeyCredentialRequestOptions:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1939,7 +2040,7 @@ components:
|
||||||
properties:
|
properties:
|
||||||
appid:
|
appid:
|
||||||
type: string
|
type: string
|
||||||
example: https://auth.example.com
|
example: {{ .BaseURL }}
|
||||||
webauthn.Transports:
|
webauthn.Transports:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -87,6 +87,23 @@ following are the assumptions we make:
|
||||||
* This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're
|
* This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're
|
||||||
just testing or you want ot use that specific domain
|
just testing or you want ot use that specific domain
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
[Caddy] utilizes the [ForwardAuth](../../reference/guides/proxy-authorization.md#forwardauth) Authz implementation. The
|
||||||
|
associated [Metadata](../../reference/guides/proxy-authorization.md#forwardauth-metadata) should be considered required.
|
||||||
|
|
||||||
|
The examples below assume you are using the default
|
||||||
|
[Authz Endpoints Configuration](../../configuration/miscellaneous/server-endpoints-authz.md) or one similar to the
|
||||||
|
following minimal configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
endpoints:
|
||||||
|
authz:
|
||||||
|
forward-auth:
|
||||||
|
implementation: ForwardAuth
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Below you will find commented examples of the following configuration:
|
Below you will find commented examples of the following configuration:
|
||||||
|
|
|
@ -61,6 +61,23 @@ following are the assumptions we make:
|
||||||
* This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're
|
* This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're
|
||||||
just testing or you want ot use that specific domain
|
just testing or you want ot use that specific domain
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
[Envoy] utilizes the [ExtAuthz](../../reference/guides/proxy-authorization.md#extauthz) Authz implementation. The
|
||||||
|
associated [Metadata](../../reference/guides/proxy-authorization.md#extauthz-metadata) should be considered required.
|
||||||
|
|
||||||
|
The examples below assume you are using the default
|
||||||
|
[Authz Endpoints Configuration](../../configuration/miscellaneous/server-endpoints-authz.md) or one similar to the
|
||||||
|
following minimal configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
endpoints:
|
||||||
|
authz:
|
||||||
|
ext-authz:
|
||||||
|
implementation: ExtAuthz
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Below you will find commented examples of the following configuration:
|
Below you will find commented examples of the following configuration:
|
||||||
|
|
|
@ -90,22 +90,37 @@ following are the assumptions we make:
|
||||||
* This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're
|
* This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're
|
||||||
just testing or you want ot use that specific domain
|
just testing or you want ot use that specific domain
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
[HAProxy] utilizes the [ForwardAuth](../../reference/guides/proxy-authorization.md#forwardauth) Authz implementation. The
|
||||||
|
associated [Metadata](../../reference/guides/proxy-authorization.md#forwardauth-metadata) should be considered required.
|
||||||
|
|
||||||
|
The examples below assume you are using the default
|
||||||
|
[Authz Endpoints Configuration](../../configuration/miscellaneous/server-endpoints-authz.md) or one similar to the
|
||||||
|
following minimal configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
endpoints:
|
||||||
|
authz:
|
||||||
|
forward-auth:
|
||||||
|
implementation: ForwardAuth
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Below you will find commented examples of the following configuration:
|
Below you will find commented examples of the following configuration:
|
||||||
|
|
||||||
* Authelia Portal
|
* Authelia Portal
|
||||||
* Protected Endpoint (Nextcloud)
|
* Protected Endpoints (Nextcloud)
|
||||||
* Protected Endpoint with `Authorization` header for basic authentication (Heimdall)
|
|
||||||
|
|
||||||
With this configuration you can protect your virtual hosts with Authelia, by following the steps below:
|
With this configuration you can protect your virtual hosts with Authelia, by following the steps below:
|
||||||
|
|
||||||
1. Add host(s) to the `protected-frontends` or `protected-frontends-basic` ACLs to support protection with Authelia.
|
1. Add host(s) to the `protected-frontends` ACLs to support protection with Authelia. You can separate each subdomain
|
||||||
You can separate each subdomain with a `|` in the regex, for example:
|
with a `|` in the regex, for example:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
acl protected-frontends hdr(host) -m reg -i ^(?i)(jenkins|nextcloud|phpmyadmin)\.example\.com
|
acl protected-frontends hdr(host) -m reg -i ^(?i)(jenkins|nextcloud|phpmyadmin)\.example\.com
|
||||||
acl protected-frontends-basic hdr(host) -m reg -i ^(?i)(heimdall)\.example\.com
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Add host ACL(s) in the form of `host-service`, this will be utilised to route to the correct
|
2. Add host ACL(s) in the form of `host-service`, this will be utilised to route to the correct
|
||||||
|
@ -190,46 +205,24 @@ frontend fe_http
|
||||||
option forwardfor
|
option forwardfor
|
||||||
|
|
||||||
# Host ACLs
|
# Host ACLs
|
||||||
acl protected-frontends hdr(host) -m reg -i ^(?i)(nextcloud)\.example\.com
|
acl protected-frontends hdr(Host) -m reg -i ^(?i)(nextcloud|heimdall)\.example\.com
|
||||||
acl protected-frontends-basic hdr(host) -m reg -i ^(?i)(heimdall)\.example\.com
|
acl host-authelia hdr(Host) -i auth.example.com
|
||||||
acl host-authelia hdr(host) -i auth.example.com
|
acl host-nextcloud hdr(Host) -i nextcloud.example.com
|
||||||
acl host-nextcloud hdr(host) -i nextcloud.example.com
|
acl host-heimdall hdr(Host) -i heimdall.example.com
|
||||||
acl host-heimdall hdr(host) -i heimdall.example.com
|
|
||||||
|
|
||||||
# This is required if utilising basic auth with /api/verify?auth=basic
|
|
||||||
http-request set-var(txn.host) hdr(Host)
|
|
||||||
|
|
||||||
http-request set-var(req.scheme) str(https) if { ssl_fc }
|
http-request set-var(req.scheme) str(https) if { ssl_fc }
|
||||||
http-request set-var(req.scheme) str(http) if !{ ssl_fc }
|
http-request set-var(req.scheme) str(http) if !{ ssl_fc }
|
||||||
http-request set-var(req.questionmark) str(?) if { query -m found }
|
http-request set-var(req.questionmark) str(?) if { query -m found }
|
||||||
|
|
||||||
# These are optional if you wish to use the Methods rule in the access_control section.
|
# Required Headers
|
||||||
#http-request set-var(req.method) str(CONNECT) if { method CONNECT }
|
http-request set-header X-Forwarded-Method %[method]
|
||||||
#http-request set-var(req.method) str(GET) if { method GET }
|
http-request set-header X-Forwarded-Proto %[var(req.scheme)]
|
||||||
#http-request set-var(req.method) str(HEAD) if { method HEAD }
|
http-request set-header X-Forwarded-Host %[req.hdr(Host)]
|
||||||
#http-request set-var(req.method) str(OPTIONS) if { method OPTIONS }
|
http-request set-header X-Forwarded-URI %[path]%[var(req.questionmark)]%[query]
|
||||||
#http-request set-var(req.method) str(POST) if { method POST }
|
|
||||||
#http-request set-var(req.method) str(TRACE) if { method TRACE }
|
|
||||||
#http-request set-var(req.method) str(PUT) if { method PUT }
|
|
||||||
#http-request set-var(req.method) str(PATCH) if { method PATCH }
|
|
||||||
#http-request set-var(req.method) str(DELETE) if { method DELETE }
|
|
||||||
#http-request set-header X-Forwarded-Method %[var(req.method)]
|
|
||||||
|
|
||||||
# Required headers
|
|
||||||
http-request set-header X-Real-IP %[src]
|
|
||||||
http-request set-header X-Original-Method %[var(req.method)]
|
|
||||||
http-request set-header X-Original-URL %[var(req.scheme)]://%[req.hdr(Host)]%[path]%[var(req.questionmark)]%[query]
|
|
||||||
|
|
||||||
# Protect endpoints with haproxy-auth-request and Authelia
|
# Protect endpoints with haproxy-auth-request and Authelia
|
||||||
http-request lua.auth-request be_authelia /api/authz/auth-request if protected-frontends
|
http-request lua.auth-intercept be_authelia /api/authz/forward-auth HEAD * authorization,proxy-authorization,remote_user,remote-user,remote-groups,remote-name,remote-email - if protected-frontends
|
||||||
# Force `Authorization` header via query arg to /api/verify
|
http-request redirect location %[var(txn.auth_response_location)] if protected-frontends !{ var(txn.auth_response_successful) -m bool }
|
||||||
http-request lua.auth-request be_authelia /api/verify?auth=basic if protected-frontends-basic
|
|
||||||
|
|
||||||
# Redirect protected-frontends to Authelia if not authenticated
|
|
||||||
http-request redirect location https://auth.example.com/?rd=%[var(req.scheme)]://%[base]%[var(req.questionmark)]%[query] if protected-frontends !{ var(txn.auth_response_successful) -m bool }
|
|
||||||
# Send 401 and pass `WWW-Authenticate` header on protected-frontend-basic if not pre-authenticated
|
|
||||||
http-request set-var(txn.auth) var(req.auth_response_header.www_authenticate) if protected-frontends-basic !{ var(txn.auth_response_successful) -m bool }
|
|
||||||
http-response deny deny_status 401 hdr WWW-Authenticate %[var(txn.auth)] if { var(txn.host) -m reg -i ^(?i)(heimdall)\.example\.com } !{ var(txn.auth_response_successful) -m bool }
|
|
||||||
|
|
||||||
# Authelia backend route
|
# Authelia backend route
|
||||||
use_backend be_authelia if host-authelia
|
use_backend be_authelia if host-authelia
|
||||||
|
@ -242,24 +235,6 @@ backend be_authelia
|
||||||
server authelia authelia:9091
|
server authelia authelia:9091
|
||||||
|
|
||||||
backend be_nextcloud
|
backend be_nextcloud
|
||||||
## Pass the special authorization response headers to the protected application.
|
|
||||||
acl authorization_exist var(req.auth_response_header.authorization) -m found
|
|
||||||
acl proxy_authorization_exist var(req.auth_response_header.proxy_authorization) -m found
|
|
||||||
|
|
||||||
http-request set-header Authorization %[var(req.auth_response_header.authorization)] if authorization_exist
|
|
||||||
http-request set-header Proxy-Authorization %[var(req.auth_response_header.proxy_authorization)] if proxy_authorization_exist
|
|
||||||
|
|
||||||
## Pass the special metadata response headers to the protected application.
|
|
||||||
acl remote_user_exist var(req.auth_response_header.remote_user) -m found
|
|
||||||
acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found
|
|
||||||
acl remote_name_exist var(req.auth_response_header.remote_name) -m found
|
|
||||||
acl remote_email_exist var(req.auth_response_header.remote_email) -m found
|
|
||||||
|
|
||||||
http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist
|
|
||||||
http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist
|
|
||||||
http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist
|
|
||||||
http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist
|
|
||||||
|
|
||||||
## Pass the Set-Cookie response headers to the user.
|
## Pass the Set-Cookie response headers to the user.
|
||||||
acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found
|
acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found
|
||||||
http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist
|
http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist
|
||||||
|
@ -267,24 +242,6 @@ backend be_nextcloud
|
||||||
server nextcloud nextcloud:443 ssl verify none
|
server nextcloud nextcloud:443 ssl verify none
|
||||||
|
|
||||||
backend be_heimdall
|
backend be_heimdall
|
||||||
## Pass the special authorization response headers to the protected application.
|
|
||||||
acl authorization_exist var(req.auth_response_header.authorization) -m found
|
|
||||||
acl proxy_authorization_exist var(req.auth_response_header.proxy_authorization) -m found
|
|
||||||
|
|
||||||
http-request set-header Authorization %[var(req.auth_response_header.authorization)] if authorization_exist
|
|
||||||
http-request set-header Proxy-Authorization %[var(req.auth_response_header.proxy_authorization)] if proxy_authorization_exist
|
|
||||||
|
|
||||||
## Pass the special metadata response headers to the protected application.
|
|
||||||
acl remote_user_exist var(req.auth_response_header.remote_user) -m found
|
|
||||||
acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found
|
|
||||||
acl remote_name_exist var(req.auth_response_header.remote_name) -m found
|
|
||||||
acl remote_email_exist var(req.auth_response_header.remote_email) -m found
|
|
||||||
|
|
||||||
http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist
|
|
||||||
http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist
|
|
||||||
http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist
|
|
||||||
http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist
|
|
||||||
|
|
||||||
## Pass the Set-Cookie response headers to the user.
|
## Pass the Set-Cookie response headers to the user.
|
||||||
acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found
|
acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found
|
||||||
http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist
|
http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist
|
||||||
|
@ -311,47 +268,37 @@ defaults
|
||||||
frontend fe_http
|
frontend fe_http
|
||||||
bind *:443 ssl crt /usr/local/etc/haproxy/haproxy.pem
|
bind *:443 ssl crt /usr/local/etc/haproxy/haproxy.pem
|
||||||
|
|
||||||
# Host ACLs
|
## Trusted Proxies.
|
||||||
acl protected-frontends hdr(host) -m reg -i ^(?i)(nextcloud)\.example\.com
|
http-request del-header X-Forwarded-For
|
||||||
acl protected-frontends-basic hdr(host) -m reg -i ^(?i)(heimdall)\.example\.com
|
|
||||||
acl host-authelia hdr(host) -i auth.example.com
|
|
||||||
acl host-nextcloud hdr(host) -i nextcloud.example.com
|
|
||||||
acl host-heimdall hdr(host) -i heimdall.example.com
|
|
||||||
|
|
||||||
# This is required if utilising basic auth with /api/verify?auth=basic
|
## Comment the above directive and the two directives below to enable the trusted proxies ACL.
|
||||||
http-request set-var(txn.host) hdr(Host)
|
# acl src-trusted_proxies src -f trusted_proxies.src.acl
|
||||||
|
# http-request del-header X-Forwarded-For if !src-trusted_proxies
|
||||||
|
|
||||||
|
## Ensure X-Forwarded-For is set for the auth request.
|
||||||
|
acl hdr-xff_exists req.hdr(X-Forwarded-For) -m found
|
||||||
|
http-request set-header X-Forwarded-For %[src] if !hdr-xff_exists
|
||||||
|
option forwardfor
|
||||||
|
|
||||||
|
# Host ACLs
|
||||||
|
acl protected-frontends hdr(Host) -m reg -i ^(?i)(nextcloud|heimdall)\.example\.com
|
||||||
|
acl host-authelia hdr(Host) -i auth.example.com
|
||||||
|
acl host-nextcloud hdr(Host) -i nextcloud.example.com
|
||||||
|
acl host-heimdall hdr(Host) -i heimdall.example.com
|
||||||
|
|
||||||
http-request set-var(req.scheme) str(https) if { ssl_fc }
|
http-request set-var(req.scheme) str(https) if { ssl_fc }
|
||||||
http-request set-var(req.scheme) str(http) if !{ ssl_fc }
|
http-request set-var(req.scheme) str(http) if !{ ssl_fc }
|
||||||
http-request set-var(req.questionmark) str(?) if { query -m found }
|
http-request set-var(req.questionmark) str(?) if { query -m found }
|
||||||
|
|
||||||
# These are optional if you wish to use the Methods rule in the access_control section.
|
# Required Headers
|
||||||
#http-request set-var(req.method) str(CONNECT) if { method CONNECT }
|
http-request set-header X-Forwarded-Method %[method]
|
||||||
#http-request set-var(req.method) str(GET) if { method GET }
|
http-request set-header X-Forwarded-Proto %[var(req.scheme)]
|
||||||
#http-request set-var(req.method) str(HEAD) if { method HEAD }
|
http-request set-header X-Forwarded-Host %[req.hdr(Host)]
|
||||||
#http-request set-var(req.method) str(OPTIONS) if { method OPTIONS }
|
http-request set-header X-Forwarded-URI %[path]%[var(req.questionmark)]%[query]
|
||||||
#http-request set-var(req.method) str(POST) if { method POST }
|
|
||||||
#http-request set-var(req.method) str(TRACE) if { method TRACE }
|
|
||||||
#http-request set-var(req.method) str(PUT) if { method PUT }
|
|
||||||
#http-request set-var(req.method) str(PATCH) if { method PATCH }
|
|
||||||
#http-request set-var(req.method) str(DELETE) if { method DELETE }
|
|
||||||
#http-request set-header X-Forwarded-Method %[var(req.method)]
|
|
||||||
|
|
||||||
# Required headers
|
|
||||||
http-request set-header X-Real-IP %[src]
|
|
||||||
http-request set-header X-Original-Method %[var(req.method)]
|
|
||||||
http-request set-header X-Original-URL %[var(req.scheme)]://%[req.hdr(Host)]%[path]%[var(req.questionmark)]%[query]
|
|
||||||
|
|
||||||
# Protect endpoints with haproxy-auth-request and Authelia
|
# Protect endpoints with haproxy-auth-request and Authelia
|
||||||
http-request lua.auth-request be_authelia_proxy /api/authz/auth-request if protected-frontends
|
http-request lua.auth-intercept be_authelia_proxy /api/authz/forward-auth HEAD * authorization,proxy-authorization,remote_user,remote-user,remote-groups,remote-name,remote-email - if protected-frontends
|
||||||
# Force `Authorization` header via query arg to /api/verify
|
http-request redirect location %[var(txn.auth_response_location)] if protected-frontends !{ var(txn.auth_response_successful) -m bool }
|
||||||
http-request lua.auth-request be_authelia_proxy /api/verify?auth=basic if protected-frontends-basic
|
|
||||||
|
|
||||||
# Redirect protected-frontends to Authelia if not authenticated
|
|
||||||
http-request redirect location https://auth.example.com/?rd=%[var(req.scheme)]://%[base]%[var(req.questionmark)]%[query] if protected-frontends !{ var(txn.auth_response_successful) -m bool }
|
|
||||||
# Send 401 and pass `WWW-Authenticate` header on protected-frontend-basic if not pre-authenticated
|
|
||||||
http-request set-var(txn.auth) var(req.auth_response_header.www_authenticate) if protected-frontends-basic !{ var(txn.auth_response_successful) -m bool }
|
|
||||||
http-response deny deny_status 401 hdr WWW-Authenticate %[var(txn.auth)] if { var(txn.host) -m reg -i ^(?i)(heimdall)\.example\.com } !{ var(txn.auth_response_successful) -m bool }
|
|
||||||
|
|
||||||
# Authelia backend route
|
# Authelia backend route
|
||||||
use_backend be_authelia if host-authelia
|
use_backend be_authelia if host-authelia
|
||||||
|
@ -373,24 +320,6 @@ listen authelia_proxy
|
||||||
server authelia authelia:9091 ssl verify none
|
server authelia authelia:9091 ssl verify none
|
||||||
|
|
||||||
backend be_nextcloud
|
backend be_nextcloud
|
||||||
## Pass the special authorization response headers to the protected application.
|
|
||||||
acl authorization_exist var(req.auth_response_header.authorization) -m found
|
|
||||||
acl proxy_authorization_exist var(req.auth_response_header.proxy_authorization) -m found
|
|
||||||
|
|
||||||
http-request set-header Authorization %[var(req.auth_response_header.authorization)] if authorization_exist
|
|
||||||
http-request set-header Proxy-Authorization %[var(req.auth_response_header.proxy_authorization)] if proxy_authorization_exist
|
|
||||||
|
|
||||||
## Pass the special metadata response headers to the protected application.
|
|
||||||
acl remote_user_exist var(req.auth_response_header.remote_user) -m found
|
|
||||||
acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found
|
|
||||||
acl remote_name_exist var(req.auth_response_header.remote_name) -m found
|
|
||||||
acl remote_email_exist var(req.auth_response_header.remote_email) -m found
|
|
||||||
|
|
||||||
http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist
|
|
||||||
http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist
|
|
||||||
http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist
|
|
||||||
http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist
|
|
||||||
|
|
||||||
## Pass the Set-Cookie response headers to the user.
|
## Pass the Set-Cookie response headers to the user.
|
||||||
acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found
|
acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found
|
||||||
http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist
|
http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist
|
||||||
|
@ -398,24 +327,6 @@ backend be_nextcloud
|
||||||
server nextcloud nextcloud:443 ssl verify none
|
server nextcloud nextcloud:443 ssl verify none
|
||||||
|
|
||||||
backend be_heimdall
|
backend be_heimdall
|
||||||
## Pass the special authorization response headers to the protected application.
|
|
||||||
acl authorization_exist var(req.auth_response_header.authorization) -m found
|
|
||||||
acl proxy_authorization_exist var(req.auth_response_header.proxy_authorization) -m found
|
|
||||||
|
|
||||||
http-request set-header Authorization %[var(req.auth_response_header.authorization)] if authorization_exist
|
|
||||||
http-request set-header Proxy-Authorization %[var(req.auth_response_header.proxy_authorization)] if proxy_authorization_exist
|
|
||||||
|
|
||||||
## Pass the special metadata response headers to the protected application.
|
|
||||||
acl remote_user_exist var(req.auth_response_header.remote_user) -m found
|
|
||||||
acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found
|
|
||||||
acl remote_name_exist var(req.auth_response_header.remote_name) -m found
|
|
||||||
acl remote_email_exist var(req.auth_response_header.remote_email) -m found
|
|
||||||
|
|
||||||
http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist
|
|
||||||
http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist
|
|
||||||
http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist
|
|
||||||
http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist
|
|
||||||
|
|
||||||
## Pass the Set-Cookie response headers to the user.
|
## Pass the Set-Cookie response headers to the user.
|
||||||
acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found
|
acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found
|
||||||
http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist
|
http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist
|
||||||
|
|
|
@ -34,8 +34,8 @@ You need the following to run __Authelia__ with [NGINX]:
|
||||||
|
|
||||||
* [NGINX] must be built with the `http_auth_request` module which is relatively common
|
* [NGINX] must be built with the `http_auth_request` module which is relatively common
|
||||||
* [NGINX] must be built with the `http_realip` module which is relatively common
|
* [NGINX] must be built with the `http_realip` module which is relatively common
|
||||||
* [NGINX] must be built with the `http_set_misc` module or the `nginx-mod-http-set-misc` package if you want to preserve
|
* [NGINX] must be built with the `http_set_misc` module or the `nginx-mod-http-set-misc` package if you want to use the
|
||||||
more than one query parameter when redirected to the portal due to a limitation in [NGINX]
|
legacy method and preserve more than one query parameter when redirected to the portal due to a limitation in [NGINX]
|
||||||
|
|
||||||
## Trusted Proxies
|
## Trusted Proxies
|
||||||
|
|
||||||
|
@ -76,6 +76,23 @@ following are the assumptions we make:
|
||||||
* This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're
|
* This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're
|
||||||
just testing or you want ot use that specific domain
|
just testing or you want ot use that specific domain
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
[NGINX] utilizes the [AuthRequest](../../reference/guides/proxy-authorization.md#authrequest) Authz implementation. The
|
||||||
|
associated [Metadata](../../reference/guides/proxy-authorization.md#authrequest-metadata) should be considered required.
|
||||||
|
|
||||||
|
The examples below assume you are using the default
|
||||||
|
[Authz Endpoints Configuration](../../configuration/miscellaneous/server-endpoints-authz.md) or one similar to the
|
||||||
|
following minimal configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
endpoints:
|
||||||
|
authz:
|
||||||
|
auth-request:
|
||||||
|
implementation: AuthRequest
|
||||||
|
```
|
||||||
|
|
||||||
## Docker Compose
|
## Docker Compose
|
||||||
|
|
||||||
The following docker compose example has various applications suitable for setting up an example environment.
|
The following docker compose example has various applications suitable for setting up an example environment.
|
||||||
|
@ -449,14 +466,6 @@ and is paired with [authelia-location.conf](#authelia-locationconf).*
|
||||||
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
|
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
|
||||||
auth_request /internal/authelia/authz;
|
auth_request /internal/authelia/authz;
|
||||||
|
|
||||||
## Set the $target_url variable based on the original request.
|
|
||||||
|
|
||||||
## Comment this line if you're using nginx without the http_set_misc module.
|
|
||||||
set_escape_uri $target_url $scheme://$http_host$request_uri;
|
|
||||||
|
|
||||||
## Uncomment this line if you're using NGINX without the http_set_misc module.
|
|
||||||
# set $target_url $scheme://$http_host$request_uri;
|
|
||||||
|
|
||||||
## Save the upstream authorization response headers from Authelia to variables.
|
## Save the upstream authorization response headers from Authelia to variables.
|
||||||
auth_request_set $authorization $upstream_http_authorization;
|
auth_request_set $authorization $upstream_http_authorization;
|
||||||
auth_request_set $proxy_authorization $upstream_http_proxy_authorization;
|
auth_request_set $proxy_authorization $upstream_http_proxy_authorization;
|
||||||
|
@ -481,11 +490,23 @@ proxy_set_header Remote-Name $name;
|
||||||
auth_request_set $cookie $upstream_http_set_cookie;
|
auth_request_set $cookie $upstream_http_set_cookie;
|
||||||
add_header Set-Cookie $cookie;
|
add_header Set-Cookie $cookie;
|
||||||
|
|
||||||
## IMPORTANT: The below URL `https://auth.example.com/` MUST be replaced with the externally accessible URL of the
|
## Configure the redirection when the authz failure occurs. Lines starting with 'Modern Method' and 'Legacy Method'
|
||||||
## Authelia Portal/Site.
|
## should be commented / uncommented as pairs. The modern method uses the session cookies configuration's authelia_url
|
||||||
##
|
## value to determine the redirection URL here. It's much simpler and compatible with the mutli-cookie domain easily.
|
||||||
## If the subreqest returns 200 pass to the backend, if the subrequest returns 401 redirect to the portal.
|
|
||||||
error_page 401 =302 https://auth.example.com/?rd=$target_url;
|
## Modern Method: Set the $redirection_url to the Location header of the response to the Authz endpoint.
|
||||||
|
auth_request_set $redirection_url $upstream_http_location;
|
||||||
|
|
||||||
|
## Modern Method: When there is a 401 response code from the authz endpoint redirect to the $redirection_url.
|
||||||
|
error_page 401 =302 $redirection_url;
|
||||||
|
|
||||||
|
## Legacy Method: Set $target_url to the original requested URL.
|
||||||
|
## This requires http_set_misc module, replace 'set_escape_uri' with 'set' if you don't have this module.
|
||||||
|
# set_escape_uri $target_url $scheme://$http_host$request_uri;
|
||||||
|
|
||||||
|
## Legacy Method: When there is a 401 response code from the authz endpoint redirect to the portal with the 'rd'
|
||||||
|
## URL parameter set to $target_url. This requires users update 'auth.example.com/' with their external authelia URL.
|
||||||
|
# error_page 401 =302 https://auth.example.com/?rd=$target_url;
|
||||||
```
|
```
|
||||||
{{< /details >}}
|
{{< /details >}}
|
||||||
|
|
||||||
|
@ -555,12 +576,6 @@ endpoint. It's recommended to use [authelia-authrequest.conf](#authelia-authrequ
|
||||||
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
|
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
|
||||||
auth_request /internal/authelia/authz/basic;
|
auth_request /internal/authelia/authz/basic;
|
||||||
|
|
||||||
## Comment this line if you're using nginx without the http_set_misc module.
|
|
||||||
set_escape_uri $target_url $scheme://$http_host$request_uri;
|
|
||||||
|
|
||||||
## Uncomment this line if you're using NGINX without the http_set_misc module.
|
|
||||||
# set $target_url $scheme://$http_host$request_uri;
|
|
||||||
|
|
||||||
## Save the upstream response headers from Authelia to variables.
|
## Save the upstream response headers from Authelia to variables.
|
||||||
auth_request_set $user $upstream_http_remote_user;
|
auth_request_set $user $upstream_http_remote_user;
|
||||||
auth_request_set $groups $upstream_http_remote_groups;
|
auth_request_set $groups $upstream_http_remote_groups;
|
||||||
|
|
|
@ -61,6 +61,23 @@ networks to the trusted proxy list in [Traefik]:
|
||||||
|
|
||||||
See the [Entry Points](https://doc.traefik.io/traefik/routing/entrypoints) documentation for more information.
|
See the [Entry Points](https://doc.traefik.io/traefik/routing/entrypoints) documentation for more information.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
[Traefik] utilizes the [ForwardAuth](../../reference/guides/proxy-authorization.md#forwardauth) Authz implementation. The
|
||||||
|
associated [Metadata](../../reference/guides/proxy-authorization.md#forwardauth-metadata) should be considered required.
|
||||||
|
|
||||||
|
The examples below assume you are using the default
|
||||||
|
[Authz Endpoints Configuration](../../configuration/miscellaneous/server-endpoints-authz.md) or one similar to the
|
||||||
|
following minimal configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
endpoints:
|
||||||
|
authz:
|
||||||
|
forward-auth:
|
||||||
|
implementation: ForwardAuth
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Below you will find commented examples of the following docker deployment:
|
Below you will find commented examples of the following docker deployment:
|
||||||
|
|
|
@ -74,6 +74,23 @@ following are the assumptions we make:
|
||||||
* This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're
|
* This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're
|
||||||
just testing or you want ot use that specific domain
|
just testing or you want ot use that specific domain
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
[Traefik] utilizes the [ForwardAuth](../../reference/guides/proxy-authorization.md#forwardauth) Authz implementation. The
|
||||||
|
associated [Metadata](../../reference/guides/proxy-authorization.md#forwardauth-metadata) should be considered required.
|
||||||
|
|
||||||
|
The examples below assume you are using the default
|
||||||
|
[Authz Endpoints Configuration](../../configuration/miscellaneous/server-endpoints-authz.md) or one similar to the
|
||||||
|
following minimal configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
endpoints:
|
||||||
|
authz:
|
||||||
|
forward-auth:
|
||||||
|
implementation: ForwardAuth
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Below you will find commented examples of the following docker deployment:
|
Below you will find commented examples of the following docker deployment:
|
||||||
|
|
|
@ -64,7 +64,7 @@ completely unset.
|
||||||
### ForwardAuth
|
### ForwardAuth
|
||||||
|
|
||||||
This is the implementation which supports [Traefik] via the [ForwardAuth Middleware], [Caddy] via the
|
This is the implementation which supports [Traefik] via the [ForwardAuth Middleware], [Caddy] via the
|
||||||
[forward_auth directive], and [Skipper] via the [webhook auth filter].
|
[forward_auth directive], [HAProxy] via the [auth-request lua plugin], and [Skipper] via the [webhook auth filter].
|
||||||
|
|
||||||
#### ForwardAuth Metadata
|
#### ForwardAuth Metadata
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ This is the implementation which supports [Traefik] via the [ForwardAuth Middlew
|
||||||
|
|
||||||
### ExtAuthz
|
### ExtAuthz
|
||||||
|
|
||||||
This is the implementation which supports [Envoy] via the [ExtAuthz Extension Filter].
|
This is the implementation which supports [Envoy] via the [HTTP ExtAuthz Filter].
|
||||||
|
|
||||||
#### ExtAuthz Metadata
|
#### ExtAuthz Metadata
|
||||||
|
|
||||||
|
@ -110,26 +110,31 @@ This is the implementation which supports [Envoy] via the [ExtAuthz Extension Fi
|
||||||
|
|
||||||
### AuthRequest
|
### AuthRequest
|
||||||
|
|
||||||
This is the implementation which supports [NGINX] via the [auth_request HTTP module] and [HAProxy] via the
|
This is the implementation which supports [NGINX] via the [auth_request HTTP module], and can technically support
|
||||||
[auth-request lua plugin].
|
[HAProxy] via the [auth-request lua plugin].
|
||||||
|
|
||||||
| Metadata | Source | Key |
|
#### AuthRequest Metadata
|
||||||
|:------------:|:--------:|:-------------------:|
|
|
||||||
| Method | [Header] | `X-Original-Method` |
|
|
||||||
| Scheme | [Header] | `X-Original-URL` |
|
|
||||||
| Hostname | [Header] | `X-Original-URL` |
|
|
||||||
| Path | [Header] | `X-Original-URL` |
|
|
||||||
| IP | [Header] | [X-Forwarded-For] |
|
|
||||||
| Authelia URL | _N/A_ | _N/A_ |
|
|
||||||
|
|
||||||
_**Note:** This endpoint does not support automatic redirection. This is because there is no support on NGINX's side to
|
| Metadata | Source | Key |
|
||||||
achieve this with `ngx_http_auth_request_module` and the redirection must be performed within the NGINX configuration._
|
|:------------:|:----------------------------:|:-------------------:|
|
||||||
|
| Method | [Header] | `X-Original-Method` |
|
||||||
|
| Scheme | [Header] | `X-Original-URL` |
|
||||||
|
| Hostname | [Header] | `X-Original-URL` |
|
||||||
|
| Path | [Header] | `X-Original-URL` |
|
||||||
|
| IP | [Header] | [X-Forwarded-For] |
|
||||||
|
| Authelia URL | Session Cookie Configuration | `authelia_url` |
|
||||||
|
|
||||||
|
_**Note:** This endpoint does not support automatic redirection. This is because there is no support on [NGINX]'s side
|
||||||
|
to achieve this with `ngx_http_auth_request_module` and the redirection must be performed within the [NGINX]
|
||||||
|
configuration. However we return the appropriate URL to redirect users to with the `Location` header which
|
||||||
|
simplifies this process especially for multi-cookie domain deployments._
|
||||||
|
|
||||||
#### AuthRequest Metadata Alternatives
|
#### AuthRequest Metadata Alternatives
|
||||||
|
|
||||||
| Metadata | Alternative Type | Source | Key |
|
| Metadata | Alternative Type | Source | Key |
|
||||||
|:--------:|:----------------:|:----------:|:---------:|
|
|:------------:|:----------------:|:--------------:|:--------------:|
|
||||||
| IP | Fallback | TCP Packet | Source IP |
|
| IP | Fallback | TCP Packet | Source IP |
|
||||||
|
| Authelia URL | Override | Query Argument | `authelia_url` |
|
||||||
|
|
||||||
### Legacy
|
### Legacy
|
||||||
|
|
||||||
|
@ -213,7 +218,7 @@ or the header is malformed it will respond with the [WWW-Authenticate] header.
|
||||||
[Skipper]: https://opensource.zalando.com/skipper/
|
[Skipper]: https://opensource.zalando.com/skipper/
|
||||||
[HAProxy]: http://www.haproxy.org/
|
[HAProxy]: http://www.haproxy.org/
|
||||||
|
|
||||||
[ExtAuthz Extension Filter]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto#envoy-v3-api-msg-extensions-filters-http-ext-authz-v3-extauthz
|
[HTTP ExtAuthz Filter]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto#envoy-v3-api-msg-extensions-filters-http-ext-authz-v3-extauthz
|
||||||
[auth_request HTTP module]: https://nginx.org/en/docs/http/ngx_http_auth_request_module.html
|
[auth_request HTTP module]: https://nginx.org/en/docs/http/ngx_http_auth_request_module.html
|
||||||
[auth-request lua plugin]: https://github.com/TimWolla/haproxy-auth-request
|
[auth-request lua plugin]: https://github.com/TimWolla/haproxy-auth-request
|
||||||
[ForwardAuth Middleware]: https://doc.traefik.io/traefik/middlewares/http/forwardauth/
|
[ForwardAuth Middleware]: https://doc.traefik.io/traefik/middlewares/http/forwardauth/
|
||||||
|
|
|
@ -81,6 +81,8 @@ The following functions which mimic the behaviour of helm exist in most templati
|
||||||
- indent
|
- indent
|
||||||
- nindent
|
- nindent
|
||||||
- uuidv4
|
- uuidv4
|
||||||
|
- urlquery
|
||||||
|
- urlunquery (opposite of urlquery)
|
||||||
|
|
||||||
See the [Helm Documentation](https://helm.sh/docs/chart_template_guide/function_list/) for more information. Please
|
See the [Helm Documentation](https://helm.sh/docs/chart_template_guide/function_list/) for more information. Please
|
||||||
note that only the functions listed above are supported and the functions don't necessarily behave exactly the same.
|
note that only the functions listed above are supported and the functions don't necessarily behave exactly the same.
|
||||||
|
|
|
@ -15,12 +15,22 @@ const (
|
||||||
ActionResetPassword = "ResetPassword"
|
ActionResetPassword = "ResetPassword"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
anonymous = "<anonymous>"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
headerAuthorization = []byte(fasthttp.HeaderAuthorization)
|
headerAuthorization = []byte(fasthttp.HeaderAuthorization)
|
||||||
headerWWWAuthenticate = []byte(fasthttp.HeaderWWWAuthenticate)
|
headerWWWAuthenticate = []byte(fasthttp.HeaderWWWAuthenticate)
|
||||||
|
|
||||||
headerProxyAuthorization = []byte(fasthttp.HeaderProxyAuthorization)
|
headerProxyAuthorization = []byte(fasthttp.HeaderProxyAuthorization)
|
||||||
headerProxyAuthenticate = []byte(fasthttp.HeaderProxyAuthenticate)
|
headerProxyAuthenticate = []byte(fasthttp.HeaderProxyAuthenticate)
|
||||||
|
|
||||||
|
headerSessionUsername = []byte("Session-Username")
|
||||||
|
headerRemoteUser = []byte("Remote-User")
|
||||||
|
headerRemoteGroups = []byte("Remote-Groups")
|
||||||
|
headerRemoteName = []byte("Remote-Name")
|
||||||
|
headerRemoteEmail = []byte("Remote-Email")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -31,14 +41,6 @@ var (
|
||||||
headerValueAuthenticateBasic = []byte(`Basic realm="Authorization Required"`)
|
headerValueAuthenticateBasic = []byte(`Basic realm="Authorization Required"`)
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
headerSessionUsername = []byte("Session-Username")
|
|
||||||
headerRemoteUser = []byte("Remote-User")
|
|
||||||
headerRemoteGroups = []byte("Remote-Groups")
|
|
||||||
headerRemoteName = []byte("Remote-Name")
|
|
||||||
headerRemoteEmail = []byte("Remote-Email")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
queryArgRD = "rd"
|
queryArgRD = "rd"
|
||||||
queryArgRM = "rm"
|
queryArgRM = "rm"
|
||||||
|
@ -87,6 +89,8 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
logFmtAuthzRedirect = "Access to %s (method %s) is not authorized to user %s, responding with status code %d with location redirect to %s"
|
||||||
|
|
||||||
logFmtAuthorizationPrefix = "Authorization Request with id '%s' on client with id '%s' "
|
logFmtAuthorizationPrefix = "Authorization Request with id '%s' on client with id '%s' "
|
||||||
|
|
||||||
logFmtErrConsentCantDetermineConsentMode = logFmtAuthorizationPrefix + "could not be processed: error occurred generating consent: client consent mode could not be reliably determined"
|
logFmtErrConsentCantDetermineConsentMode = logFmtAuthorizationPrefix + "could not be processed: error occurred generating consent: client consent mode could not be reliably determined"
|
||||||
|
|
|
@ -21,9 +21,9 @@ func (authz *Authz) Handler(ctx *middlewares.AutheliaCtx) {
|
||||||
)
|
)
|
||||||
|
|
||||||
if object, err = authz.handleGetObject(ctx); err != nil {
|
if object, err = authz.handleGetObject(ctx); err != nil {
|
||||||
ctx.Logger.Errorf("Error getting original request object: %v", err)
|
ctx.Logger.WithError(err).Error("Error getting Target URL and Request Method")
|
||||||
|
|
||||||
ctx.ReplyUnauthorized()
|
ctx.ReplyStatusCode(authz.config.StatusCodeBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -31,23 +31,23 @@ func (authz *Authz) Handler(ctx *middlewares.AutheliaCtx) {
|
||||||
if !utils.IsURISecure(object.URL) {
|
if !utils.IsURISecure(object.URL) {
|
||||||
ctx.Logger.Errorf("Target URL '%s' has an insecure scheme '%s', only the 'https' and 'wss' schemes are supported so session cookies can be transmitted securely", object.URL.String(), object.URL.Scheme)
|
ctx.Logger.Errorf("Target URL '%s' has an insecure scheme '%s', only the 'https' and 'wss' schemes are supported so session cookies can be transmitted securely", object.URL.String(), object.URL.Scheme)
|
||||||
|
|
||||||
ctx.ReplyUnauthorized()
|
ctx.ReplyStatusCode(authz.config.StatusCodeBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if provider, err = ctx.GetSessionProviderByTargetURL(object.URL); err != nil {
|
if provider, err = ctx.GetSessionProviderByTargetURL(object.URL); err != nil {
|
||||||
ctx.Logger.WithError(err).Errorf("Target URL '%s' does not appear to be configured as a session domain", object.URL.String())
|
ctx.Logger.WithError(err).WithField("target_url", object.URL.String()).Error("Target URL does not appear to have a relevant session cookies configuration")
|
||||||
|
|
||||||
ctx.ReplyUnauthorized()
|
ctx.ReplyStatusCode(authz.config.StatusCodeBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if autheliaURL, err = authz.getAutheliaURL(ctx, provider); err != nil {
|
if autheliaURL, err = authz.getAutheliaURL(ctx, provider); err != nil {
|
||||||
ctx.Logger.WithError(err).Error("Error occurred trying to determine the URL of the portal")
|
ctx.Logger.WithError(err).WithField("target_url", object.URL.String()).Error("Error occurred trying to determine the external Authelia URL for Target URL")
|
||||||
|
|
||||||
ctx.ReplyUnauthorized()
|
ctx.ReplyStatusCode(authz.config.StatusCodeBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -104,23 +104,23 @@ func (authz *Authz) Handler(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (authz *Authz) getAutheliaURL(ctx *middlewares.AutheliaCtx, provider *session.Session) (autheliaURL *url.URL, err error) {
|
func (authz *Authz) getAutheliaURL(ctx *middlewares.AutheliaCtx, provider *session.Session) (autheliaURL *url.URL, err error) {
|
||||||
if authz.handleGetAutheliaURL == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if autheliaURL, err = authz.handleGetAutheliaURL(ctx); err != nil {
|
if autheliaURL, err = authz.handleGetAutheliaURL(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if autheliaURL != nil || authz.legacy {
|
switch {
|
||||||
|
case authz.implementation == AuthzImplLegacy:
|
||||||
return autheliaURL, nil
|
return autheliaURL, nil
|
||||||
|
case autheliaURL != nil:
|
||||||
|
switch {
|
||||||
|
case utils.HasURIDomainSuffix(autheliaURL, provider.Config.Domain):
|
||||||
|
return autheliaURL, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("authelia url '%s' is not valid for detected domain '%s' as the url does not have the domain as a suffix", autheliaURL.String(), provider.Config.Domain)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if provider.Config.AutheliaURL != nil {
|
if provider.Config.AutheliaURL != nil {
|
||||||
if authz.legacy {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.Config.AutheliaURL, nil
|
return provider.Config.AutheliaURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,6 +134,10 @@ func (authz *Authz) getRedirectionURL(object *authorization.Object, autheliaURL
|
||||||
|
|
||||||
redirectionURL, _ = url.ParseRequestURI(autheliaURL.String())
|
redirectionURL, _ = url.ParseRequestURI(autheliaURL.String())
|
||||||
|
|
||||||
|
if redirectionURL.Path == "" {
|
||||||
|
redirectionURL.Path = "/"
|
||||||
|
}
|
||||||
|
|
||||||
qry := redirectionURL.Query()
|
qry := redirectionURL.Query()
|
||||||
|
|
||||||
qry.Set(queryArgRD, object.URL.String())
|
qry.Set(queryArgRD, object.URL.String())
|
||||||
|
@ -151,10 +155,10 @@ func (authz *Authz) authn(ctx *middlewares.AutheliaCtx, provider *session.Sessio
|
||||||
for _, strategy = range authz.strategies {
|
for _, strategy = range authz.strategies {
|
||||||
if authn, err = strategy.Get(ctx, provider); err != nil {
|
if authn, err = strategy.Get(ctx, provider); err != nil {
|
||||||
if strategy.CanHandleUnauthorized() {
|
if strategy.CanHandleUnauthorized() {
|
||||||
return Authn{Type: authn.Type, Level: authentication.NotAuthenticated}, strategy, err
|
return Authn{Type: authn.Type, Level: authentication.NotAuthenticated, Username: anonymous}, strategy, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return Authn{Type: authn.Type, Level: authentication.NotAuthenticated}, nil, err
|
return Authn{Type: authn.Type, Level: authentication.NotAuthenticated, Username: anonymous}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if authn.Level != authentication.NotAuthenticated {
|
if authn.Level != authentication.NotAuthenticated {
|
||||||
|
|
|
@ -81,13 +81,14 @@ type CookieSessionAuthnStrategy struct {
|
||||||
|
|
||||||
// Get returns the Authn information for this AuthnStrategy.
|
// Get returns the Authn information for this AuthnStrategy.
|
||||||
func (s *CookieSessionAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, provider *session.Session) (authn Authn, err error) {
|
func (s *CookieSessionAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, provider *session.Session) (authn Authn, err error) {
|
||||||
authn = Authn{
|
|
||||||
Type: AuthnTypeCookie,
|
|
||||||
Level: authentication.NotAuthenticated,
|
|
||||||
}
|
|
||||||
|
|
||||||
var userSession session.UserSession
|
var userSession session.UserSession
|
||||||
|
|
||||||
|
authn = Authn{
|
||||||
|
Type: AuthnTypeCookie,
|
||||||
|
Level: authentication.NotAuthenticated,
|
||||||
|
Username: anonymous,
|
||||||
|
}
|
||||||
|
|
||||||
if userSession, err = provider.GetSession(ctx.RequestCtx); err != nil {
|
if userSession, err = provider.GetSession(ctx.RequestCtx); err != nil {
|
||||||
return authn, fmt.Errorf("failed to retrieve user session: %w", err)
|
return authn, fmt.Errorf("failed to retrieve user session: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -108,21 +109,21 @@ func (s *CookieSessionAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, provider
|
||||||
|
|
||||||
if invalid := handleVerifyGETAuthnCookieValidate(ctx, provider, &userSession, s.refreshEnabled, s.refreshInterval); invalid {
|
if invalid := handleVerifyGETAuthnCookieValidate(ctx, provider, &userSession, s.refreshEnabled, s.refreshInterval); invalid {
|
||||||
if err = ctx.DestroySession(); err != nil {
|
if err = ctx.DestroySession(); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to destroy user session: %+v", err)
|
ctx.Logger.WithError(err).Errorf("Unable to destroy user session")
|
||||||
}
|
}
|
||||||
|
|
||||||
userSession = provider.NewDefaultUserSession()
|
userSession = provider.NewDefaultUserSession()
|
||||||
userSession.LastActivity = ctx.Clock.Now().Unix()
|
userSession.LastActivity = ctx.Clock.Now().Unix()
|
||||||
|
|
||||||
if err = provider.SaveSession(ctx.RequestCtx, userSession); err != nil {
|
if err = provider.SaveSession(ctx.RequestCtx, userSession); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to save updated user session: %+v", err)
|
ctx.Logger.WithError(err).Error("Unable to save updated user session")
|
||||||
}
|
}
|
||||||
|
|
||||||
return authn, nil
|
return authn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = provider.SaveSession(ctx.RequestCtx, userSession); err != nil {
|
if err = provider.SaveSession(ctx.RequestCtx, userSession); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to save updated user session: %+v", err)
|
ctx.Logger.WithError(err).Error("Unable to save updated user session")
|
||||||
}
|
}
|
||||||
|
|
||||||
return Authn{
|
return Authn{
|
||||||
|
@ -164,8 +165,9 @@ func (s *HeaderAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, _ *session.Sessi
|
||||||
)
|
)
|
||||||
|
|
||||||
authn = Authn{
|
authn = Authn{
|
||||||
Type: s.authn,
|
Type: s.authn,
|
||||||
Level: authentication.NotAuthenticated,
|
Level: authentication.NotAuthenticated,
|
||||||
|
Username: anonymous,
|
||||||
}
|
}
|
||||||
|
|
||||||
if value = ctx.Request.Header.PeekBytes(s.headerAuthorize); value == nil {
|
if value = ctx.Request.Header.PeekBytes(s.headerAuthorize); value == nil {
|
||||||
|
@ -195,7 +197,7 @@ func (s *HeaderAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, _ *session.Sessi
|
||||||
|
|
||||||
if details, err = ctx.Providers.UserProvider.GetDetails(username); err != nil {
|
if details, err = ctx.Providers.UserProvider.GetDetails(username); err != nil {
|
||||||
if errors.Is(err, authentication.ErrUserNotFound) {
|
if errors.Is(err, authentication.ErrUserNotFound) {
|
||||||
ctx.Logger.Errorf("Error occurred while attempting to get user details for user '%s': the user was not found indicating they were deleted, disabled, or otherwise no longer authorized to login", username)
|
ctx.Logger.WithField("username", username).Error("Error occurred while attempting to get user details for user: the user was not found indicating they were deleted, disabled, or otherwise no longer authorized to login")
|
||||||
|
|
||||||
return authn, err
|
return authn, err
|
||||||
}
|
}
|
||||||
|
@ -237,7 +239,8 @@ func (s *HeaderLegacyAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, _ *session
|
||||||
)
|
)
|
||||||
|
|
||||||
authn = Authn{
|
authn = Authn{
|
||||||
Level: authentication.NotAuthenticated,
|
Level: authentication.NotAuthenticated,
|
||||||
|
Username: anonymous,
|
||||||
}
|
}
|
||||||
|
|
||||||
if qryValueAuth := ctx.QueryArgs().PeekBytes(qryArgAuth); bytes.Equal(qryValueAuth, qryValueBasic) {
|
if qryValueAuth := ctx.QueryArgs().PeekBytes(qryArgAuth); bytes.Equal(qryValueAuth, qryValueBasic) {
|
||||||
|
@ -280,7 +283,7 @@ func (s *HeaderLegacyAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, _ *session
|
||||||
|
|
||||||
if details, err = ctx.Providers.UserProvider.GetDetails(username); err != nil {
|
if details, err = ctx.Providers.UserProvider.GetDetails(username); err != nil {
|
||||||
if errors.Is(err, authentication.ErrUserNotFound) {
|
if errors.Is(err, authentication.ErrUserNotFound) {
|
||||||
ctx.Logger.Errorf("Error occurred while attempting to get user details for user '%s': the user was not found indicating they were deleted, disabled, or otherwise no longer authorized to login", username)
|
ctx.Logger.WithField("username", username).Error("Error occurred while attempting to get user details for user: the user was not found indicating they were deleted, disabled, or otherwise no longer authorized to login")
|
||||||
|
|
||||||
return authn, err
|
return authn, err
|
||||||
}
|
}
|
||||||
|
@ -309,13 +312,13 @@ func handleVerifyGETAuthnCookieValidate(ctx *middlewares.AutheliaCtx, provider *
|
||||||
isAnonymous := userSession.Username == ""
|
isAnonymous := userSession.Username == ""
|
||||||
|
|
||||||
if isAnonymous && userSession.AuthenticationLevel != authentication.NotAuthenticated {
|
if isAnonymous && userSession.AuthenticationLevel != authentication.NotAuthenticated {
|
||||||
ctx.Logger.Errorf("Session for anonymous user has an authentication level of '%s': this may be a sign of a compromise", userSession.AuthenticationLevel)
|
ctx.Logger.WithFields(map[string]any{"username": anonymous, "level": userSession.AuthenticationLevel.String()}).Errorf("Session for user has an invalid authentication level: this may be a sign of a compromise")
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if invalid = handleVerifyGETAuthnCookieValidateInactivity(ctx, provider, userSession, isAnonymous); invalid {
|
if invalid = handleVerifyGETAuthnCookieValidateInactivity(ctx, provider, userSession, isAnonymous); invalid {
|
||||||
ctx.Logger.Infof("Session for user '%s' not marked as remembereded has exceeded configured session inactivity", userSession.Username)
|
ctx.Logger.WithField("username", userSession.Username).Info("Session for user not marked as remembered has exceeded configured session inactivity")
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -325,7 +328,7 @@ func handleVerifyGETAuthnCookieValidate(ctx *middlewares.AutheliaCtx, provider *
|
||||||
}
|
}
|
||||||
|
|
||||||
if username := ctx.Request.Header.PeekBytes(headerSessionUsername); username != nil && !strings.EqualFold(string(username), userSession.Username) {
|
if username := ctx.Request.Header.PeekBytes(headerSessionUsername); username != nil && !strings.EqualFold(string(username), userSession.Username) {
|
||||||
ctx.Logger.Warnf("Session for user '%s' does not match the Session-Username header with value '%s' which could be a sign of a cookie hijack", userSession.Username, username)
|
ctx.Logger.WithField("username", userSession.Username).Warnf("Session for user does not match the Session-Username header with value '%s' which could be a sign of a cookie hijack", username)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -342,7 +345,7 @@ func handleVerifyGETAuthnCookieValidateInactivity(ctx *middlewares.AutheliaCtx,
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Tracef("Inactivity report for user '%s'. Current Time: %d, Last Activity: %d, Maximum Inactivity: %d.", userSession.Username, ctx.Clock.Now().Unix(), userSession.LastActivity, int(provider.Config.Inactivity.Seconds()))
|
ctx.Logger.WithField("username", userSession.Username).Tracef("Inactivity report for user. Current Time: %d, Last Activity: %d, Maximum Inactivity: %d.", ctx.Clock.Now().Unix(), userSession.LastActivity, int(provider.Config.Inactivity.Seconds()))
|
||||||
|
|
||||||
return time.Unix(userSession.LastActivity, 0).Add(provider.Config.Inactivity).Before(ctx.Clock.Now())
|
return time.Unix(userSession.LastActivity, 0).Add(provider.Config.Inactivity).Before(ctx.Clock.Now())
|
||||||
}
|
}
|
||||||
|
@ -352,13 +355,13 @@ func handleVerifyGETAuthnCookieValidateUpdate(ctx *middlewares.AutheliaCtx, user
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Tracef("Checking if we need check the authentication backend for an updated profile for user '%s'", userSession.Username)
|
ctx.Logger.WithField("username", userSession.Username).Trace("Checking if we need check the authentication backend for an updated profile for user")
|
||||||
|
|
||||||
if interval != schema.RefreshIntervalAlways && userSession.RefreshTTL.After(ctx.Clock.Now()) {
|
if interval != schema.RefreshIntervalAlways && userSession.RefreshTTL.After(ctx.Clock.Now()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Debugf("Checking the authentication backend for an updated profile for user '%s'", userSession.Username)
|
ctx.Logger.WithField("username", userSession.Username).Debug("Checking the authentication backend for an updated profile for user")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
details *authentication.UserDetails
|
details *authentication.UserDetails
|
||||||
|
@ -367,12 +370,12 @@ func handleVerifyGETAuthnCookieValidateUpdate(ctx *middlewares.AutheliaCtx, user
|
||||||
|
|
||||||
if details, err = ctx.Providers.UserProvider.GetDetails(userSession.Username); err != nil {
|
if details, err = ctx.Providers.UserProvider.GetDetails(userSession.Username); err != nil {
|
||||||
if errors.Is(err, authentication.ErrUserNotFound) {
|
if errors.Is(err, authentication.ErrUserNotFound) {
|
||||||
ctx.Logger.Errorf("Error occurred while attempting to update user details for user '%s': the user was not found indicating they were deleted, disabled, or otherwise no longer authorized to login", userSession.Username)
|
ctx.Logger.WithField("username", userSession.Username).Error("Error occurred while attempting to update user details for user: the user was not found indicating they were deleted, disabled, or otherwise no longer authorized to login")
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Errorf("Error occurred while attempting to update user details for user '%s': %v", userSession.Username, err)
|
ctx.Logger.WithError(err).WithField("username", userSession.Username).Error("Error occurred while attempting to update user details for user")
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -389,12 +392,12 @@ func handleVerifyGETAuthnCookieValidateUpdate(ctx *middlewares.AutheliaCtx, user
|
||||||
}
|
}
|
||||||
|
|
||||||
if !diffEmails && !diffGroups && !diffDisplayName {
|
if !diffEmails && !diffGroups && !diffDisplayName {
|
||||||
ctx.Logger.Tracef("Updated profile not detected for user '%s'", userSession.Username)
|
ctx.Logger.WithField("username", userSession.Username).Trace("Updated profile not detected for user")
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Debugf("Updated profile detected for user '%s'", userSession.Username)
|
ctx.Logger.WithField("username", userSession.Username).Debug("Updated profile detected for user")
|
||||||
|
|
||||||
if ctx.Logger.Level >= logrus.TraceLevel {
|
if ctx.Logger.Level >= logrus.TraceLevel {
|
||||||
generateVerifySessionHasUpToDateProfileTraceLogs(ctx, userSession, details)
|
generateVerifySessionHasUpToDateProfileTraceLogs(ctx, userSession, details)
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
@ -22,31 +23,10 @@ func (b *AuthzBuilder) WithStrategies(strategies ...AuthnStrategy) *AuthzBuilder
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithStrategyCookie adds the Cookie header strategy to the strategies in this builder.
|
|
||||||
func (b *AuthzBuilder) WithStrategyCookie(refreshInterval time.Duration) *AuthzBuilder {
|
|
||||||
b.strategies = append(b.strategies, NewCookieSessionAuthnStrategy(refreshInterval))
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithStrategyAuthorization adds the Authorization header strategy to the strategies in this builder.
|
|
||||||
func (b *AuthzBuilder) WithStrategyAuthorization() *AuthzBuilder {
|
|
||||||
b.strategies = append(b.strategies, NewHeaderAuthorizationAuthnStrategy())
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithStrategyProxyAuthorization adds the Proxy-Authorization header strategy to the strategies in this builder.
|
|
||||||
func (b *AuthzBuilder) WithStrategyProxyAuthorization() *AuthzBuilder {
|
|
||||||
b.strategies = append(b.strategies, NewHeaderProxyAuthorizationAuthnStrategy())
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithImplementationLegacy configures this builder to output an Authz which is used with the Legacy
|
// WithImplementationLegacy configures this builder to output an Authz which is used with the Legacy
|
||||||
// implementation which is a mix of the other implementations and usually works with most proxies.
|
// implementation which is a mix of the other implementations and usually works with most proxies.
|
||||||
func (b *AuthzBuilder) WithImplementationLegacy() *AuthzBuilder {
|
func (b *AuthzBuilder) WithImplementationLegacy() *AuthzBuilder {
|
||||||
b.impl = AuthzImplLegacy
|
b.implementation = AuthzImplLegacy
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
@ -54,7 +34,7 @@ func (b *AuthzBuilder) WithImplementationLegacy() *AuthzBuilder {
|
||||||
// WithImplementationForwardAuth configures this builder to output an Authz which is used with the ForwardAuth
|
// WithImplementationForwardAuth configures this builder to output an Authz which is used with the ForwardAuth
|
||||||
// implementation traditionally used by Traefik, Caddy, and Skipper.
|
// implementation traditionally used by Traefik, Caddy, and Skipper.
|
||||||
func (b *AuthzBuilder) WithImplementationForwardAuth() *AuthzBuilder {
|
func (b *AuthzBuilder) WithImplementationForwardAuth() *AuthzBuilder {
|
||||||
b.impl = AuthzImplForwardAuth
|
b.implementation = AuthzImplForwardAuth
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
@ -62,7 +42,7 @@ func (b *AuthzBuilder) WithImplementationForwardAuth() *AuthzBuilder {
|
||||||
// WithImplementationAuthRequest configures this builder to output an Authz which is used with the AuthRequest
|
// WithImplementationAuthRequest configures this builder to output an Authz which is used with the AuthRequest
|
||||||
// implementation traditionally used by NGINX.
|
// implementation traditionally used by NGINX.
|
||||||
func (b *AuthzBuilder) WithImplementationAuthRequest() *AuthzBuilder {
|
func (b *AuthzBuilder) WithImplementationAuthRequest() *AuthzBuilder {
|
||||||
b.impl = AuthzImplAuthRequest
|
b.implementation = AuthzImplAuthRequest
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
@ -70,7 +50,7 @@ func (b *AuthzBuilder) WithImplementationAuthRequest() *AuthzBuilder {
|
||||||
// WithImplementationExtAuthz configures this builder to output an Authz which is used with the ExtAuthz
|
// WithImplementationExtAuthz configures this builder to output an Authz which is used with the ExtAuthz
|
||||||
// implementation traditionally used by Envoy.
|
// implementation traditionally used by Envoy.
|
||||||
func (b *AuthzBuilder) WithImplementationExtAuthz() *AuthzBuilder {
|
func (b *AuthzBuilder) WithImplementationExtAuthz() *AuthzBuilder {
|
||||||
b.impl = AuthzImplExtAuthz
|
b.implementation = AuthzImplExtAuthz
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
@ -95,12 +75,6 @@ func (b *AuthzBuilder) WithConfig(config *schema.Configuration) *AuthzBuilder {
|
||||||
|
|
||||||
b.config = AuthzConfig{
|
b.config = AuthzConfig{
|
||||||
RefreshInterval: refreshInterval,
|
RefreshInterval: refreshInterval,
|
||||||
Domains: []AuthzDomain{
|
|
||||||
{
|
|
||||||
Name: fmt.Sprintf(".%s", config.Session.Domain),
|
|
||||||
PortalURL: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return b
|
return b
|
||||||
|
@ -140,24 +114,19 @@ func (b *AuthzBuilder) WithEndpointConfig(config schema.ServerAuthzEndpoint) *Au
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithAuthzConfig allows configuring the Authz config by providing a AuthzConfig directly. Recommended this is only
|
|
||||||
// used in testing and WithConfig is used instead.
|
|
||||||
func (b *AuthzBuilder) WithAuthzConfig(config AuthzConfig) *AuthzBuilder {
|
|
||||||
b.config = config
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build returns a new Authz from the currently configured options in this builder.
|
// Build returns a new Authz from the currently configured options in this builder.
|
||||||
func (b *AuthzBuilder) Build() (authz *Authz) {
|
func (b *AuthzBuilder) Build() (authz *Authz) {
|
||||||
authz = &Authz{
|
authz = &Authz{
|
||||||
config: b.config,
|
config: b.config,
|
||||||
strategies: b.strategies,
|
strategies: b.strategies,
|
||||||
handleAuthorized: handleAuthzAuthorizedStandard,
|
handleAuthorized: handleAuthzAuthorizedStandard,
|
||||||
|
implementation: b.implementation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authz.config.StatusCodeBadRequest = fasthttp.StatusBadRequest
|
||||||
|
|
||||||
if len(authz.strategies) == 0 {
|
if len(authz.strategies) == 0 {
|
||||||
switch b.impl {
|
switch b.implementation {
|
||||||
case AuthzImplLegacy:
|
case AuthzImplLegacy:
|
||||||
authz.strategies = []AuthnStrategy{NewHeaderLegacyAuthnStrategy(), NewCookieSessionAuthnStrategy(b.config.RefreshInterval)}
|
authz.strategies = []AuthnStrategy{NewHeaderLegacyAuthnStrategy(), NewCookieSessionAuthnStrategy(b.config.RefreshInterval)}
|
||||||
case AuthzImplAuthRequest:
|
case AuthzImplAuthRequest:
|
||||||
|
@ -167,9 +136,9 @@ func (b *AuthzBuilder) Build() (authz *Authz) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch b.impl {
|
switch b.implementation {
|
||||||
case AuthzImplLegacy:
|
case AuthzImplLegacy:
|
||||||
authz.legacy = true
|
authz.config.StatusCodeBadRequest = fasthttp.StatusUnauthorized
|
||||||
authz.handleGetObject = handleAuthzGetObjectLegacy
|
authz.handleGetObject = handleAuthzGetObjectLegacy
|
||||||
authz.handleUnauthorized = handleAuthzUnauthorizedLegacy
|
authz.handleUnauthorized = handleAuthzUnauthorizedLegacy
|
||||||
authz.handleGetAutheliaURL = handleAuthzPortalURLLegacy
|
authz.handleGetAutheliaURL = handleAuthzPortalURLLegacy
|
||||||
|
@ -180,6 +149,7 @@ func (b *AuthzBuilder) Build() (authz *Authz) {
|
||||||
case AuthzImplAuthRequest:
|
case AuthzImplAuthRequest:
|
||||||
authz.handleGetObject = handleAuthzGetObjectAuthRequest
|
authz.handleGetObject = handleAuthzGetObjectAuthRequest
|
||||||
authz.handleUnauthorized = handleAuthzUnauthorizedAuthRequest
|
authz.handleUnauthorized = handleAuthzUnauthorizedAuthRequest
|
||||||
|
authz.handleGetAutheliaURL = handleAuthzPortalURLFromQuery
|
||||||
case AuthzImplExtAuthz:
|
case AuthzImplExtAuthz:
|
||||||
authz.handleGetObject = handleAuthzGetObjectExtAuthz
|
authz.handleGetObject = handleAuthzGetObjectExtAuthz
|
||||||
authz.handleUnauthorized = handleAuthzUnauthorizedExtAuthz
|
authz.handleUnauthorized = handleAuthzUnauthorizedExtAuthz
|
||||||
|
|
|
@ -36,7 +36,13 @@ func handleAuthzGetObjectAuthRequest(ctx *middlewares.AutheliaCtx) (object autho
|
||||||
return authorization.NewObjectRaw(targetURL, method), nil
|
return authorization.NewObjectRaw(targetURL, method), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAuthzUnauthorizedAuthRequest(ctx *middlewares.AutheliaCtx, authn *Authn, _ *url.URL) {
|
func handleAuthzUnauthorizedAuthRequest(ctx *middlewares.AutheliaCtx, authn *Authn, redirectionURL *url.URL) {
|
||||||
ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d", authn.Object.URL.String(), authn.Method, authn.Username, fasthttp.StatusUnauthorized)
|
ctx.Logger.Infof(logFmtAuthzRedirect, authn.Object.URL.String(), authn.Method, authn.Username, fasthttp.StatusUnauthorized, redirectionURL)
|
||||||
ctx.ReplyUnauthorized()
|
|
||||||
|
switch authn.Object.Method {
|
||||||
|
case fasthttp.MethodHead:
|
||||||
|
ctx.SpecialRedirectNoBody(redirectionURL.String(), fasthttp.StatusUnauthorized)
|
||||||
|
default:
|
||||||
|
ctx.SpecialRedirect(redirectionURL.String(), fasthttp.StatusUnauthorized)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
"github.com/authelia/authelia/v4/internal/authorization"
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
"github.com/authelia/authelia/v4/internal/mocks"
|
"github.com/authelia/authelia/v4/internal/mocks"
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRunAuthRequestAuthzSuite(t *testing.T) {
|
func TestRunAuthRequestAuthzSuite(t *testing.T) {
|
||||||
|
@ -35,26 +35,36 @@ type AuthRequestAuthzSuite struct {
|
||||||
|
|
||||||
func (s *AuthRequestAuthzSuite) TestShouldHandleAllMethodsDeny() {
|
func (s *AuthRequestAuthzSuite) TestShouldHandleAllMethodsDeny() {
|
||||||
for _, method := range testRequestMethods {
|
for _, method := range testRequestMethods {
|
||||||
s.T().Run(fmt.Sprintf("OriginalMethod%s", method), func(t *testing.T) {
|
s.T().Run(fmt.Sprintf("Method%s", method), func(t *testing.T) {
|
||||||
for _, targetURI := range []*url.URL{
|
for _, pairURI := range []urlpair{
|
||||||
s.RequireParseRequestURI("https://one-factor.example.com"),
|
{s.RequireParseRequestURI("https://one-factor.example.com"), s.RequireParseRequestURI("https://auth.example.com/")},
|
||||||
s.RequireParseRequestURI("https://one-factor.example.com/subpath"),
|
{s.RequireParseRequestURI("https://one-factor.example.com/subpath"), s.RequireParseRequestURI("https://auth.example.com/")},
|
||||||
s.RequireParseRequestURI("https://one-factor.example2.com"),
|
{s.RequireParseRequestURI("https://one-factor.example2.com"), s.RequireParseRequestURI("https://auth.example2.com/")},
|
||||||
s.RequireParseRequestURI("https://one-factor.example2.com/subpath"),
|
{s.RequireParseRequestURI("https://one-factor.example2.com/subpath"), s.RequireParseRequestURI("https://auth.example2.com/")},
|
||||||
} {
|
} {
|
||||||
t.Run(targetURI.String(), func(t *testing.T) {
|
t.Run(pairURI.TargetURI.String(), func(t *testing.T) {
|
||||||
|
expected := s.RequireParseRequestURI(pairURI.AutheliaURI.String())
|
||||||
|
|
||||||
authz := s.Builder().Build()
|
authz := s.Builder().Build()
|
||||||
|
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
|
|
||||||
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
|
|
||||||
|
s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
|
query := expected.Query()
|
||||||
|
query.Set(queryArgRD, pairURI.TargetURI.String())
|
||||||
|
query.Set(queryArgRM, method)
|
||||||
|
expected.RawQuery = query.Encode()
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, expected.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -83,7 +93,7 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleInvalidMethodCharsDeny() {
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -109,7 +119,7 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleMissingXOriginalMethodDeny() {
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -128,7 +138,7 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleMissingXOriginalURLDeny() {
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -150,6 +160,8 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleAllMethodsAllow() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
@ -174,11 +186,7 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
|
@ -188,8 +196,21 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() {
|
||||||
assert.Equal(t, fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
expected := s.RequireParseRequestURI("https://auth.example.com/")
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
|
||||||
|
query := expected.Query()
|
||||||
|
query.Set(queryArgRD, targetURI.String())
|
||||||
|
query.Set(queryArgRM, method)
|
||||||
|
expected.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
switch method {
|
||||||
|
case fasthttp.MethodHead:
|
||||||
|
assert.Nil(t, mock.Ctx.Response.Body())
|
||||||
|
default:
|
||||||
|
assert.Equal(t, fmt.Sprintf(`<a href="%s">%d %s</a>`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusUnauthorized, fasthttp.StatusMessage(fasthttp.StatusUnauthorized)), string(mock.Ctx.Response.Body()))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expected.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -205,7 +226,7 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() {
|
||||||
}{
|
}{
|
||||||
{"Should401UnauthorizedWithNullByte",
|
{"Should401UnauthorizedWithNullByte",
|
||||||
[]byte{104, 116, 116, 112, 115, 58, 47, 47, 0, 110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109},
|
[]byte{104, 116, 116, 112, 115, 58, 47, 47, 0, 110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109},
|
||||||
fasthttp.StatusUnauthorized,
|
fasthttp.StatusBadRequest,
|
||||||
},
|
},
|
||||||
{"Should200OkWithoutNullByte",
|
{"Should200OkWithoutNullByte",
|
||||||
[]byte{104, 116, 116, 112, 115, 58, 47, 47, 110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109},
|
[]byte{104, 116, 116, 112, 115, 58, 47, 47, 110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109},
|
||||||
|
@ -226,6 +247,8 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() {
|
||||||
mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass
|
mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass
|
||||||
mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&mock.Ctx.Configuration)
|
mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&mock.Ctx.Configuration)
|
||||||
|
|
||||||
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
|
|
||||||
mock.Ctx.Request.Header.Set(testXOriginalMethod, method)
|
mock.Ctx.Request.Header.Set(testXOriginalMethod, method)
|
||||||
mock.Ctx.Request.Header.SetBytesKV([]byte(testXOriginalUrl), tc.uri)
|
mock.Ctx.Request.Header.SetBytesKV([]byte(testXOriginalUrl), tc.uri)
|
||||||
|
|
||||||
|
@ -255,17 +278,13 @@ func (s *AuthRequestAuthzSuite) TestShouldNotHandleExtAuthzAllMethodsAllow() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestExtAuthz(mock.Ctx, method, targetURI, true, false)
|
setRequestExtAuthz(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -291,17 +310,13 @@ func (s *AuthRequestAuthzSuite) TestShouldNotHandleExtAuthzAllMethodsAllowXHR()
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestExtAuthz(mock.Ctx, method, targetURI, x, x)
|
setRequestExtAuthz(mock.Ctx, method, targetURI, x, x)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -323,17 +338,13 @@ func (s *AuthRequestAuthzSuite) TestShouldNotHandleExtAuthzAllMethodsWithMethods
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestExtAuthz(mock.Ctx, method, targetURI, true, false)
|
setRequestExtAuthz(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -357,17 +368,13 @@ func (s *AuthRequestAuthzSuite) TestShouldNotHandleForwardAuthAllMethodsAllow()
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestForwardAuth(mock.Ctx, method, targetURI, true, false)
|
setRequestForwardAuth(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -393,17 +400,13 @@ func (s *AuthRequestAuthzSuite) TestShouldNotHandleForwardAuthAllMethodsAllowXHR
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestForwardAuth(mock.Ctx, method, targetURI, x, x)
|
setRequestForwardAuth(mock.Ctx, method, targetURI, x, x)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -425,17 +428,13 @@ func (s *AuthRequestAuthzSuite) TestShouldNotHandleForwardAuthAllMethodsWithMeth
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestForwardAuth(mock.Ctx, method, targetURI, true, false)
|
setRequestForwardAuth(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,12 @@ func handleAuthzUnauthorizedExtAuthz(ctx *middlewares.AutheliaCtx, authn *Authn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d with location redirect to %s", authn.Object.String(), authn.Method, authn.Username, statusCode, redirectionURL)
|
ctx.Logger.Infof(logFmtAuthzRedirect, authn.Object.String(), authn.Method, authn.Username, statusCode, redirectionURL)
|
||||||
ctx.SpecialRedirect(redirectionURL.String(), statusCode)
|
|
||||||
|
switch authn.Object.Method {
|
||||||
|
case fasthttp.MethodHead:
|
||||||
|
ctx.SpecialRedirectNoBody(redirectionURL.String(), statusCode)
|
||||||
|
default:
|
||||||
|
ctx.SpecialRedirect(redirectionURL.String(), statusCode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
"github.com/authelia/authelia/v4/internal/authorization"
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
"github.com/authelia/authelia/v4/internal/mocks"
|
"github.com/authelia/authelia/v4/internal/mocks"
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRunExtAuthzAuthzSuite(t *testing.T) {
|
func TestRunExtAuthzAuthzSuite(t *testing.T) {
|
||||||
|
@ -51,11 +51,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false)
|
s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false)
|
||||||
|
|
||||||
|
@ -98,11 +94,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsOverrideAutheliaURLDeny()
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.Request.Header.Set("X-Authelia-Url", pairURI.AutheliaURI.String())
|
mock.Ctx.Request.Header.Set("X-Authelia-Url", pairURI.AutheliaURI.String())
|
||||||
s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false)
|
s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false)
|
||||||
|
@ -148,8 +140,10 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsMissingAutheliaURLDeny()
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, fmt.Sprintf("%d %s", fasthttp.StatusBadRequest, fasthttp.StatusMessage(fasthttp.StatusBadRequest)), string(mock.Ctx.Response.Body()))
|
||||||
assert.Equal(t, "", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
assert.Equal(t, "", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderContentType)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -176,11 +170,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsXHRDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, pairURI.TargetURI, x, x)
|
s.setRequest(mock.Ctx, method, pairURI.TargetURI, x, x)
|
||||||
|
|
||||||
|
@ -222,17 +212,13 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleInvalidMethodCharsDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -249,17 +235,13 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleMissingHostDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, nil, true, false)
|
s.setRequest(mock.Ctx, method, nil, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -281,11 +263,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsAllow() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
|
@ -317,11 +295,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsAllowXHR() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, targetURI, x, x)
|
s.setRequest(mock.Ctx, method, targetURI, x, x)
|
||||||
|
|
||||||
|
@ -349,11 +323,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
|
@ -365,18 +335,23 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() {
|
||||||
} else {
|
} else {
|
||||||
expected := s.RequireParseRequestURI("https://auth.example.com/")
|
expected := s.RequireParseRequestURI("https://auth.example.com/")
|
||||||
|
|
||||||
switch method {
|
|
||||||
case fasthttp.MethodGet, fasthttp.MethodOptions, fasthttp.MethodHead:
|
|
||||||
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
|
||||||
default:
|
|
||||||
assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode())
|
|
||||||
}
|
|
||||||
|
|
||||||
query := expected.Query()
|
query := expected.Query()
|
||||||
query.Set(queryArgRD, targetURI.String())
|
query.Set(queryArgRD, targetURI.String())
|
||||||
query.Set(queryArgRM, method)
|
query.Set(queryArgRM, method)
|
||||||
expected.RawQuery = query.Encode()
|
expected.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
switch method {
|
||||||
|
case fasthttp.MethodHead:
|
||||||
|
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Nil(t, mock.Ctx.Response.Body())
|
||||||
|
case fasthttp.MethodGet, fasthttp.MethodOptions:
|
||||||
|
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, fmt.Sprintf(`<a href="%s">%d %s</a>`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusFound, fasthttp.StatusMessage(fasthttp.StatusFound)), string(mock.Ctx.Response.Body()))
|
||||||
|
default:
|
||||||
|
assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, fmt.Sprintf(`<a href="%s">%d %s</a>`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusSeeOther, fasthttp.StatusMessage(fasthttp.StatusSeeOther)), string(mock.Ctx.Response.Body()))
|
||||||
|
}
|
||||||
|
|
||||||
assert.Equal(t, expected.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
assert.Equal(t, expected.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -394,7 +369,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() {
|
||||||
}{
|
}{
|
||||||
{"Should401UnauthorizedWithNullByte",
|
{"Should401UnauthorizedWithNullByte",
|
||||||
[]byte("https"), []byte{0, 110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, "/path-example",
|
[]byte("https"), []byte{0, 110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, "/path-example",
|
||||||
fasthttp.StatusUnauthorized,
|
fasthttp.StatusBadRequest,
|
||||||
},
|
},
|
||||||
{"Should200OkWithoutNullByte",
|
{"Should200OkWithoutNullByte",
|
||||||
[]byte("https"), []byte{110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, "/path-example",
|
[]byte("https"), []byte{110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, "/path-example",
|
||||||
|
@ -415,11 +390,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() {
|
||||||
mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass
|
mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass
|
||||||
mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&mock.Ctx.Configuration)
|
mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&mock.Ctx.Configuration)
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.Request.SetHostBytes(tc.host)
|
mock.Ctx.Request.SetHostBytes(tc.host)
|
||||||
mock.Ctx.Request.Header.SetMethodBytes([]byte(method))
|
mock.Ctx.Request.Header.SetMethodBytes([]byte(method))
|
||||||
|
@ -458,7 +429,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldNotHandleAuthRequestAllMethodsAllow() {
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -478,17 +449,13 @@ func (s *ExtAuthzAuthzSuite) TestShouldNotHandleAuthRequestAllMethodsWithMethods
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestAuthRequest(mock.Ctx, method, targetURI, true, false)
|
setRequestAuthRequest(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -512,17 +479,13 @@ func (s *ExtAuthzAuthzSuite) TestShouldNotHandleForwardAuthAllMethodsAllow() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestForwardAuth(mock.Ctx, method, targetURI, true, false)
|
setRequestForwardAuth(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -548,17 +511,13 @@ func (s *ExtAuthzAuthzSuite) TestShouldNotHandleForwardAuthAllMethodsAllowXHR()
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestForwardAuth(mock.Ctx, method, targetURI, x, x)
|
setRequestForwardAuth(mock.Ctx, method, targetURI, x, x)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -580,17 +539,13 @@ func (s *ExtAuthzAuthzSuite) TestShouldNotHandleForwardAuthAllMethodsWithMethods
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestForwardAuth(mock.Ctx, method, targetURI, true, false)
|
setRequestForwardAuth(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,12 @@ func handleAuthzUnauthorizedForwardAuth(ctx *middlewares.AutheliaCtx, authn *Aut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d with location redirect to %s", authn.Object.String(), authn.Method, authn.Username, statusCode, redirectionURL)
|
ctx.Logger.Infof(logFmtAuthzRedirect, authn.Object.String(), authn.Method, authn.Username, statusCode, redirectionURL)
|
||||||
ctx.SpecialRedirect(redirectionURL.String(), statusCode)
|
|
||||||
|
switch authn.Object.Method {
|
||||||
|
case fasthttp.MethodHead:
|
||||||
|
ctx.SpecialRedirectNoBody(redirectionURL.String(), statusCode)
|
||||||
|
default:
|
||||||
|
ctx.SpecialRedirect(redirectionURL.String(), statusCode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
"github.com/authelia/authelia/v4/internal/authorization"
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
"github.com/authelia/authelia/v4/internal/mocks"
|
"github.com/authelia/authelia/v4/internal/mocks"
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRunForwardAuthAuthzSuite(t *testing.T) {
|
func TestRunForwardAuthAuthzSuite(t *testing.T) {
|
||||||
|
@ -51,11 +51,9 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false)
|
s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false)
|
||||||
|
|
||||||
|
@ -98,11 +96,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsOverrideAutheliaURLDen
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.RequestCtx.QueryArgs().Set("authelia_url", pairURI.AutheliaURI.String())
|
mock.Ctx.RequestCtx.QueryArgs().Set("authelia_url", pairURI.AutheliaURI.String())
|
||||||
s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false)
|
s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false)
|
||||||
|
@ -148,8 +142,10 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsMissingAutheliaURLDeny
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, fmt.Sprintf("%d %s", fasthttp.StatusBadRequest, fasthttp.StatusMessage(fasthttp.StatusBadRequest)), string(mock.Ctx.Response.Body()))
|
||||||
assert.Equal(t, "", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
assert.Equal(t, "", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderContentType)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -176,11 +172,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsXHRDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, pairURI.TargetURI, x, x)
|
s.setRequest(mock.Ctx, method, pairURI.TargetURI, x, x)
|
||||||
|
|
||||||
|
@ -220,17 +212,13 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleInvalidMethodCharsDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -247,11 +235,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleMissingHostDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
||||||
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https")
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https")
|
||||||
|
@ -261,7 +245,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleMissingHostDeny() {
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -283,11 +267,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsAllow() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
|
@ -313,11 +293,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
|
@ -329,18 +305,23 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() {
|
||||||
} else {
|
} else {
|
||||||
expected := s.RequireParseRequestURI("https://auth.example.com/")
|
expected := s.RequireParseRequestURI("https://auth.example.com/")
|
||||||
|
|
||||||
switch method {
|
|
||||||
case fasthttp.MethodGet, fasthttp.MethodOptions, fasthttp.MethodHead:
|
|
||||||
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
|
||||||
default:
|
|
||||||
assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode())
|
|
||||||
}
|
|
||||||
|
|
||||||
query := expected.Query()
|
query := expected.Query()
|
||||||
query.Set(queryArgRD, targetURI.String())
|
query.Set(queryArgRD, targetURI.String())
|
||||||
query.Set(queryArgRM, method)
|
query.Set(queryArgRM, method)
|
||||||
expected.RawQuery = query.Encode()
|
expected.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
switch method {
|
||||||
|
case fasthttp.MethodHead:
|
||||||
|
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Nil(t, mock.Ctx.Response.Body())
|
||||||
|
case fasthttp.MethodGet, fasthttp.MethodOptions:
|
||||||
|
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, fmt.Sprintf(`<a href="%s">%d %s</a>`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusFound, fasthttp.StatusMessage(fasthttp.StatusFound)), string(mock.Ctx.Response.Body()))
|
||||||
|
default:
|
||||||
|
assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, fmt.Sprintf(`<a href="%s">%d %s</a>`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusSeeOther, fasthttp.StatusMessage(fasthttp.StatusSeeOther)), string(mock.Ctx.Response.Body()))
|
||||||
|
}
|
||||||
|
|
||||||
assert.Equal(t, expected.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
assert.Equal(t, expected.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -365,11 +346,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsAllowXHR() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, method, targetURI, true, true)
|
s.setRequest(mock.Ctx, method, targetURI, true, true)
|
||||||
|
|
||||||
|
@ -392,7 +369,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() {
|
||||||
}{
|
}{
|
||||||
{"Should401UnauthorizedWithNullByte",
|
{"Should401UnauthorizedWithNullByte",
|
||||||
[]byte("https"), []byte{0, 110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, "/path-example",
|
[]byte("https"), []byte{0, 110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, "/path-example",
|
||||||
fasthttp.StatusUnauthorized,
|
fasthttp.StatusBadRequest,
|
||||||
},
|
},
|
||||||
{"Should200OkWithoutNullByte",
|
{"Should200OkWithoutNullByte",
|
||||||
[]byte("https"), []byte{110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, "/path-example",
|
[]byte("https"), []byte{110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, "/path-example",
|
||||||
|
@ -413,11 +390,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() {
|
||||||
mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass
|
mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass
|
||||||
mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&mock.Ctx.Configuration)
|
mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&mock.Ctx.Configuration)
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
||||||
mock.Ctx.Request.Header.SetBytesKV([]byte(fasthttp.HeaderXForwardedProto), tc.scheme)
|
mock.Ctx.Request.Header.SetBytesKV([]byte(fasthttp.HeaderXForwardedProto), tc.scheme)
|
||||||
|
@ -455,7 +428,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldNotHandleAuthRequestAllMethodsAllow()
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -475,17 +448,13 @@ func (s *ForwardAuthAuthzSuite) TestShouldNotHandleAuthRequestAllMethodsWithMeth
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestAuthRequest(mock.Ctx, method, targetURI, true, false)
|
setRequestAuthRequest(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -509,17 +478,13 @@ func (s *ForwardAuthAuthzSuite) TestShouldNotHandleExtAuthzAllMethodsAllow() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestExtAuthz(mock.Ctx, method, targetURI, true, false)
|
setRequestExtAuthz(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -545,17 +510,13 @@ func (s *ForwardAuthAuthzSuite) TestShouldNotHandleExtAuthzAllMethodsAllowXHR()
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestExtAuthz(mock.Ctx, method, targetURI, x, x)
|
setRequestExtAuthz(mock.Ctx, method, targetURI, x, x)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -577,17 +538,13 @@ func (s *ForwardAuthAuthzSuite) TestShouldNotHandleExtAuthzAllMethodsWithMethods
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
setRequestExtAuthz(mock.Ctx, method, targetURI, true, false)
|
setRequestExtAuthz(mock.Ctx, method, targetURI, true, false)
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ func handleAuthzUnauthorizedLegacy(ctx *middlewares.AutheliaCtx, authn *Authn, r
|
||||||
statusCode = fasthttp.StatusUnauthorized
|
statusCode = fasthttp.StatusUnauthorized
|
||||||
default:
|
default:
|
||||||
switch authn.Object.Method {
|
switch authn.Object.Method {
|
||||||
case fasthttp.MethodGet, fasthttp.MethodOptions, "":
|
case fasthttp.MethodGet, fasthttp.MethodOptions, fasthttp.MethodHead, "":
|
||||||
statusCode = fasthttp.StatusFound
|
statusCode = fasthttp.StatusFound
|
||||||
default:
|
default:
|
||||||
statusCode = fasthttp.StatusSeeOther
|
statusCode = fasthttp.StatusSeeOther
|
||||||
|
@ -55,8 +55,14 @@ func handleAuthzUnauthorizedLegacy(ctx *middlewares.AutheliaCtx, authn *Authn, r
|
||||||
}
|
}
|
||||||
|
|
||||||
if redirectionURL != nil {
|
if redirectionURL != nil {
|
||||||
ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d with location redirect to %s", authn.Object.URL.String(), authn.Method, authn.Username, statusCode, redirectionURL.String())
|
ctx.Logger.Infof(logFmtAuthzRedirect, authn.Object.URL.String(), authn.Method, authn.Username, statusCode, redirectionURL)
|
||||||
ctx.SpecialRedirect(redirectionURL.String(), statusCode)
|
|
||||||
|
switch authn.Object.Method {
|
||||||
|
case fasthttp.MethodHead:
|
||||||
|
ctx.SpecialRedirectNoBody(redirectionURL.String(), statusCode)
|
||||||
|
default:
|
||||||
|
ctx.SpecialRedirect(redirectionURL.String(), statusCode)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d", authn.Object.URL.String(), authn.Method, authn.Username, statusCode)
|
ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d", authn.Object.URL.String(), authn.Method, authn.Username, statusCode)
|
||||||
ctx.ReplyUnauthorized()
|
ctx.ReplyUnauthorized()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
|
@ -15,7 +16,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
"github.com/authelia/authelia/v4/internal/authorization"
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
"github.com/authelia/authelia/v4/internal/mocks"
|
"github.com/authelia/authelia/v4/internal/mocks"
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRunLegacyAuthzSuite(t *testing.T) {
|
func TestRunLegacyAuthzSuite(t *testing.T) {
|
||||||
|
@ -53,11 +54,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, pairURI.AutheliaURI.String())
|
mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, pairURI.AutheliaURI.String())
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
||||||
|
@ -69,7 +66,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsDeny() {
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
switch method {
|
switch method {
|
||||||
case fasthttp.MethodGet, fasthttp.MethodOptions:
|
case fasthttp.MethodGet, fasthttp.MethodOptions, fasthttp.MethodHead:
|
||||||
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
||||||
default:
|
default:
|
||||||
assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode())
|
||||||
|
@ -105,11 +102,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsOverrideAutheliaURLDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, pairURI.AutheliaURI.String())
|
mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, pairURI.AutheliaURI.String())
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
||||||
|
@ -121,7 +114,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsOverrideAutheliaURLDeny() {
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
switch method {
|
switch method {
|
||||||
case fasthttp.MethodGet, fasthttp.MethodOptions:
|
case fasthttp.MethodGet, fasthttp.MethodOptions, fasthttp.MethodHead:
|
||||||
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
||||||
default:
|
default:
|
||||||
assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode())
|
||||||
|
@ -227,7 +220,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsRDAutheliaURLOneFactorStatu
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
switch method {
|
switch method {
|
||||||
case fasthttp.MethodGet, fasthttp.MethodOptions:
|
case fasthttp.MethodGet, fasthttp.MethodOptions, fasthttp.MethodHead:
|
||||||
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
||||||
default:
|
default:
|
||||||
assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode())
|
||||||
|
@ -264,11 +257,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsXHRDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, pairURI.AutheliaURI.String())
|
mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, pairURI.AutheliaURI.String())
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
||||||
|
@ -317,11 +306,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleInvalidMethodCharsDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
||||||
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, targetURI.Scheme)
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, targetURI.Scheme)
|
||||||
|
@ -348,11 +333,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleMissingHostDeny() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
||||||
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https")
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https")
|
||||||
|
@ -384,11 +365,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsAllow() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
||||||
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, targetURI.Scheme)
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, targetURI.Scheme)
|
||||||
|
@ -406,6 +383,56 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsAllow() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() {
|
||||||
|
for _, method := range testRequestMethods {
|
||||||
|
s.T().Run(fmt.Sprintf("Method%s", method), func(t *testing.T) {
|
||||||
|
for _, methodACL := range testRequestMethods {
|
||||||
|
targetURI := s.RequireParseRequestURI(fmt.Sprintf("https://bypass-%s.example.com", strings.ToLower(methodACL)))
|
||||||
|
t.Run(targetURI.String(), func(t *testing.T) {
|
||||||
|
authz := s.Builder().Build()
|
||||||
|
|
||||||
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
|
|
||||||
|
defer mock.Close()
|
||||||
|
|
||||||
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
|
|
||||||
|
s.setRequest(mock.Ctx, method, targetURI, true, false)
|
||||||
|
mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, "https://auth.example.com")
|
||||||
|
|
||||||
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
|
if method == methodACL {
|
||||||
|
assert.Equal(t, fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))
|
||||||
|
} else {
|
||||||
|
expected := s.RequireParseRequestURI("https://auth.example.com/")
|
||||||
|
|
||||||
|
query := expected.Query()
|
||||||
|
query.Set(queryArgRD, targetURI.String())
|
||||||
|
query.Set(queryArgRM, method)
|
||||||
|
expected.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
switch method {
|
||||||
|
case fasthttp.MethodHead:
|
||||||
|
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Nil(t, mock.Ctx.Response.Body())
|
||||||
|
case fasthttp.MethodGet, fasthttp.MethodOptions:
|
||||||
|
assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, fmt.Sprintf(`<a href="%s">%d %s</a>`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusFound, fasthttp.StatusMessage(fasthttp.StatusFound)), string(mock.Ctx.Response.Body()))
|
||||||
|
default:
|
||||||
|
assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, fmt.Sprintf(`<a href="%s">%d %s</a>`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusSeeOther, fasthttp.StatusMessage(fasthttp.StatusSeeOther)), string(mock.Ctx.Response.Body()))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expected.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsAllowXHR() {
|
func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsAllowXHR() {
|
||||||
for _, method := range testRequestMethods {
|
for _, method := range testRequestMethods {
|
||||||
s.T().Run(fmt.Sprintf("Method%s", method), func(t *testing.T) {
|
s.T().Run(fmt.Sprintf("Method%s", method), func(t *testing.T) {
|
||||||
|
@ -422,11 +449,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsAllowXHR() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
||||||
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, targetURI.Scheme)
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, targetURI.Scheme)
|
||||||
|
@ -451,11 +474,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleLegacyBasicAuth() { // TestShouldVeri
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.QueryArgs().Add("auth", "basic")
|
mock.Ctx.QueryArgs().Add("auth", "basic")
|
||||||
mock.Ctx.Request.Header.Set("Authorization", "Basic am9objpwYXNzd29yZA==")
|
mock.Ctx.Request.Header.Set("Authorization", "Basic am9objpwYXNzd29yZA==")
|
||||||
|
@ -540,11 +559,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleLegacyBasicAuthFailures() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.QueryArgs().Add("auth", "basic")
|
mock.Ctx.QueryArgs().Add("auth", "basic")
|
||||||
mock.Ctx.Request.Header.Set("X-Original-URL", "https://one-factor.example.com")
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://one-factor.example.com")
|
||||||
|
@ -593,11 +608,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() {
|
||||||
mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass
|
mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass
|
||||||
mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&mock.Ctx.Configuration)
|
mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&mock.Ctx.Configuration)
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
mock.Ctx.Request.Header.Set("X-Forwarded-Method", method)
|
||||||
mock.Ctx.Request.Header.SetBytesKV([]byte(fasthttp.HeaderXForwardedProto), tc.scheme)
|
mock.Ctx.Request.Header.SetBytesKV([]byte(fasthttp.HeaderXForwardedProto), tc.scheme)
|
||||||
|
|
|
@ -50,9 +50,12 @@ func (s *AuthzSuite) RequireParseRequestURI(rawURL string) *url.URL {
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
type urlpair struct {
|
func (s *AuthzSuite) ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock *mocks.MockAutheliaCtx) {
|
||||||
TargetURI *url.URL
|
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
||||||
AutheliaURI *url.URL
|
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
||||||
|
}
|
||||||
|
|
||||||
|
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AuthzSuite) Builder() (builder *AuthzBuilder) {
|
func (s *AuthzSuite) Builder() (builder *AuthzBuilder) {
|
||||||
|
@ -87,11 +90,7 @@ func (s *AuthzSuite) TestShouldNotBeAbleToParseBasicAuth() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://test.example.com")
|
targetURI := s.RequireParseRequestURI("https://test.example.com")
|
||||||
|
|
||||||
|
@ -124,11 +123,7 @@ func (s *AuthzSuite) TestShouldApplyDefaultPolicy() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://test.example.com")
|
targetURI := s.RequireParseRequestURI("https://test.example.com")
|
||||||
|
|
||||||
|
@ -181,11 +176,7 @@ func (s *AuthzSuite) TestShouldDenyObject() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI(tc.value)
|
targetURI := s.RequireParseRequestURI(tc.value)
|
||||||
|
|
||||||
|
@ -193,7 +184,12 @@ func (s *AuthzSuite) TestShouldDenyObject() {
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
switch s.implementation {
|
||||||
|
case AuthzImplLegacy:
|
||||||
|
assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
||||||
|
default:
|
||||||
|
assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,11 +205,7 @@ func (s *AuthzSuite) TestShouldApplyPolicyOfBypassDomain() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
||||||
|
|
||||||
|
@ -250,11 +242,7 @@ func (s *AuthzSuite) TestShouldVerifyFailureToGetDetailsUsingBasicScheme() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
||||||
|
|
||||||
|
@ -299,11 +287,7 @@ func (s *AuthzSuite) TestShouldNotFailOnMissingEmail() {
|
||||||
|
|
||||||
mock.Clock.Set(time.Now())
|
mock.Clock.Set(time.Now())
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
||||||
|
|
||||||
|
@ -340,11 +324,7 @@ func (s *AuthzSuite) TestShouldApplyPolicyOfOneFactorDomain() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
||||||
|
|
||||||
|
@ -392,11 +372,7 @@ func (s *AuthzSuite) TestShouldHandleAnyCaseSchemeParameter() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
||||||
|
|
||||||
|
@ -435,11 +411,7 @@ func (s *AuthzSuite) TestShouldApplyPolicyOfTwoFactorDomain() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
||||||
|
|
||||||
|
@ -483,11 +455,7 @@ func (s *AuthzSuite) TestShouldApplyPolicyOfDenyDomain() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://deny.example.com")
|
targetURI := s.RequireParseRequestURI("https://deny.example.com")
|
||||||
|
|
||||||
|
@ -534,11 +502,7 @@ func (s *AuthzSuite) TestShouldApplyPolicyOfOneFactorDomainWithAuthorizationHead
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
||||||
|
|
||||||
|
@ -584,11 +548,7 @@ func (s *AuthzSuite) TestShouldHandleAuthzWithoutHeaderNoCookie() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
||||||
|
|
||||||
|
@ -621,11 +581,7 @@ func (s *AuthzSuite) TestShouldHandleAuthzWithEmptyAuthorizationHeader() {
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
||||||
|
|
||||||
|
@ -660,11 +616,7 @@ func (s *AuthzSuite) TestShouldHandleAuthzWithAuthorizationHeaderInvalidPassword
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
||||||
|
|
||||||
|
@ -700,11 +652,7 @@ func (s *AuthzSuite) TestShouldHandleAuthzWithIncorrectAuthHeader() { // TestSho
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
||||||
|
|
||||||
|
@ -744,11 +692,7 @@ func (s *AuthzSuite) TestShouldDestroySessionWhenInactiveForTooLong() {
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
||||||
|
|
||||||
|
@ -796,11 +740,7 @@ func (s *AuthzSuite) TestShouldNotDestroySessionWhenInactiveForTooLongRememberMe
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
||||||
|
|
||||||
|
@ -850,11 +790,7 @@ func (s *AuthzSuite) TestShouldNotDestroySessionWhenNotInactiveForTooLong() {
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
||||||
|
|
||||||
|
@ -905,11 +841,7 @@ func (s *AuthzSuite) TestShouldUpdateInactivityTimestampEvenWhenHittingForbidden
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://deny.example.com")
|
targetURI := s.RequireParseRequestURI("https://deny.example.com")
|
||||||
|
|
||||||
|
@ -971,11 +903,7 @@ func (s *AuthzSuite) TestShouldNotRefreshUserDetailsFromBackendWhenRefreshDisabl
|
||||||
mock.Ctx.Configuration.AuthenticationBackend.RefreshInterval = schema.ProfileRefreshDisabled
|
mock.Ctx.Configuration.AuthenticationBackend.RefreshInterval = schema.ProfileRefreshDisabled
|
||||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
||||||
|
|
||||||
|
@ -1057,11 +985,7 @@ func (s *AuthzSuite) TestShouldDestroySessionWhenUserDoesNotExist() {
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://two-factor.example.com")
|
||||||
|
|
||||||
|
@ -1149,11 +1073,7 @@ func (s *AuthzSuite) TestShouldUpdateRemovedUserGroupsFromBackendAndDeny() {
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://admin.example.com")
|
targetURI := s.RequireParseRequestURI("https://admin.example.com")
|
||||||
|
|
||||||
|
@ -1239,11 +1159,7 @@ func (s *AuthzSuite) TestShouldUpdateAddedUserGroupsFromBackendAndDeny() {
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://admin.example.com")
|
targetURI := s.RequireParseRequestURI("https://admin.example.com")
|
||||||
|
|
||||||
|
@ -1328,11 +1244,7 @@ func (s *AuthzSuite) TestShouldCheckValidSessionUsernameHeaderAndReturn200() {
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
||||||
|
|
||||||
|
@ -1385,11 +1297,7 @@ func (s *AuthzSuite) TestShouldCheckInvalidSessionUsernameHeaderAndReturn401AndD
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
targetURI := s.RequireParseRequestURI("https://one-factor.example.com")
|
||||||
|
|
||||||
|
@ -1462,11 +1370,7 @@ func (s *AuthzSuite) TestShouldNotRedirectRequestsForBypassACLWhenInactiveForToo
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
||||||
|
|
||||||
|
@ -1520,7 +1424,7 @@ func (s *AuthzSuite) TestShouldNotRedirectRequestsForBypassACLWhenInactiveForToo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AuthzSuite) TestShouldFailToParsePortalURL() {
|
func (s *AuthzSuite) TestShouldFailToParsePortalURL() {
|
||||||
if s.setRequest == nil || s.implementation == AuthzImplAuthRequest {
|
if s.setRequest == nil {
|
||||||
s.T().Skip()
|
s.T().Skip()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1538,20 +1442,20 @@ func (s *AuthzSuite) TestShouldFailToParsePortalURL() {
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
for i, cookie := range mock.Ctx.Configuration.Session.Cookies {
|
s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock)
|
||||||
mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
|
||||||
|
|
||||||
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
targetURI := s.RequireParseRequestURI("https://bypass.example.com")
|
||||||
|
|
||||||
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false)
|
||||||
|
|
||||||
|
expected := fasthttp.StatusBadRequest
|
||||||
|
|
||||||
switch s.implementation {
|
switch s.implementation {
|
||||||
case AuthzImplLegacy:
|
case AuthzImplLegacy:
|
||||||
|
expected = fasthttp.StatusUnauthorized
|
||||||
|
|
||||||
mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, "JKL$#N%KJ#@$N")
|
mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, "JKL$#N%KJ#@$N")
|
||||||
case AuthzImplForwardAuth:
|
case AuthzImplForwardAuth, AuthzImplAuthRequest:
|
||||||
mock.Ctx.RequestCtx.QueryArgs().Set("authelia_url", "JKL$#N%KJ#@$N")
|
mock.Ctx.RequestCtx.QueryArgs().Set("authelia_url", "JKL$#N%KJ#@$N")
|
||||||
case AuthzImplExtAuthz:
|
case AuthzImplExtAuthz:
|
||||||
mock.Ctx.Request.Header.Set("X-Authelia-URL", "JKL$#N%KJ#@$N")
|
mock.Ctx.Request.Header.Set("X-Authelia-URL", "JKL$#N%KJ#@$N")
|
||||||
|
@ -1559,7 +1463,10 @@ func (s *AuthzSuite) TestShouldFailToParsePortalURL() {
|
||||||
|
|
||||||
authz.Handler(mock.Ctx)
|
authz.Handler(mock.Ctx)
|
||||||
|
|
||||||
s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
|
s.Equal(expected, mock.Ctx.Response.StatusCode())
|
||||||
|
s.Equal(fmt.Sprintf("%d %s", expected, fasthttp.StatusMessage(expected)), string(mock.Ctx.Response.Body()))
|
||||||
|
s.Equal("", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)))
|
||||||
|
s.Equal("text/plain; charset=utf-8", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderContentType)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func setRequestXHRValues(ctx *middlewares.AutheliaCtx, accept, xhr bool) {
|
func setRequestXHRValues(ctx *middlewares.AutheliaCtx, accept, xhr bool) {
|
||||||
|
@ -1571,3 +1478,8 @@ func setRequestXHRValues(ctx *middlewares.AutheliaCtx, accept, xhr bool) {
|
||||||
ctx.Request.Header.Set(fasthttp.HeaderXRequestedWith, "XMLHttpRequest")
|
ctx.Request.Header.Set(fasthttp.HeaderXRequestedWith, "XMLHttpRequest")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type urlpair struct {
|
||||||
|
TargetURI *url.URL
|
||||||
|
AutheliaURI *url.URL
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Authz is a type which is a effectively is a middlewares.RequestHandler for authorization requests.
|
// Authz is a type which is a effectively is a middlewares.RequestHandler for authorization requests. This should NOT be
|
||||||
|
// manually used and developers should instead use NewAuthzBuilder.
|
||||||
type Authz struct {
|
type Authz struct {
|
||||||
config AuthzConfig
|
config AuthzConfig
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ type Authz struct {
|
||||||
handleAuthorized HandlerAuthzAuthorized
|
handleAuthorized HandlerAuthzAuthorized
|
||||||
handleUnauthorized HandlerAuthzUnauthorized
|
handleUnauthorized HandlerAuthzUnauthorized
|
||||||
|
|
||||||
legacy bool
|
implementation AuthzImplementation
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlerAuthzUnauthorized is a Authz handler func that handles unauthorized responses.
|
// HandlerAuthzUnauthorized is a Authz handler func that handles unauthorized responses.
|
||||||
|
@ -75,20 +76,17 @@ type Authn struct {
|
||||||
// AuthzConfig represents the configuration elements of the Authz type.
|
// AuthzConfig represents the configuration elements of the Authz type.
|
||||||
type AuthzConfig struct {
|
type AuthzConfig struct {
|
||||||
RefreshInterval time.Duration
|
RefreshInterval time.Duration
|
||||||
Domains []AuthzDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthzDomain represents a domain for the AuthzConfig.
|
// StatusCodeBadRequest is sent for configuration issues prior to performing authorization checks. It's set by the
|
||||||
type AuthzDomain struct {
|
// builder.
|
||||||
Name string
|
StatusCodeBadRequest int
|
||||||
PortalURL *url.URL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthzBuilder is a builder pattern for the Authz type.
|
// AuthzBuilder is a builder pattern for the Authz type.
|
||||||
type AuthzBuilder struct {
|
type AuthzBuilder struct {
|
||||||
config AuthzConfig
|
config AuthzConfig
|
||||||
impl AuthzImplementation
|
implementation AuthzImplementation
|
||||||
strategies []AuthnStrategy
|
strategies []AuthnStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthnStrategy is a strategy used for Authz authentication.
|
// AuthnStrategy is a strategy used for Authz authentication.
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authentication"
|
"github.com/authelia/authelia/v4/internal/authentication"
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
"github.com/authelia/authelia/v4/internal/authorization"
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
|
@ -23,7 +20,7 @@ func friendlyMethod(m string) (fm string) {
|
||||||
func friendlyUsername(username string) (fusername string) {
|
func friendlyUsername(username string) (fusername string) {
|
||||||
switch username {
|
switch username {
|
||||||
case "":
|
case "":
|
||||||
return "<anonymous>"
|
return anonymous
|
||||||
default:
|
default:
|
||||||
return username
|
return username
|
||||||
}
|
}
|
||||||
|
@ -54,39 +51,55 @@ func generateVerifySessionHasUpToDateProfileTraceLogs(ctx *middlewares.AutheliaC
|
||||||
emailsAdded, emailsRemoved := utils.StringSlicesDelta(userSession.Emails, details.Emails)
|
emailsAdded, emailsRemoved := utils.StringSlicesDelta(userSession.Emails, details.Emails)
|
||||||
nameDelta := userSession.DisplayName != details.DisplayName
|
nameDelta := userSession.DisplayName != details.DisplayName
|
||||||
|
|
||||||
var groupsDelta []string
|
fields := map[string]any{"username": userSession.Username}
|
||||||
if len(groupsAdded) != 0 {
|
msg := "User session groups are current"
|
||||||
groupsDelta = append(groupsDelta, fmt.Sprintf("added: %s.", strings.Join(groupsAdded, ", ")))
|
|
||||||
|
if len(groupsAdded) != 0 || len(groupsRemoved) != 0 {
|
||||||
|
if len(groupsAdded) != 0 {
|
||||||
|
fields["added"] = groupsAdded
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(groupsRemoved) != 0 {
|
||||||
|
fields["removed"] = groupsRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = "User session groups were updated"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(groupsRemoved) != 0 {
|
ctx.Logger.WithFields(fields).Trace(msg)
|
||||||
groupsDelta = append(groupsDelta, fmt.Sprintf("removed: %s.", strings.Join(groupsRemoved, ", ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(groupsDelta) != 0 {
|
if len(emailsAdded) != 0 || len(emailsRemoved) != 0 {
|
||||||
ctx.Logger.Tracef("Updated groups detected for %s. %s", userSession.Username, strings.Join(groupsDelta, " "))
|
if len(emailsAdded) != 0 {
|
||||||
|
fields["added"] = emailsAdded
|
||||||
|
} else {
|
||||||
|
delete(fields, "added")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(emailsRemoved) != 0 {
|
||||||
|
fields["removed"] = emailsRemoved
|
||||||
|
} else {
|
||||||
|
delete(fields, "removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = "User session emails were updated"
|
||||||
} else {
|
} else {
|
||||||
ctx.Logger.Tracef("No updated groups detected for %s", userSession.Username)
|
msg = "User session emails are current"
|
||||||
|
|
||||||
|
delete(fields, "added")
|
||||||
|
delete(fields, "removed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var emailsDelta []string
|
ctx.Logger.WithFields(fields).Trace(msg)
|
||||||
if len(emailsAdded) != 0 {
|
|
||||||
emailsDelta = append(emailsDelta, fmt.Sprintf("added: %s.", strings.Join(emailsAdded, ", ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(emailsRemoved) != 0 {
|
|
||||||
emailsDelta = append(emailsDelta, fmt.Sprintf("removed: %s.", strings.Join(emailsRemoved, ", ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(emailsDelta) != 0 {
|
|
||||||
ctx.Logger.Tracef("Updated emails detected for %s. %s", userSession.Username, strings.Join(emailsDelta, " "))
|
|
||||||
} else {
|
|
||||||
ctx.Logger.Tracef("No updated emails detected for %s", userSession.Username)
|
|
||||||
}
|
|
||||||
|
|
||||||
if nameDelta {
|
if nameDelta {
|
||||||
ctx.Logger.Tracef("Updated display name detected for %s. Added: %s. Removed: %s.", userSession.Username, details.DisplayName, userSession.DisplayName)
|
ctx.Logger.
|
||||||
|
WithFields(map[string]any{
|
||||||
|
"username": userSession.Username,
|
||||||
|
"before": userSession.DisplayName,
|
||||||
|
"after": details.DisplayName,
|
||||||
|
}).
|
||||||
|
Trace("User session display name updated")
|
||||||
} else {
|
} else {
|
||||||
ctx.Logger.Tracef("No updated display name detected for %s", userSession.Username)
|
ctx.Logger.Trace("User session display name is current")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
||||||
req.Context(), fosite.AccessTokenFromRequest(req), fosite.AccessToken, oidcSession); err != nil {
|
req.Context(), fosite.AccessTokenFromRequest(req), fosite.AccessToken, oidcSession); err != nil {
|
||||||
rfc := fosite.ErrorToRFC6749Error(err)
|
rfc := fosite.ErrorToRFC6749Error(err)
|
||||||
|
|
||||||
ctx.Logger.Errorf("UserInfo Request failed with error: %+v", rfc)
|
ctx.Logger.Errorf("UserInfo Request failed with error: %s", rfc.WithExposeDebug(true).GetDescription())
|
||||||
|
|
||||||
if rfc.StatusCode() == http.StatusUnauthorized {
|
if rfc.StatusCode() == http.StatusUnauthorized {
|
||||||
rw.Header().Set(fasthttp.HeaderWWWAuthenticate, fmt.Sprintf(`Bearer error="%s",error_description="%s"`, rfc.ErrorField, rfc.GetDescription()))
|
rw.Header().Set(fasthttp.HeaderWWWAuthenticate, fmt.Sprintf(`Bearer error="%s",error_description="%s"`, rfc.ErrorField, rfc.GetDescription()))
|
||||||
|
@ -47,7 +47,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
||||||
clientID := requester.GetClient().GetID()
|
clientID := requester.GetClient().GetID()
|
||||||
|
|
||||||
if tokenType != fosite.AccessToken {
|
if tokenType != fosite.AccessToken {
|
||||||
ctx.Logger.Errorf("UserInfo Request with id '%s' on client with id '%s' failed with error: bearer authorization failed as the token is not an access_token", requester.GetID(), client.GetID())
|
ctx.Logger.Errorf("UserInfo Request with id '%s' on client with id '%s' failed with error: bearer authorization failed as the token is not an access token", requester.GetID(), client.GetID())
|
||||||
|
|
||||||
errStr := "Only access tokens are allowed in the authorization header."
|
errStr := "Only access tokens are allowed in the authorization header."
|
||||||
rw.Header().Set(fasthttp.HeaderWWWAuthenticate, fmt.Sprintf(`Bearer error="invalid_token",error_description="%s"`, errStr))
|
rw.Header().Set(fasthttp.HeaderWWWAuthenticate, fmt.Sprintf(`Bearer error="invalid_token",error_description="%s"`, errStr))
|
||||||
|
@ -57,7 +57,11 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
||||||
}
|
}
|
||||||
|
|
||||||
if client, err = ctx.Providers.OpenIDConnect.GetFullClient(clientID); err != nil {
|
if client, err = ctx.Providers.OpenIDConnect.GetFullClient(clientID); err != nil {
|
||||||
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHint("Unable to assert type of client")))
|
rfc := fosite.ErrorToRFC6749Error(err)
|
||||||
|
|
||||||
|
ctx.Logger.Errorf("UserInfo Request with id '%s' on client with id '%s' failed to retrieve client configuration with error: %s", requester.GetID(), client.GetID(), rfc.WithExposeDebug(true).GetDescription())
|
||||||
|
|
||||||
|
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(rfc))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -100,7 +104,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
||||||
var jti uuid.UUID
|
var jti uuid.UUID
|
||||||
|
|
||||||
if jti, err = uuid.NewRandom(); err != nil {
|
if jti, err = uuid.NewRandom(); err != nil {
|
||||||
ctx.Providers.OpenIDConnect.WriteError(rw, req, fosite.ErrServerError.WithHintf("Could not generate JTI."))
|
ctx.Providers.OpenIDConnect.WriteError(rw, req, fosite.ErrServerError.WithHint("Could not generate JTI."))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -120,9 +124,9 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/jwt")
|
rw.Header().Set(fasthttp.HeaderContentType, "application/jwt")
|
||||||
_, _ = rw.Write([]byte(token))
|
_, _ = rw.Write([]byte(token))
|
||||||
case "none", "":
|
case oidc.SigningAlgorithmNone, "":
|
||||||
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
|
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
|
||||||
default:
|
default:
|
||||||
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.UserinfoSigningAlgorithm)))
|
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.UserinfoSigningAlgorithm)))
|
||||||
|
|
|
@ -563,13 +563,27 @@ func (ctx *AutheliaCtx) AcceptsMIME(mime string) (acceptsMime bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpecialRedirect performs a redirect similar to fasthttp.RequestCtx except it allows statusCode 401 and includes body
|
// SpecialRedirect performs a redirect similar to fasthttp.RequestCtx except it allows statusCode 401 and includes body
|
||||||
// content in the form of a link to the location.
|
// content in the form of a link to the location if the request method was not head.
|
||||||
func (ctx *AutheliaCtx) SpecialRedirect(uri string, statusCode int) {
|
func (ctx *AutheliaCtx) SpecialRedirect(uri string, statusCode int) {
|
||||||
|
var u []byte
|
||||||
|
|
||||||
|
u, statusCode = ctx.setSpecialRedirect(uri, statusCode)
|
||||||
|
|
||||||
|
ctx.SetContentTypeTextHTML()
|
||||||
|
ctx.SetBodyString(fmt.Sprintf("<a href=\"%s\">%d %s</a>", utils.StringHTMLEscape(string(u)), statusCode, fasthttp.StatusMessage(statusCode)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpecialRedirectNoBody performs a redirect similar to fasthttp.RequestCtx except it allows statusCode 401 and includes
|
||||||
|
// no body.
|
||||||
|
func (ctx *AutheliaCtx) SpecialRedirectNoBody(uri string, statusCode int) {
|
||||||
|
_, _ = ctx.setSpecialRedirect(uri, statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *AutheliaCtx) setSpecialRedirect(uri string, statusCode int) ([]byte, int) {
|
||||||
if statusCode < fasthttp.StatusMovedPermanently || (statusCode > fasthttp.StatusSeeOther && statusCode != fasthttp.StatusTemporaryRedirect && statusCode != fasthttp.StatusPermanentRedirect && statusCode != fasthttp.StatusUnauthorized) {
|
if statusCode < fasthttp.StatusMovedPermanently || (statusCode > fasthttp.StatusSeeOther && statusCode != fasthttp.StatusTemporaryRedirect && statusCode != fasthttp.StatusPermanentRedirect && statusCode != fasthttp.StatusUnauthorized) {
|
||||||
statusCode = fasthttp.StatusFound
|
statusCode = fasthttp.StatusFound
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.SetContentTypeTextHTML()
|
|
||||||
ctx.SetStatusCode(statusCode)
|
ctx.SetStatusCode(statusCode)
|
||||||
|
|
||||||
u := fasthttp.AcquireURI()
|
u := fasthttp.AcquireURI()
|
||||||
|
@ -577,11 +591,13 @@ func (ctx *AutheliaCtx) SpecialRedirect(uri string, statusCode int) {
|
||||||
ctx.URI().CopyTo(u)
|
ctx.URI().CopyTo(u)
|
||||||
u.Update(uri)
|
u.Update(uri)
|
||||||
|
|
||||||
ctx.Response.Header.SetBytesKV(headerLocation, u.FullURI())
|
raw := u.FullURI()
|
||||||
|
|
||||||
ctx.SetBodyString(fmt.Sprintf("<a href=\"%s\">%d %s</a>", utils.StringHTMLEscape(string(u.FullURI())), statusCode, fasthttp.StatusMessage(statusCode)))
|
ctx.Response.Header.SetBytesKV(headerLocation, raw)
|
||||||
|
|
||||||
fasthttp.ReleaseURI(u)
|
fasthttp.ReleaseURI(u)
|
||||||
|
|
||||||
|
return raw, statusCode
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordAuthn records authentication metrics.
|
// RecordAuthn records authentication metrics.
|
||||||
|
|
|
@ -79,7 +79,7 @@ func (s *Store) GetClientPolicy(id string) (level authorization.Level) {
|
||||||
func (s *Store) GetFullClient(id string) (client *Client, err error) {
|
func (s *Store) GetFullClient(id string) (client *Client, err error) {
|
||||||
client, ok := s.clients[id]
|
client, ok := s.clients[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fosite.ErrNotFound
|
return nil, fosite.ErrInvalidClient
|
||||||
}
|
}
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
|
|
|
@ -59,7 +59,7 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
client, err := s.GetClient(context.Background(), "myinvalidclient")
|
client, err := s.GetClient(context.Background(), "myinvalidclient")
|
||||||
assert.EqualError(t, err, "not_found")
|
assert.EqualError(t, err, "invalid_client")
|
||||||
assert.Nil(t, client)
|
assert.Nil(t, client)
|
||||||
|
|
||||||
client, err = s.GetClient(context.Background(), "myclient")
|
client, err = s.GetClient(context.Background(), "myclient")
|
||||||
|
@ -113,7 +113,7 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
||||||
|
|
||||||
client, err := s.GetFullClient("another-client")
|
client, err := s.GetFullClient("another-client")
|
||||||
assert.Nil(t, client)
|
assert.Nil(t, client)
|
||||||
assert.EqualError(t, err, "not_found")
|
assert.EqualError(t, err, "invalid_client")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
|
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
|
||||||
|
|
|
@ -196,8 +196,8 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers)
|
||||||
case "legacy":
|
case "legacy":
|
||||||
log.
|
log.
|
||||||
WithField("path_prefix", pathAuthzLegacy).
|
WithField("path_prefix", pathAuthzLegacy).
|
||||||
WithField("impl", endpoint.Implementation).
|
WithField("implementation", endpoint.Implementation).
|
||||||
WithField("methods", []string{"*"}).
|
WithField("methods", "*").
|
||||||
Trace("Registering Authz Endpoint")
|
Trace("Registering Authz Endpoint")
|
||||||
|
|
||||||
r.ANY(pathAuthzLegacy, handler)
|
r.ANY(pathAuthzLegacy, handler)
|
||||||
|
@ -207,8 +207,8 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers)
|
||||||
case handlers.AuthzImplLegacy.String(), handlers.AuthzImplExtAuthz.String():
|
case handlers.AuthzImplLegacy.String(), handlers.AuthzImplExtAuthz.String():
|
||||||
log.
|
log.
|
||||||
WithField("path_prefix", uri).
|
WithField("path_prefix", uri).
|
||||||
WithField("impl", endpoint.Implementation).
|
WithField("implementation", endpoint.Implementation).
|
||||||
WithField("methods", []string{"*"}).
|
WithField("methods", "*").
|
||||||
Trace("Registering Authz Endpoint")
|
Trace("Registering Authz Endpoint")
|
||||||
|
|
||||||
r.ANY(uri, handler)
|
r.ANY(uri, handler)
|
||||||
|
@ -216,7 +216,7 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers)
|
||||||
default:
|
default:
|
||||||
log.
|
log.
|
||||||
WithField("path", uri).
|
WithField("path", uri).
|
||||||
WithField("impl", endpoint.Implementation).
|
WithField("implementation", endpoint.Implementation).
|
||||||
WithField("methods", []string{fasthttp.MethodGet, fasthttp.MethodHead}).
|
WithField("methods", []string{fasthttp.MethodGet, fasthttp.MethodHead}).
|
||||||
Trace("Registering Authz Endpoint")
|
Trace("Registering Authz Endpoint")
|
||||||
|
|
||||||
|
|
|
@ -61,16 +61,27 @@ func ServeTemplatedFile(t templates.Template, opts *TemplatedFileOptions) middle
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rememberMe string
|
rememberMe string
|
||||||
|
baseURL string
|
||||||
|
domain string
|
||||||
provider *session.Session
|
provider *session.Session
|
||||||
)
|
)
|
||||||
|
|
||||||
if provider, err = ctx.GetSessionProvider(); err == nil {
|
if provider, err = ctx.GetSessionProvider(); err == nil {
|
||||||
|
if provider.Config.AutheliaURL != nil {
|
||||||
|
baseURL = provider.Config.AutheliaURL.String()
|
||||||
|
} else {
|
||||||
|
baseURL = ctx.RootURLSlash().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
domain = provider.Config.Domain
|
||||||
rememberMe = strconv.FormatBool(!provider.Config.DisableRememberMe)
|
rememberMe = strconv.FormatBool(!provider.Config.DisableRememberMe)
|
||||||
|
} else {
|
||||||
|
baseURL = ctx.RootURLSlash().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &bytes.Buffer{}
|
data := &bytes.Buffer{}
|
||||||
|
|
||||||
if err = t.Execute(data, opts.CommonData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce, logoOverride, rememberMe)); err != nil {
|
if err = t.Execute(data, opts.CommonData(ctx.BasePath(), baseURL, domain, nonce, logoOverride, rememberMe)); err != nil {
|
||||||
ctx.RequestCtx.Error("an error occurred", fasthttp.StatusServiceUnavailable)
|
ctx.RequestCtx.Error("an error occurred", fasthttp.StatusServiceUnavailable)
|
||||||
ctx.Logger.WithError(err).Errorf("Error occcurred rendering template")
|
ctx.Logger.WithError(err).Errorf("Error occcurred rendering template")
|
||||||
|
|
||||||
|
@ -118,11 +129,28 @@ func ServeTemplatedOpenAPI(t templates.Template, opts *TemplatedFileOptions) mid
|
||||||
ctx.SetContentTypeTextPlain()
|
ctx.SetContentTypeTextPlain()
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var (
|
||||||
|
baseURL string
|
||||||
|
domain string
|
||||||
|
provider *session.Session
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if provider, err = ctx.GetSessionProvider(); err == nil {
|
||||||
|
if provider.Config.AutheliaURL != nil {
|
||||||
|
baseURL = provider.Config.AutheliaURL.String()
|
||||||
|
} else {
|
||||||
|
baseURL = ctx.RootURLSlash().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
domain = provider.Config.Domain
|
||||||
|
} else {
|
||||||
|
baseURL = ctx.RootURLSlash().String()
|
||||||
|
}
|
||||||
|
|
||||||
data := &bytes.Buffer{}
|
data := &bytes.Buffer{}
|
||||||
|
|
||||||
if err = t.Execute(data, opts.OpenAPIData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce)); err != nil {
|
if err = t.Execute(data, opts.OpenAPIData(ctx.BasePath(), baseURL, domain, nonce)); err != nil {
|
||||||
ctx.RequestCtx.Error("an error occurred", fasthttp.StatusServiceUnavailable)
|
ctx.RequestCtx.Error("an error occurred", fasthttp.StatusServiceUnavailable)
|
||||||
ctx.Logger.WithError(err).Errorf("Error occcurred rendering template")
|
ctx.Logger.WithError(err).Errorf("Error occcurred rendering template")
|
||||||
|
|
||||||
|
@ -285,14 +313,15 @@ type TemplatedFileOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommonData returns a TemplatedFileCommonData with the dynamic options.
|
// CommonData returns a TemplatedFileCommonData with the dynamic options.
|
||||||
func (options *TemplatedFileOptions) CommonData(base, baseURL, nonce, logoOverride, rememberMe string) TemplatedFileCommonData {
|
func (options *TemplatedFileOptions) CommonData(base, baseURL, domain, nonce, logoOverride, rememberMe string) TemplatedFileCommonData {
|
||||||
if rememberMe != "" {
|
if rememberMe != "" {
|
||||||
return options.commonDataWithRememberMe(base, baseURL, nonce, logoOverride, rememberMe)
|
return options.commonDataWithRememberMe(base, baseURL, domain, nonce, logoOverride, rememberMe)
|
||||||
}
|
}
|
||||||
|
|
||||||
return TemplatedFileCommonData{
|
return TemplatedFileCommonData{
|
||||||
Base: base,
|
Base: base,
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
|
Domain: domain,
|
||||||
CSPNonce: nonce,
|
CSPNonce: nonce,
|
||||||
LogoOverride: logoOverride,
|
LogoOverride: logoOverride,
|
||||||
DuoSelfEnrollment: options.DuoSelfEnrollment,
|
DuoSelfEnrollment: options.DuoSelfEnrollment,
|
||||||
|
@ -307,10 +336,11 @@ func (options *TemplatedFileOptions) CommonData(base, baseURL, nonce, logoOverri
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommonDataWithRememberMe returns a TemplatedFileCommonData with the dynamic options.
|
// CommonDataWithRememberMe returns a TemplatedFileCommonData with the dynamic options.
|
||||||
func (options *TemplatedFileOptions) commonDataWithRememberMe(base, baseURL, nonce, logoOverride, rememberMe string) TemplatedFileCommonData {
|
func (options *TemplatedFileOptions) commonDataWithRememberMe(base, baseURL, domain, nonce, logoOverride, rememberMe string) TemplatedFileCommonData {
|
||||||
return TemplatedFileCommonData{
|
return TemplatedFileCommonData{
|
||||||
Base: base,
|
Base: base,
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
|
Domain: domain,
|
||||||
CSPNonce: nonce,
|
CSPNonce: nonce,
|
||||||
LogoOverride: logoOverride,
|
LogoOverride: logoOverride,
|
||||||
DuoSelfEnrollment: options.DuoSelfEnrollment,
|
DuoSelfEnrollment: options.DuoSelfEnrollment,
|
||||||
|
@ -323,10 +353,11 @@ func (options *TemplatedFileOptions) commonDataWithRememberMe(base, baseURL, non
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenAPIData returns a TemplatedFileOpenAPIData with the dynamic options.
|
// OpenAPIData returns a TemplatedFileOpenAPIData with the dynamic options.
|
||||||
func (options *TemplatedFileOptions) OpenAPIData(base, baseURL, nonce string) TemplatedFileOpenAPIData {
|
func (options *TemplatedFileOptions) OpenAPIData(base, baseURL, domain, nonce string) TemplatedFileOpenAPIData {
|
||||||
return TemplatedFileOpenAPIData{
|
return TemplatedFileOpenAPIData{
|
||||||
Base: base,
|
Base: base,
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
|
Domain: domain,
|
||||||
CSPNonce: nonce,
|
CSPNonce: nonce,
|
||||||
|
|
||||||
Session: options.Session,
|
Session: options.Session,
|
||||||
|
@ -343,6 +374,7 @@ func (options *TemplatedFileOptions) OpenAPIData(base, baseURL, nonce string) Te
|
||||||
type TemplatedFileCommonData struct {
|
type TemplatedFileCommonData struct {
|
||||||
Base string
|
Base string
|
||||||
BaseURL string
|
BaseURL string
|
||||||
|
Domain string
|
||||||
CSPNonce string
|
CSPNonce string
|
||||||
LogoOverride string
|
LogoOverride string
|
||||||
DuoSelfEnrollment string
|
DuoSelfEnrollment string
|
||||||
|
@ -359,6 +391,7 @@ type TemplatedFileCommonData struct {
|
||||||
type TemplatedFileOpenAPIData struct {
|
type TemplatedFileOpenAPIData struct {
|
||||||
Base string
|
Base string
|
||||||
BaseURL string
|
BaseURL string
|
||||||
|
Domain string
|
||||||
CSPNonce string
|
CSPNonce string
|
||||||
Session string
|
Session string
|
||||||
PasswordReset bool
|
PasswordReset bool
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/v4/internal/mocks"
|
||||||
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
|
"github.com/authelia/authelia/v4/internal/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
assetsOpenAPIPath = "public_html/api/openapi.yml"
|
||||||
|
localOpenAPIPath = "../../api/openapi.yml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReadFileOpenAPI struct{}
|
||||||
|
|
||||||
|
func (lfs *ReadFileOpenAPI) Open(name string) (fs.File, error) {
|
||||||
|
switch name {
|
||||||
|
case assetsOpenAPIPath:
|
||||||
|
return os.Open(localOpenAPIPath)
|
||||||
|
default:
|
||||||
|
return assets.Open(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *ReadFileOpenAPI) ReadFile(name string) ([]byte, error) {
|
||||||
|
switch name {
|
||||||
|
case assetsOpenAPIPath:
|
||||||
|
return os.ReadFile(localOpenAPIPath)
|
||||||
|
default:
|
||||||
|
return assets.ReadFile(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldTemplateOpenAPI(t *testing.T) {
|
||||||
|
provider, err := templates.New(templates.Config{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fs := &ReadFileOpenAPI{}
|
||||||
|
|
||||||
|
require.NoError(t, provider.LoadTemplatedAssets(fs))
|
||||||
|
|
||||||
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
|
|
||||||
|
mock.Ctx.Configuration.Server = schema.DefaultServerConfiguration
|
||||||
|
mock.Ctx.Configuration.Session = schema.SessionConfiguration{
|
||||||
|
Cookies: []schema.SessionCookieConfiguration{
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: "example.com",
|
||||||
|
},
|
||||||
|
AutheliaURL: &url.URL{Scheme: "https", Host: "auth.example.com", Path: "/"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
||||||
|
|
||||||
|
opts := NewTemplatedFileOptions(&mock.Ctx.Configuration)
|
||||||
|
|
||||||
|
handler := ServeTemplatedOpenAPI(provider.GetAssetOpenAPISpecTemplate(), opts)
|
||||||
|
|
||||||
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https")
|
||||||
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "example.com")
|
||||||
|
mock.Ctx.Request.Header.Set("X-Forwarded-Uri", "/api/openapi.yml")
|
||||||
|
|
||||||
|
handler(mock.Ctx)
|
||||||
|
|
||||||
|
assert.Equal(t, fasthttp.StatusOK, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.NotEqual(t, "", string(mock.Ctx.Response.Body()))
|
||||||
|
|
||||||
|
assert.Contains(t, string(mock.Ctx.Response.Body()), "example: https://auth.example.com/?rd=https%3A%2F%2Fexample.com&rm=GET")
|
||||||
|
}
|
|
@ -31,10 +31,12 @@ authentication_backend:
|
||||||
|
|
||||||
session:
|
session:
|
||||||
secret: unsecure_session_secret
|
secret: unsecure_session_secret
|
||||||
domain: example.com
|
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me: 1y
|
remember_me: 1y
|
||||||
|
cookies:
|
||||||
|
- domain: 'example.com'
|
||||||
|
authelia_url: 'https://login.example.com:8080'
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
encryption_key: a_not_so_secure_encryption_key
|
encryption_key: a_not_so_secure_encryption_key
|
||||||
|
|
|
@ -87,7 +87,9 @@ session:
|
||||||
secret: unsecure_session_secret
|
secret: unsecure_session_secret
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
domain: example.com
|
cookies:
|
||||||
|
- domain: 'example.com'
|
||||||
|
authelia_url: 'https://login.example.com:8080'
|
||||||
redis:
|
redis:
|
||||||
username: authelia
|
username: authelia
|
||||||
password: redis-user-password
|
password: redis-user-password
|
||||||
|
|
|
@ -32,13 +32,14 @@ session:
|
||||||
cookies:
|
cookies:
|
||||||
- name: 'authelia_session'
|
- name: 'authelia_session'
|
||||||
domain: 'example.com'
|
domain: 'example.com'
|
||||||
|
authelia_url: 'https://login.example.com:8080'
|
||||||
- name: 'example2_session'
|
- name: 'example2_session'
|
||||||
domain: 'example2.com'
|
domain: 'example2.com'
|
||||||
authelia_url: 'https://login.example2.com'
|
authelia_url: 'https://login.example2.com:8080'
|
||||||
remember_me: -1
|
remember_me: -1
|
||||||
- name: 'authelia_session'
|
- name: 'authelia_session'
|
||||||
domain: 'example3.com'
|
domain: 'example3.com'
|
||||||
authelia_url: 'https://login.example3.com'
|
authelia_url: 'https://login.example3.com:8080'
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
encryption_key: a_not_so_secure_encryption_key
|
encryption_key: a_not_so_secure_encryption_key
|
||||||
|
|
|
@ -19,9 +19,42 @@
|
||||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
-- SOFTWARE.
|
-- SOFTWARE.
|
||||||
|
--
|
||||||
|
-- SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
local http = require("haproxy-lua-http")
|
local http = require("haproxy-lua-http")
|
||||||
|
|
||||||
|
core.register_action("auth-request", { "http-req" }, function(txn, be, path)
|
||||||
|
auth_request(txn, be, path, "HEAD", ".*", "-", "-")
|
||||||
|
end, 2)
|
||||||
|
|
||||||
|
core.register_action("auth-intercept", { "http-req" }, function(txn, be, path, method, hdr_req, hdr_succeed, hdr_fail)
|
||||||
|
hdr_req = globToLuaPattern(hdr_req)
|
||||||
|
hdr_succeed = globToLuaPattern(hdr_succeed)
|
||||||
|
hdr_fail = globToLuaPattern(hdr_fail)
|
||||||
|
auth_request(txn, be, path, method, hdr_req, hdr_succeed, hdr_fail)
|
||||||
|
end, 6)
|
||||||
|
|
||||||
|
function globToLuaPattern(glob)
|
||||||
|
if glob == "-" then
|
||||||
|
return "-"
|
||||||
|
end
|
||||||
|
-- magic chars: '^', '$', '(', ')', '%', '.', '[', ']', '*', '+', '-', '?'
|
||||||
|
-- https://www.lua.org/manual/5.4/manual.html#6.4.1
|
||||||
|
--
|
||||||
|
-- this chain is:
|
||||||
|
-- 1. escaping all the magic chars, adding a `%` in front of all of them,
|
||||||
|
-- except the chars being processed later in the chain;
|
||||||
|
-- 1.1. all the chars inside the [set] are magic chars and have special
|
||||||
|
-- meaning inside a set, so we're also escaping all of them to avoid
|
||||||
|
-- misbehavior;
|
||||||
|
-- 2. converting "match all" `*` and "match one" `?` to their Lua pattern
|
||||||
|
-- counterparts;
|
||||||
|
-- 3. adding start and finish boundaries outside the whole string and,
|
||||||
|
-- being a comma-separated list, between every single item as well.
|
||||||
|
return "^" .. glob:gsub("[%^%$%(%)%%%.%[%]%+%-]", "%%%1"):gsub("*", ".*"):gsub("?", "."):gsub(",", "$,^") .. "$"
|
||||||
|
end
|
||||||
|
|
||||||
function set_var_pre_2_2(txn, var, value)
|
function set_var_pre_2_2(txn, var, value)
|
||||||
return txn:set_var(var, value)
|
return txn:set_var(var, value)
|
||||||
end
|
end
|
||||||
|
@ -44,8 +77,49 @@ function sanitize_header_for_variable(header)
|
||||||
return header:gsub("[^a-zA-Z0-9]", "_")
|
return header:gsub("[^a-zA-Z0-9]", "_")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- header_match checks whether the provided header matches the pattern.
|
||||||
|
-- pattern is a comma-separated list of Lua Patterns.
|
||||||
|
function header_match(header, pattern)
|
||||||
|
if header == "content-length" or header == "host" or pattern == "-" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
for p in pattern:gmatch("[^,]*") do
|
||||||
|
if header:match(p) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
core.register_action("auth-request", { "http-req" }, function(txn, be, path)
|
-- Terminates the transaction and sends the provided response to the client.
|
||||||
|
-- hdr_fail filters header names that should be provided using Lua Patterns.
|
||||||
|
function send_response(txn, response, hdr_fail)
|
||||||
|
local reply = txn:reply()
|
||||||
|
if response then
|
||||||
|
reply:set_status(response.status_code)
|
||||||
|
for header, value in response:get_headers(false) do
|
||||||
|
if header_match(header, hdr_fail) then
|
||||||
|
reply:add_header(header, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if response.content then
|
||||||
|
reply:set_body(response.content)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
reply:set_status(500)
|
||||||
|
end
|
||||||
|
txn:done(reply)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- auth_request makes the request to the external authentication service
|
||||||
|
-- and waits for the response. hdr_* params receive a comma-separated
|
||||||
|
-- list of Lua Patterns used to identify the headers that should be
|
||||||
|
-- copied between the requests and responses. A dash `-` in these params
|
||||||
|
-- mean that the headers shouldn't be copied at all.
|
||||||
|
-- Special values and behavior:
|
||||||
|
-- * method == "*": call the auth service using the same method used by the client.
|
||||||
|
-- * hdr_fail == "-": make the Lua script to not terminate the request.
|
||||||
|
function auth_request(txn, be, path, method, hdr_req, hdr_succeed, hdr_fail)
|
||||||
set_var(txn, "txn.auth_response_successful", false)
|
set_var(txn, "txn.auth_response_successful", false)
|
||||||
|
|
||||||
-- Check whether the given backend exists.
|
-- Check whether the given backend exists.
|
||||||
|
@ -75,7 +149,7 @@ core.register_action("auth-request", { "http-req" }, function(txn, be, path)
|
||||||
-- socket.http's format.
|
-- socket.http's format.
|
||||||
local headers = {}
|
local headers = {}
|
||||||
for header, values in pairs(txn.http:req_get_headers()) do
|
for header, values in pairs(txn.http:req_get_headers()) do
|
||||||
if header ~= 'content-length' then
|
if header_match(header, hdr_req) then
|
||||||
for i, v in pairs(values) do
|
for i, v in pairs(values) do
|
||||||
if headers[header] == nil then
|
if headers[header] == nil then
|
||||||
headers[header] = v
|
headers[header] = v
|
||||||
|
@ -87,28 +161,46 @@ core.register_action("auth-request", { "http-req" }, function(txn, be, path)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Make request to backend.
|
-- Make request to backend.
|
||||||
local response, err = http.head {
|
if method == "*" then
|
||||||
|
method = txn.sf:method()
|
||||||
|
end
|
||||||
|
local response, err = http.send(method:upper(), {
|
||||||
url = "http://" .. addr .. path,
|
url = "http://" .. addr .. path,
|
||||||
headers = headers,
|
headers = headers,
|
||||||
}
|
})
|
||||||
|
|
||||||
|
-- `terminate_on_failure == true` means that the Lua script should send the response
|
||||||
|
-- and terminate the transaction in the case of a failure. This will happen when
|
||||||
|
-- hdr_fail content isn't a dash `-`.
|
||||||
|
local terminate_on_failure = hdr_fail ~= "-"
|
||||||
|
|
||||||
-- Check whether we received a valid HTTP response.
|
-- Check whether we received a valid HTTP response.
|
||||||
if response == nil then
|
if response == nil then
|
||||||
txn:Warning("Failure in auth-request backend '" .. be .. "': " .. err)
|
txn:Warning("Failure in auth-request backend '" .. be .. "': " .. err)
|
||||||
set_var(txn, "txn.auth_response_code", 500)
|
set_var(txn, "txn.auth_response_code", 500)
|
||||||
|
if terminate_on_failure then
|
||||||
|
send_response(txn)
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
set_var(txn, "txn.auth_response_code", response.status_code)
|
set_var(txn, "txn.auth_response_code", response.status_code)
|
||||||
|
local response_ok = 200 <= response.status_code and response.status_code < 300
|
||||||
|
|
||||||
for header, value in response:get_headers(true) do
|
for header, value in response:get_headers(true) do
|
||||||
set_var(txn, "req.auth_response_header." .. sanitize_header_for_variable(header), value)
|
set_var(txn, "req.auth_response_header." .. sanitize_header_for_variable(header), value)
|
||||||
|
if response_ok and hdr_succeed ~= "-" and header_match(header, hdr_succeed) then
|
||||||
|
txn.http:req_set_header(header, value)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 2xx: Allow request.
|
-- response_ok means 2xx: allow request.
|
||||||
if 200 <= response.status_code and response.status_code < 300 then
|
if response_ok then
|
||||||
set_var(txn, "txn.auth_response_successful", true)
|
set_var(txn, "txn.auth_response_successful", true)
|
||||||
-- Don't allow other codes.
|
-- Don't allow codes < 200 or >= 300.
|
||||||
|
-- Forward the response to the client if required.
|
||||||
|
elseif terminate_on_failure then
|
||||||
|
send_response(txn, response, hdr_fail)
|
||||||
-- Codes with Location: Passthrough location at redirect.
|
-- Codes with Location: Passthrough location at redirect.
|
||||||
elseif response.status_code == 301 or response.status_code == 302 or response.status_code == 303 or response.status_code == 307 or response.status_code == 308 then
|
elseif response.status_code == 301 or response.status_code == 302 or response.status_code == 303 or response.status_code == 307 or response.status_code == 308 then
|
||||||
set_var(txn, "txn.auth_response_location", response:get_header("location", "last"))
|
set_var(txn, "txn.auth_response_location", response:get_header("location", "last"))
|
||||||
|
@ -116,4 +208,4 @@ core.register_action("auth-request", { "http-req" }, function(txn, be, path)
|
||||||
elseif response.status_code ~= 401 and response.status_code ~= 403 then
|
elseif response.status_code ~= 401 and response.status_code ~= 403 then
|
||||||
txn:Warning("Invalid status code in auth-request backend '" .. be .. "': " .. response.status_code)
|
txn:Warning("Invalid status code in auth-request backend '" .. be .. "': " .. response.status_code)
|
||||||
end
|
end
|
||||||
end, 2)
|
end
|
||||||
|
|
|
@ -42,25 +42,17 @@ frontend fe_http
|
||||||
http-request set-var(req.scheme) str(https) if { ssl_fc }
|
http-request set-var(req.scheme) str(https) if { ssl_fc }
|
||||||
http-request set-var(req.scheme) str(http) if !{ ssl_fc }
|
http-request set-var(req.scheme) str(http) if !{ ssl_fc }
|
||||||
http-request set-var(req.questionmark) str(?) if { query -m found }
|
http-request set-var(req.questionmark) str(?) if { query -m found }
|
||||||
http-request set-var(req.method) str(CONNECT) if { method CONNECT }
|
|
||||||
http-request set-var(req.method) str(GET) if { method GET }
|
|
||||||
http-request set-var(req.method) str(HEAD) if { method HEAD }
|
|
||||||
http-request set-var(req.method) str(OPTIONS) if { method OPTIONS }
|
|
||||||
http-request set-var(req.method) str(POST) if { method POST }
|
|
||||||
http-request set-var(req.method) str(TRACE) if { method TRACE }
|
|
||||||
http-request set-var(req.method) str(PUT) if { method PUT }
|
|
||||||
http-request set-var(req.method) str(PATCH) if { method PATCH }
|
|
||||||
http-request set-var(req.method) str(DELETE) if { method DELETE }
|
|
||||||
|
|
||||||
http-request set-header X-Real-IP %[src]
|
http-request set-header X-Real-IP %[src]
|
||||||
http-request set-header X-Original-Method %[var(req.method)]
|
http-request set-header X-Forwarded-Method %[method]
|
||||||
http-request set-header X-Original-URL %[var(req.scheme)]://%[req.hdr(Host)]%[path]%[var(req.questionmark)]%[query]
|
http-request set-header X-Forwarded-Proto %[var(req.scheme)]
|
||||||
|
http-request set-header X-Forwarded-Host %[req.hdr(Host)]
|
||||||
|
http-request set-header X-Forwarded-URI %[path]%[var(req.questionmark)]%[query]
|
||||||
|
|
||||||
# be_auth_request is used to make HAProxy do the TLS termination since the Lua script
|
# be_auth_request is used to make HAProxy do the TLS termination since the Lua script
|
||||||
# does not know how to handle it (see https://github.com/TimWolla/haproxy-auth-request/issues/12).
|
# does not know how to handle it (see https://github.com/TimWolla/haproxy-auth-request/issues/12).
|
||||||
http-request lua.auth-request be_auth_request /api/authz/auth-request if protected-frontends
|
http-request lua.auth-intercept be_auth_request /api/authz/forward-auth HEAD * authorization,proxy-authorization,remote_user,remote-user,remote-groups,remote-name,remote-email - if protected-frontends
|
||||||
|
http-request redirect location %[var(txn.auth_response_location)] if protected-frontends !{ var(txn.auth_response_successful) -m bool }
|
||||||
http-request redirect location https://login.example.com:8080/?rd=%[var(req.scheme)]://%[base]%[var(req.questionmark)]%[query]&rm=%[var(req.method)] if protected-frontends !{ var(txn.auth_response_successful) -m bool }
|
|
||||||
|
|
||||||
use_backend be_authelia if host-authelia-portal api-path || devworkflow-path || jwks-path || locales-path || wellknown-path
|
use_backend be_authelia if host-authelia-portal api-path || devworkflow-path || jwks-path || locales-path || wellknown-path
|
||||||
use_backend fe_authelia if host-authelia-portal !api-path
|
use_backend fe_authelia if host-authelia-portal !api-path
|
||||||
|
@ -86,24 +78,6 @@ backend fe_authelia
|
||||||
server authelia-backend authelia-backend:9091 check backup resolvers docker ssl verify none
|
server authelia-backend authelia-backend:9091 check backup resolvers docker ssl verify none
|
||||||
|
|
||||||
backend be_httpbin
|
backend be_httpbin
|
||||||
## Pass the special authorization response headers to the protected application.
|
|
||||||
acl authorization_exist var(req.auth_response_header.authorization) -m found
|
|
||||||
acl proxy_authorization_exist var(req.auth_response_header.proxy_authorization) -m found
|
|
||||||
|
|
||||||
http-request set-header Authorization %[var(req.auth_response_header.authorization)] if authorization_exist
|
|
||||||
http-request set-header Proxy-Authorization %[var(req.auth_response_header.proxy_authorization)] if proxy_authorization_exist
|
|
||||||
|
|
||||||
## Pass the special metadata response headers to the protected application.
|
|
||||||
acl remote_user_exist var(req.auth_response_header.remote_user) -m found
|
|
||||||
acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found
|
|
||||||
acl remote_name_exist var(req.auth_response_header.remote_name) -m found
|
|
||||||
acl remote_email_exist var(req.auth_response_header.remote_email) -m found
|
|
||||||
|
|
||||||
http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist
|
|
||||||
http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist
|
|
||||||
http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist
|
|
||||||
http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist
|
|
||||||
|
|
||||||
## Pass the Set-Cookie response headers to the user.
|
## Pass the Set-Cookie response headers to the user.
|
||||||
acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found
|
acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found
|
||||||
http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist
|
http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist
|
||||||
|
@ -114,4 +88,8 @@ backend be_mail
|
||||||
server smtp-backend smtp:1080 resolvers docker
|
server smtp-backend smtp:1080 resolvers docker
|
||||||
|
|
||||||
backend be_protected
|
backend be_protected
|
||||||
|
## Pass the Set-Cookie response headers to the user.
|
||||||
|
acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found
|
||||||
|
http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist
|
||||||
|
|
||||||
server nginx-backend nginx-backend:80 resolvers docker
|
server nginx-backend nginx-backend:80 resolvers docker
|
||||||
|
|
|
@ -166,9 +166,6 @@ http {
|
||||||
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
|
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
|
||||||
auth_request /internal/authelia/authz;
|
auth_request /internal/authelia/authz;
|
||||||
|
|
||||||
## Set the $target_url variable based on the original request.
|
|
||||||
set $target_url $scheme://$http_host$request_uri;
|
|
||||||
|
|
||||||
## Save the upstream authorization response headers from Authelia to variables.
|
## Save the upstream authorization response headers from Authelia to variables.
|
||||||
auth_request_set $authorization $upstream_http_authorization;
|
auth_request_set $authorization $upstream_http_authorization;
|
||||||
auth_request_set $proxy_authorization $upstream_http_proxy_authorization;
|
auth_request_set $proxy_authorization $upstream_http_proxy_authorization;
|
||||||
|
@ -193,8 +190,23 @@ http {
|
||||||
auth_request_set $cookie $upstream_http_set_cookie;
|
auth_request_set $cookie $upstream_http_set_cookie;
|
||||||
add_header Set-Cookie $cookie;
|
add_header Set-Cookie $cookie;
|
||||||
|
|
||||||
## If the subreqest returns 200 pass to the backend, if the subrequest returns 401 redirect to the portal.
|
## Configure the redirection when the Authz failure occurs. Lines starting with 'Modern Method' and 'Legacy Method'
|
||||||
error_page 401 =302 https://login.$basedomain:8080/?rd=$target_url;
|
## should be commented / uncommented as pairs. The modern method uses the session cookies configuration's authelia_url
|
||||||
|
## value to determine the redirection URL here. It's much simpler and compatible with the mutli-cookie domain easily.
|
||||||
|
|
||||||
|
## Modern Method: Set the $redirection_url to the Location header of the response to the Authz endpoint.
|
||||||
|
auth_request_set $redirection_url $upstream_http_location;
|
||||||
|
|
||||||
|
## Modern Method: When there is a 401 response code from the Authz endpoint redirect to the $redirection_url.
|
||||||
|
error_page 401 =302 $redirection_url;
|
||||||
|
|
||||||
|
## Legacy Method: Set $target_url to the original requested URL.
|
||||||
|
## This requires http_set_misc module, replace 'set_escape_uri' with 'set' if you don't have this module.
|
||||||
|
# set $target_url $scheme://$http_host$request_uri;
|
||||||
|
|
||||||
|
## Legacy Method: When there is a 401 response code from the Authz endpoint redirect to the portal with the 'rd'
|
||||||
|
## URL parameter set to $target_url. This requires users update 'auth.example.com/' with their external authelia URL.
|
||||||
|
# error_page 401 =302 https://login.$basedomain:8080/?rd=$target_url;
|
||||||
|
|
||||||
# Authelia relies on Proxy-Authorization header to authenticate in basic auth.
|
# Authelia relies on Proxy-Authorization header to authenticate in basic auth.
|
||||||
# but for the sake of simplicity (because Authorization in supported in most
|
# but for the sake of simplicity (because Authorization in supported in most
|
||||||
|
@ -252,9 +264,6 @@ http {
|
||||||
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
|
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
|
||||||
auth_request /internal/authelia/authz;
|
auth_request /internal/authelia/authz;
|
||||||
|
|
||||||
## Set the $target_url variable based on the original request.
|
|
||||||
set $target_url $scheme://$http_host$request_uri;
|
|
||||||
|
|
||||||
## Save the upstream authorization response headers from Authelia to variables.
|
## Save the upstream authorization response headers from Authelia to variables.
|
||||||
auth_request_set $authorization $upstream_http_authorization;
|
auth_request_set $authorization $upstream_http_authorization;
|
||||||
auth_request_set $proxy_authorization $upstream_http_proxy_authorization;
|
auth_request_set $proxy_authorization $upstream_http_proxy_authorization;
|
||||||
|
@ -279,8 +288,23 @@ http {
|
||||||
auth_request_set $cookie $upstream_http_set_cookie;
|
auth_request_set $cookie $upstream_http_set_cookie;
|
||||||
add_header Set-Cookie $cookie;
|
add_header Set-Cookie $cookie;
|
||||||
|
|
||||||
## If the subreqest returns 200 pass to the backend, if the subrequest returns 401 redirect to the portal.
|
## Configure the redirection when the Authz failure occurs. Lines starting with 'Modern Method' and 'Legacy Method'
|
||||||
error_page 401 =302 https://login.$basedomain:8080/?rd=$target_url;
|
## should be commented / uncommented as pairs. The modern method uses the session cookies configuration's authelia_url
|
||||||
|
## value to determine the redirection URL here. It's much simpler and compatible with the mutli-cookie domain easily.
|
||||||
|
|
||||||
|
## Modern Method: Set the $redirection_url to the Location header of the response to the Authz endpoint.
|
||||||
|
auth_request_set $redirection_url $upstream_http_location;
|
||||||
|
|
||||||
|
## Modern Method: When there is a 401 response code from the Authz endpoint redirect to the $redirection_url.
|
||||||
|
error_page 401 =302 $redirection_url;
|
||||||
|
|
||||||
|
## Legacy Method: Set $target_url to the original requested URL.
|
||||||
|
## This requires http_set_misc module, replace 'set_escape_uri' with 'set' if you don't have this module.
|
||||||
|
# set $target_url $scheme://$http_host$request_uri;
|
||||||
|
|
||||||
|
## Legacy Method: When there is a 401 response code from the Authz endpoint redirect to the portal with the 'rd'
|
||||||
|
## URL parameter set to $target_url. This requires users update 'auth.example.com/' with their external authelia URL.
|
||||||
|
# error_page 401 =302 https://login.$basedomain:8080/?rd=$target_url;
|
||||||
|
|
||||||
proxy_pass $upstream_headers;
|
proxy_pass $upstream_headers;
|
||||||
}
|
}
|
||||||
|
@ -309,9 +333,6 @@ http {
|
||||||
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
|
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
|
||||||
auth_request /internal/authelia/authz;
|
auth_request /internal/authelia/authz;
|
||||||
|
|
||||||
## Set the $target_url variable based on the original request.
|
|
||||||
set $target_url $scheme://$http_host$request_uri;
|
|
||||||
|
|
||||||
## Save the upstream authorization response headers from Authelia to variables.
|
## Save the upstream authorization response headers from Authelia to variables.
|
||||||
auth_request_set $authorization $upstream_http_authorization;
|
auth_request_set $authorization $upstream_http_authorization;
|
||||||
auth_request_set $proxy_authorization $upstream_http_proxy_authorization;
|
auth_request_set $proxy_authorization $upstream_http_proxy_authorization;
|
||||||
|
@ -336,8 +357,23 @@ http {
|
||||||
auth_request_set $cookie $upstream_http_set_cookie;
|
auth_request_set $cookie $upstream_http_set_cookie;
|
||||||
add_header Set-Cookie $cookie;
|
add_header Set-Cookie $cookie;
|
||||||
|
|
||||||
## If the subreqest returns 200 pass to the backend, if the subrequest returns 401 redirect to the portal.
|
## Configure the redirection when the Authz failure occurs. Lines starting with 'Modern Method' and 'Legacy Method'
|
||||||
error_page 401 =302 https://login.$basedomain:8080/?rd=$target_url;
|
## should be commented / uncommented as pairs. The modern method uses the session cookies configuration's authelia_url
|
||||||
|
## value to determine the redirection URL here. It's much simpler and compatible with the mutli-cookie domain easily.
|
||||||
|
|
||||||
|
## Modern Method: Set the $redirection_url to the Location header of the response to the Authz endpoint.
|
||||||
|
auth_request_set $redirection_url $upstream_http_location;
|
||||||
|
|
||||||
|
## Modern Method: When there is a 401 response code from the Authz endpoint redirect to the $redirection_url.
|
||||||
|
error_page 401 =302 $redirection_url;
|
||||||
|
|
||||||
|
## Legacy Method: Set $target_url to the original requested URL.
|
||||||
|
## This requires http_set_misc module, replace 'set_escape_uri' with 'set' if you don't have this module.
|
||||||
|
# set $target_url $scheme://$http_host$request_uri;
|
||||||
|
|
||||||
|
## Legacy Method: When there is a 401 response code from the Authz endpoint redirect to the portal with the 'rd'
|
||||||
|
## URL parameter set to $target_url. This requires users update 'auth.example.com/' with their external authelia URL.
|
||||||
|
# error_page 401 =302 https://login.$basedomain:8080/?rd=$target_url;
|
||||||
|
|
||||||
# Route the request to the correct virtual host in the backend.
|
# Route the request to the correct virtual host in the backend.
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -79,6 +80,8 @@ func FuncMap() map[string]any {
|
||||||
"indent": FuncIndent,
|
"indent": FuncIndent,
|
||||||
"nindent": FuncNewlineIndent,
|
"nindent": FuncNewlineIndent,
|
||||||
"uuidv4": FuncUUIDv4,
|
"uuidv4": FuncUUIDv4,
|
||||||
|
"urlquery": url.QueryEscape,
|
||||||
|
"urlunquery": url.QueryUnescape,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
th "html/template"
|
th "html/template"
|
||||||
|
"io/fs"
|
||||||
"path"
|
"path"
|
||||||
tt "text/template"
|
tt "text/template"
|
||||||
)
|
)
|
||||||
|
@ -28,7 +28,7 @@ type Provider struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadTemplatedAssets takes an embed.FS and loads each templated asset document into a Template.
|
// LoadTemplatedAssets takes an embed.FS and loads each templated asset document into a Template.
|
||||||
func (p *Provider) LoadTemplatedAssets(fs embed.FS) (err error) {
|
func (p *Provider) LoadTemplatedAssets(fs fs.ReadFileFS) (err error) {
|
||||||
var (
|
var (
|
||||||
data []byte
|
data []byte
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue