Repository: authelia/authelia (go)

Issue:
[Feature Request] Allow custom HTTP methods
Right now Authelia blocks any HTTP method that is not in the standard list, i.e.:
```
{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
```
This is hardcoded, as far as I understand, and even if I explicitly specify some other method in configuration, Authelia will refuse to start. This poses a major problem for WebDAV, CalDAV and alikes, which use an additional set of HTTP methods to communicate with backend (COPY, LOCK, PROPFIND, etc...).

I propose to make the list of allowed methods strict only if the user didn't specify otherwise.




Pull request description:
feat(configuration): allow rfc4918 http verbs in acl
This allows the HTTP Method verbs from RFC4918 to be used. See https://datatracker.ietf.org/doc/html/rfc4918 for more information.

Closes #2923

Patch:
diff --git a/api/openapi.yml b/api/openapi.yml
index 74f4dad16..35136362d 100644
--- a/api/openapi.yml
+++ b/api/openapi.yml
@@ -637,7 +637,23 @@ components:
       explode: true
       schema:
         type: string
-        enum: ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"]
+        enum:
+          - "GET"
+          - "HEAD"
+          - "POST"
+          - "PUT"
+          - "PATCH"
+          - "DELETE"
+          - "TRACE"
+          - "CONNECT"
+          - "OPTIONS"
+          - "COPY"
+          - "LOCK"
+          - "MKCOL"
+          - "MOVE"
+          - "PROPFIND"
+          - "PROPPATCH"
+          - "UNLOCK"
     authParam:
       name: auth
       in: query
diff --git a/docs/configuration/access-control.md b/docs/configuration/access-control.md
index 158d27612..7c90395bc 100644
--- a/docs/configuration/access-control.md
+++ b/docs/configuration/access-control.md
@@ -271,8 +271,16 @@ access_control:
     - OPTIONS
 ```
 
-The valid request methods are: OPTIONS, HEAD, GET, POST, PUT, PATCH, DELETE, TRACE, CONNECT. Additional information 
-about HTTP request methods can be found on the [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods).
+The accepted and valid methods for this configuration option are those specified in well known RFC's. The RFC's and the
+relevant methods are listed in this table:
+
+|                           RFC                            |                        Methods                        |                     Additional Documentation                     |
+|:--------------------------------------------------------:|:-----------------------------------------------------:|:----------------------------------------------------------------:|
+| [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231) | GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE | [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) |
+| [RFC5789](https://datatracker.ietf.org/doc/html/rfc5789) |                         PATCH                         | [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) |
+| [RFC4918](https://datatracker.ietf.org/doc/html/rfc4918) | PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK  |                                                                  |
+
+
 
 ### networks
 <div markdown="1">
diff --git a/internal/configuration/validator/access_control.go b/internal/configuration/validator/access_control.go
index 1b70be0df..ec89b395a 100644
--- a/internal/configuration/validator/access_control.go
+++ b/internal/configuration/validator/access_control.go
@@ -147,8 +147,8 @@ func validateSubjects(rulePosition int, rule schema.ACLRule, validator *schema.S
 
 func validateMethods(rulePosition int, rule schema.ACLRule, validator *schema.StructValidator) {
 	for _, method := range rule.Methods {
-		if !utils.IsStringInSliceFold(method, validACLRuleMethods) {
-			validator.Push(fmt.Errorf(errFmtAccessControlRuleMethodInvalid, ruleDescriptor(rulePosition, rule), method, strings.Join(validACLRuleMethods, "', '")))
+		if !utils.IsStringInSliceFold(method, validACLHTTPMethodVerbs) {
+			validator.Push(fmt.Errorf(errFmtAccessControlRuleMethodInvalid, ruleDescriptor(rulePosition, rule), method, strings.Join(validACLHTTPMethodVerbs, "', '")))
 		}
 	}
 }
diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go
index 0b3a410c9..95ba84f9d 100644
--- a/internal/configuration/validator/const.go
+++ b/internal/configuration/validator/const.go
@@ -256,7 +256,11 @@ var validLoLevels = []string{"trace", "debug", "info", "warn", "error"}
 var validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
 var validWebauthnUserVerificationRequirement = []string{string(protocol.VerificationDiscouraged), string(protocol.VerificationPreferred), string(protocol.VerificationRequired)}
 
-var validACLRuleMethods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
+var validRFC7231HTTPMethodVerbs = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
+var validRFC4918HTTPMethodVerbs = []string{"COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK"}
+
+var validACLHTTPMethodVerbs = append(validRFC7231HTTPMethodVerbs, validRFC4918HTTPMethodVerbs...)
+
 var validACLRulePolicies = []string{policyBypass, policyOneFactor, policyTwoFactor, policyDeny}
 
 var validOIDCScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, "offline_access"}


Test patch:
diff --git a/internal/configuration/validator/access_control_test.go b/internal/configuration/validator/access_control_test.go
index 05c95e4e9..7e66864bf 100644
--- a/internal/configuration/validator/access_control_test.go
+++ b/internal/configuration/validator/access_control_test.go
@@ -152,7 +152,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidMethod() {
 	suite.Assert().False(suite.validator.HasWarnings())
 	suite.Require().Len(suite.validator.Errors(), 1)
 
-	suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'methods' option 'HOP' is invalid: must be one of 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS'")
+	suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'methods' option 'HOP' is invalid: must be one of 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS', 'COPY', 'LOCK', 'MKCOL', 'MOVE', 'PROPFIND', 'PROPPATCH', 'UNLOCK'")
 }
 
 func (suite *AccessControl) TestShouldRaiseErrorInvalidResource() {