Skip to content

Commit

Permalink
Basic-Auth "user" accessable via request.context (#402)
Browse files Browse the repository at this point in the history
* Make basic-auth user accessable via request.context

* Test

* Changelog

* Docu
  • Loading branch information
Alex Schneider authored Dec 2, 2021
1 parent b5e6101 commit ac72c6e
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Unreleased changes are available as `avenga/couper:edge` container.

* **Added**
* [`custom_log_fields`](./docs/LOGS.md#custom-logging) attribute to be able to describe a user defined map for `custom` log field enrichment ([#388](https://github.com/avenga/couper/pull/388))
* * The `user` as context variable from a [Basic Auth](./docs/REFERENCE.md#basic-auth-block) is now accessable via `request.context.<label>.user` for successfully authenticated requests ([#402](https://github.com/avenga/couper/pull/402))

* **Changed**
* Missing [scope or roles claims](./docs/REFERENCE.md#jwt-block), or scope or roles claim with unsupported values are now ignored instead of causing an error ([#380](https://github.com/avenga/couper/issues/380))
Expand Down
23 changes: 21 additions & 2 deletions accesscontrol/basic_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package accesscontrol

import (
"bufio"
"context"
"crypto/subtle"
"fmt"
"net/http"
"os"
"strconv"
"strings"

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

Expand Down Expand Up @@ -117,7 +119,7 @@ func (ba *BasicAuth) Validate(req *http.Request) error {
if ba.user == user {
if ba.pass != "" {
if subtle.ConstantTimeCompare([]byte(ba.pass), []byte(pass)) == 1 {
return nil
return ba.withUsername(req, user)
}
return errors.BasicAuth.Message("credential mismatch")
}
Expand All @@ -129,10 +131,27 @@ func (ba *BasicAuth) Validate(req *http.Request) error {

if len(ba.htFile) > 0 {
if validateAccessData(user, pass, ba.htFile) {
return nil
return ba.withUsername(req, user)
}
return errors.BasicAuth.Message("file: credential mismatch")
}

return errors.BasicAuth.Message("credential mismatch")
}

func (ba *BasicAuth) withUsername(req *http.Request, user string) error {
u := make(map[string]interface{})
u["user"] = user

ctx := req.Context()
acMap, ok := ctx.Value(request.AccessControls).(map[string]interface{})
if !ok {
acMap = make(map[string]interface{})
}
acMap[ba.name] = u

ctx = context.WithValue(ctx, request.AccessControls, acMap)
*req = *req.WithContext(ctx)

return nil
}
8 changes: 6 additions & 2 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ by `htpasswd_file` otherwise.
| `realm` | string | `""` | The realm to be sent in a `WWW-Authenticate` response HTTP header field. | - | - |
| `custom_log_fields` | map | - | Defines log fields for [Custom Logging](LOGS.md#custom-logging). | &#9888; Inherited by nested blocks. | - |

The `user` is accessable via `request.context.<label>.user` for successfully authenticated requests.

### JWT Block

The `jwt` block lets you configure JSON Web Token access control for your gateway.
Expand Down Expand Up @@ -608,9 +610,11 @@ defaults {

The value of `context.<name>` depends on the type of block referenced by `<name>`.

For a [JWT Block](#jwt-block), the variable contains claims from the JWT used for [Access Control](#access-control).
For a [Basic Auth](#basic-auth-block) and successfully authenticated request the variable contains the `user` name.

For a [JWT Block](#jwt-block) the variable contains claims from the JWT used for [Access Control](#access-control).

For a [SAML Block](#saml-block), the variable contains
For a [SAML Block](#saml-block) the variable contains

- `sub`: the `NameID` of the SAML assertion
- `exp`: optional expiration date (value of `SessionNotOnOrAfter` of the SAML assertion)
Expand Down
2 changes: 2 additions & 0 deletions server/http_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3026,6 +3026,7 @@ func TestConfigBodyContentAccessControl(t *testing.T) {
{"/v5/not-exist", http.Header{}, http.StatusUnauthorized, "application/json", "access control error: ba1: credentials required"},
{"/superadmin", http.Header{"Authorization": []string{"Basic OmFzZGY="}, "Auth": []string{"ba1", "ba4"}}, http.StatusOK, "application/json", ""},
{"/superadmin", http.Header{}, http.StatusUnauthorized, "application/json", "access control error: ba1: credentials required"},
{"/ba5", http.Header{"Authorization": []string{"Basic VVNSOlBXRA=="}, "X-Ba-User": []string{"USR"}}, http.StatusOK, "application/json", ""},
{"/v4", http.Header{}, http.StatusUnauthorized, "text/html", "access control error: ba1: credentials required"},
} {
t.Run(tc.path[1:], func(subT *testing.T) {
Expand All @@ -3041,6 +3042,7 @@ func TestConfigBodyContentAccessControl(t *testing.T) {

res, err := client.Do(req)
helper.Must(err)
// t.Errorf(">>> %#v", res.Header)

message := getAccessControlMessages(hook)
if tc.wantErrLog == "" {
Expand Down
20 changes: 20 additions & 0 deletions server/testdata/integration/config/03_couper.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@ server "acs" {
}
}

endpoint "/ba5" {
access_control = ["ba5"]
disable_access_control = ["ba1"]
proxy {
backend "test" {
set_request_headers = {
X-Ba-User = request.context.ba5.user
Authorization = request.headers.authorization # proxy blacklist
}
set_response_headers = {
X-BA-User = request.context.ba5.user
}
}
}
}

endpoint "/jwt" {
disable_access_control = ["ba1"]
access_control = ["JWTToken"]
Expand Down Expand Up @@ -200,6 +216,10 @@ definitions {
basic_auth "ba4" {
password = "asdf"
}
basic_auth "ba5" {
user = "USR"
password = "PWD"
}
jwt "JWTToken" {
header = "Authorization"
signature_algorithm = "HS256"
Expand Down

0 comments on commit ac72c6e

Please sign in to comment.