mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-10 07:01:01 +00:00
add test coverage
for the following auth related packages: * authn * http * server fix minor bugs, do some cleaning up, etc in `authn/authenticate.go` and `http/handler.go`
This commit is contained in:
parent
c7208cf5e3
commit
836df379ac
5 changed files with 525 additions and 82 deletions
|
|
@ -117,6 +117,7 @@ type Groups struct {
|
|||
// Authenticate takes in a bearer token `bearer` and returns UserInfo from that token
|
||||
func (a *Auth) Authenticate(bearer string) (*UserInfo, error) {
|
||||
// parse the bearer token into a jwt.Token
|
||||
// this also validates the token, and checks that it's not expired
|
||||
token, err := jwt.Parse(bearer, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
|
|
@ -124,39 +125,21 @@ func (a *Auth) Authenticate(bearer string) (*UserInfo, error) {
|
|||
return a.secretKey, nil
|
||||
})
|
||||
if token == nil || token.Claims == nil || err != nil || !token.Valid {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("%#v parsing jwt claims from access tokens", token))
|
||||
return nil, fmt.Errorf("parsing bearer token: %v", err)
|
||||
}
|
||||
userInfo := UserInfo{}
|
||||
// check that token does not expire now
|
||||
switch claimType := token.Claims.(type) {
|
||||
case jwt.MapClaims:
|
||||
if exp, ok := claimType["exp"]; ok {
|
||||
var e int64
|
||||
switch expType := exp.(type) {
|
||||
case float64:
|
||||
e = int64(expType)
|
||||
case json.Number:
|
||||
e, _ = expType.Int64()
|
||||
}
|
||||
if e <= time.Now().Unix() {
|
||||
return nil, fmt.Errorf("token expired")
|
||||
}
|
||||
}
|
||||
|
||||
userInfo.UserID = claimType["oid"].(string)
|
||||
userInfo.UserName = claimType["name"].(string)
|
||||
userInfo.Token = bearer
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
userInfo.UserID = claims["oid"].(string)
|
||||
userInfo.UserName = claims["name"].(string)
|
||||
userInfo.Token = bearer
|
||||
|
||||
g := claimType["molecula-idp-groups"].(string)
|
||||
groups, err := FromGob64(g)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "decoding groups")
|
||||
}
|
||||
userInfo.Groups = groups
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("could not parse jwt claims of type %T, expected jwt.MapClaims", claimType)
|
||||
g := claims["molecula-idp-groups"].(string)
|
||||
groups, err := FromGob64(g)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "decoding groups")
|
||||
}
|
||||
userInfo.Groups = groups
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
|
@ -194,8 +177,16 @@ func (a *Auth) Redirect(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// with vitamin A!
|
||||
enrichedTkn, err := a.addGroupMembership(token.AccessToken)
|
||||
// enrich token with groups!
|
||||
g, err := a.getGroups(token.AccessToken)
|
||||
if err != nil {
|
||||
a.logger.Warnf("getting groups from IdP: %+v", err)
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// with vitamin G! (for groups)
|
||||
enrichedTkn, err := a.addGroupMembership(token.AccessToken, g)
|
||||
if err != nil {
|
||||
a.logger.Warnf("enriching token with group membership: %+v", err)
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
|
|
@ -217,40 +208,28 @@ func (a *Auth) getToken(r *http.Request, code string) (*oauth2.Token, error) {
|
|||
|
||||
// addGroupMembership is only called in `a.Redirect`. It adds groups to a jwt's
|
||||
// claims, and signs it using `a.secretKey`.
|
||||
func (a *Auth) addGroupMembership(token string) (string, error) {
|
||||
g, err := a.getGroups(token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
func (a *Auth) addGroupMembership(token string, g []Group) (string, error) {
|
||||
// parse token into jwt
|
||||
unenriched, _, err := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{})
|
||||
if unenriched == nil || unenriched.Claims == nil || err != nil {
|
||||
return "", errors.Wrap(err, fmt.Sprintf("%v parsing jwt claims from access tokens", token))
|
||||
return "", fmt.Errorf("parsing bearer token: %v", err)
|
||||
}
|
||||
|
||||
enriched := jwt.New(jwt.SigningMethodHS256)
|
||||
enriched.Claims = unenriched.Claims
|
||||
var tokenStr string
|
||||
// parse groups into string format
|
||||
switch claims := enriched.Claims.(type) {
|
||||
case jwt.MapClaims:
|
||||
groupString, err := ToGob64(g)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to serialize groups")
|
||||
}
|
||||
claims := enriched.Claims.(jwt.MapClaims)
|
||||
groupString, err := ToGob64(g)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to serialize groups")
|
||||
}
|
||||
// stick it into jwt claims
|
||||
claims["molecula-idp-groups"] = groupString
|
||||
|
||||
// stick it into jwt claims
|
||||
claims["molecula-idp-groups"] = groupString
|
||||
|
||||
// get stringified and signed jwt
|
||||
tokenStr, err = enriched.SignedString(a.secretKey)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "signing jwt")
|
||||
}
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("could not parse jwt claims of type %T, expected jwt.MapClaims", claims)
|
||||
// get stringified and signed jwt
|
||||
tokenStr, err := enriched.SignedString(a.secretKey)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "signing jwt")
|
||||
}
|
||||
|
||||
return tokenStr, nil
|
||||
|
|
@ -303,7 +282,7 @@ func decodeHex(hexstr string) ([]byte, error) {
|
|||
return nil, errors.Wrap(err, "decoding hex string to byte slice")
|
||||
}
|
||||
if len(data) != 32 {
|
||||
return nil, errors.Wrap(err, "invalid key length")
|
||||
return nil, fmt.Errorf("invalid key length")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
package authn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/molecula/featurebase/v3/logger"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
func NewTestAuth(t *testing.T) *Auth {
|
||||
t.Helper()
|
||||
var (
|
||||
ClientID = "e9088663-eb08-41d7-8f65-efb5f54bbb71"
|
||||
ClientSecret = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
|
||||
|
|
@ -20,7 +28,6 @@ func TestAuth(t *testing.T) {
|
|||
LogoutURL = "https://login.microsoftonline.com/common/oauth2/v2.0/logout"
|
||||
Scopes = []string{"https://graph.microsoft.com/.default", "offline_access"}
|
||||
Key = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
|
||||
ShortKey = "DEADBEEFD"
|
||||
)
|
||||
|
||||
a, err := NewAuth(
|
||||
|
|
@ -36,9 +43,13 @@ func TestAuth(t *testing.T) {
|
|||
Key,
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("building auth object%s", err)
|
||||
t.Fatalf("building auth object%s", err)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
a := NewTestAuth(t)
|
||||
t.Run("SetCookie", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
err := a.setCookie(w, "a cookie value", time.Now().Add(time.Hour))
|
||||
|
|
@ -53,23 +64,267 @@ func TestAuth(t *testing.T) {
|
|||
if got, want := w.Result().Cookies()[0].Path, "/"; got != want {
|
||||
t.Fatalf("path=%s, want %s", got, want)
|
||||
}
|
||||
|
||||
})
|
||||
t.Run("KeyLength", func(t *testing.T) {
|
||||
_, err := NewAuth(
|
||||
logger.NewStandardLogger(os.Stdout),
|
||||
"http://localhost:10101/",
|
||||
Scopes,
|
||||
AuthorizeURL,
|
||||
TokenURL,
|
||||
GroupEndpointURL,
|
||||
LogoutURL,
|
||||
ClientID,
|
||||
ClientSecret,
|
||||
ShortKey,
|
||||
[]string{"https://graph.microsoft.com/.default", "offline_access"},
|
||||
"https://login.microsoftonline.com/4a137d66-d161-4ae4-b1e6-07e9920874b8/oauth2/v2.0/authorize",
|
||||
"https://login.microsoftonline.com/4a137d66-d161-4ae4-b1e6-07e9920874b8/oauth2/v2.0/token",
|
||||
"https://graph.microsoft.com/v1.0/me/transitiveMemberOf/microsoft.graph.group?$count=true",
|
||||
"https://login.microsoftonline.com/common/oauth2/v2.0/logout",
|
||||
"e9088663-eb08-41d7-8f65-efb5f54bbb71",
|
||||
"DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
|
||||
"DEADBEEFD",
|
||||
)
|
||||
if err == nil || !strings.Contains(err.Error(), "decoding secret key") {
|
||||
t.Fatalf("expected error decoding secret key got: %v", err)
|
||||
}
|
||||
})
|
||||
t.Run("GetSecretKey", func(t *testing.T) {
|
||||
want, _ := hex.DecodeString("DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
|
||||
if got := a.SecretKey(); !bytes.Equal(got, want) {
|
||||
t.Fatalf("expected %v, got %v", got, want)
|
||||
}
|
||||
})
|
||||
cases := []struct {
|
||||
name string
|
||||
uid string
|
||||
uname string
|
||||
exp interface{}
|
||||
groups []Group
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "GoodToken",
|
||||
uid: "42",
|
||||
uname: "A. Token",
|
||||
groups: []Group{
|
||||
{
|
||||
GroupID: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe",
|
||||
GroupName: "adminGroup",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ExpiredToken",
|
||||
uid: "42",
|
||||
uname: "A. Token",
|
||||
groups: []Group{
|
||||
{
|
||||
GroupID: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe",
|
||||
GroupName: "adminGroup",
|
||||
},
|
||||
},
|
||||
exp: "-17764800",
|
||||
err: errors.Wrap(fmt.Errorf("Token is expired"), "parsing bearer token"),
|
||||
},
|
||||
}
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
tkn := jwt.New(jwt.SigningMethodHS256)
|
||||
claims := tkn.Claims.(jwt.MapClaims)
|
||||
groupString, err := ToGob64(test.groups)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error when gobbing groups %v", err)
|
||||
}
|
||||
claims["molecula-idp-groups"] = groupString
|
||||
claims["oid"] = test.uid
|
||||
claims["name"] = test.uname
|
||||
if test.exp != nil {
|
||||
claims["exp"] = test.exp
|
||||
}
|
||||
token, err := tkn.SignedString(a.SecretKey())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error when signing token %v", err)
|
||||
}
|
||||
|
||||
uinfo, err := a.Authenticate(token)
|
||||
// okay this part kind of sucks bc we need to check errors and i
|
||||
// dont want to write a whole new test for things that should have
|
||||
// errors just to avoid this mess. errors.Is doesn't work either
|
||||
if (test.err == nil && err != nil) || (test.err != nil && err == nil) {
|
||||
t.Fatalf("expected %v, but got %v", test.err, err)
|
||||
} else if test.err != nil && err != nil {
|
||||
if test.err.Error() != err.Error() {
|
||||
t.Fatalf("expected %v, but got %v", test.err, err)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(uinfo.Groups, test.groups) {
|
||||
t.Fatalf("expected %v, got %v", test.groups, uinfo.Groups)
|
||||
}
|
||||
if !reflect.DeepEqual(uinfo.UserID, test.uid) {
|
||||
t.Fatalf("expected %v, got %v", test.uid, uinfo.UserID)
|
||||
}
|
||||
if !reflect.DeepEqual(uinfo.UserName, test.uname) {
|
||||
t.Fatalf("expected %v, got %v", test.uname, uinfo.UserName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGobs(t *testing.T) {
|
||||
t.Run("goodGob!", func(t *testing.T) {
|
||||
g := []Group{
|
||||
{
|
||||
GroupID: "groupA",
|
||||
GroupName: "groupA-Name",
|
||||
},
|
||||
{
|
||||
GroupID: "groupB",
|
||||
GroupName: "groupB-Name",
|
||||
},
|
||||
{
|
||||
GroupID: "groupC",
|
||||
GroupName: "groupC-Name",
|
||||
},
|
||||
}
|
||||
gobbed, err := ToGob64(g)
|
||||
if err != nil {
|
||||
t.Fatalf("could not gob %+v", g)
|
||||
}
|
||||
ungobbed, err := FromGob64(gobbed)
|
||||
if err != nil {
|
||||
t.Fatalf("could not ungob %+v", gobbed)
|
||||
}
|
||||
if !reflect.DeepEqual(ungobbed, g) {
|
||||
t.Fatalf("expected %v, got %v", g, ungobbed)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDecodeHex(t *testing.T) {
|
||||
t.Run("cantDecode", func(t *testing.T) {
|
||||
_, err := decodeHex("gggg")
|
||||
if err == nil {
|
||||
t.Fatalf("expected err cannot decode slice, got nil")
|
||||
}
|
||||
})
|
||||
t.Run("tooSmall", func(t *testing.T) {
|
||||
_, err := decodeHex("DEADBEEF")
|
||||
if err == nil {
|
||||
t.Fatalf("expected err wrong length, got nil")
|
||||
}
|
||||
})
|
||||
t.Run("tooBig", func(t *testing.T) {
|
||||
_, err := decodeHex("DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
|
||||
if err == nil {
|
||||
t.Fatalf("expected err wrong length, got nil")
|
||||
}
|
||||
})
|
||||
t.Run("justRight", func(t *testing.T) {
|
||||
_, err := decodeHex("DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAddGroupMembership(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
groups []Group
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "emptyGroups",
|
||||
groups: []Group{},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "happyPath",
|
||||
groups: []Group{
|
||||
{
|
||||
GroupID: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe",
|
||||
GroupName: "adminGroup",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
a := NewTestAuth(t)
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
tkn := jwt.New(jwt.SigningMethodHS256)
|
||||
token, err := tkn.SignedString(a.SecretKey())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error when signing token %v", err)
|
||||
}
|
||||
|
||||
tokenWithGroups, err := a.addGroupMembership(token, test.groups)
|
||||
// okay this part kind of sucks bc we need to check errors and i
|
||||
// dont want to write a whole new test for things that should have
|
||||
// errors just to avoid this mess. errors.Is doesn't work either
|
||||
if (test.err == nil && err != nil) || (test.err != nil && err == nil) {
|
||||
t.Fatalf("expected %v but got %v", test.err, err)
|
||||
} else if test.err != nil && err != nil {
|
||||
if test.err.Error() != err.Error() {
|
||||
t.Fatalf("expected %v, but got %v", test.err, err)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
parsed, _, err := new(jwt.Parser).ParseUnverified(tokenWithGroups, jwt.MapClaims{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error parsing token %v", err)
|
||||
}
|
||||
|
||||
claims := parsed.Claims.(jwt.MapClaims)
|
||||
groups, err := FromGob64(claims["molecula-idp-groups"].(string))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error parsing groupString %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(groups, test.groups) {
|
||||
t.Fatalf("expected %v, got %v", test.groups, groups)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlers(t *testing.T) {
|
||||
a := NewTestAuth(t)
|
||||
t.Run("login", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/login", nil)
|
||||
w := httptest.NewRecorder()
|
||||
a.Login(w, req)
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != http.StatusTemporaryRedirect {
|
||||
t.Fatalf("expected redirect, got %v", resp.StatusCode)
|
||||
}
|
||||
redirect := a.oAuthConfig.AuthCodeURL(a.oAuthConfig.Endpoint.AuthURL)
|
||||
if got, err := resp.Location(); err != nil || got.String() != redirect {
|
||||
t.Fatalf("expected %v, got %v", redirect, got.Path)
|
||||
}
|
||||
})
|
||||
t.Run("logout", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/logout", nil)
|
||||
w := httptest.NewRecorder()
|
||||
a.Logout(w, req)
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != http.StatusTemporaryRedirect {
|
||||
t.Fatalf("expected redirect, got %v", resp.StatusCode)
|
||||
}
|
||||
redirect := fmt.Sprintf("%s?post_logout_redirect_uri=%s/", a.logoutEndpoint, a.fbURL)
|
||||
if got, err := resp.Location(); err != nil || got.String() != redirect {
|
||||
t.Fatalf("expected %v, got %v", redirect, got.Path)
|
||||
}
|
||||
for _, c := range resp.Cookies() {
|
||||
if c.Name == "molecula-chip" {
|
||||
if c.Value != "" {
|
||||
t.Fatalf("cookie not set to empty value!")
|
||||
}
|
||||
want := time.Unix(0, 0).Unix()
|
||||
got := c.Expires.Unix()
|
||||
if want != got {
|
||||
t.Fatalf("expected %v, got %v", want, got)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -544,9 +544,13 @@ func (h *Handler) chkInternal(handler http.HandlerFunc) http.HandlerFunc {
|
|||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if h.auth != nil {
|
||||
secret, ok := r.Header["X-Feature-Key"]
|
||||
decodedString, err := hex.DecodeString(secret[0])
|
||||
secretString := ""
|
||||
if ok {
|
||||
secretString = secret[0]
|
||||
}
|
||||
decodedString, err := hex.DecodeString(secretString)
|
||||
if err != nil || !ok || !bytes.Equal(decodedString, h.auth.SecretKey()) {
|
||||
http.Error(w, errors.Wrap(err, "internal secret key validation failed").Error(), http.StatusUnauthorized)
|
||||
http.Error(w, "internal secret key validation failed", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
gohttp "net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
|
|
@ -235,7 +236,7 @@ func TestAuthentication(t *testing.T) {
|
|||
claims["name"] = "todd"
|
||||
validToken, err := tkn.SignedString([]byte(secretKey))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
validToken = "Bearer " + validToken
|
||||
|
||||
|
|
@ -247,15 +248,10 @@ func TestAuthentication(t *testing.T) {
|
|||
}
|
||||
|
||||
// make an expired token
|
||||
expiredTkn := jwt.New(jwt.SigningMethodHS256)
|
||||
expiredClaims := expiredTkn.Claims.(jwt.MapClaims)
|
||||
expiredClaims["molecula-idp-groups"] = groupString
|
||||
expiredClaims["oid"] = "42"
|
||||
expiredClaims["name"] = "todd"
|
||||
expiredClaims["exp"] = "1"
|
||||
expiredToken, err := expiredTkn.SignedString([]byte(secretKey))
|
||||
claims["exp"] = "1"
|
||||
expiredToken, err := tkn.SignedString([]byte(secretKey))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
expiredToken = "Bearer " + expiredToken
|
||||
|
||||
|
|
@ -502,9 +498,7 @@ admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
|||
},
|
||||
},
|
||||
{
|
||||
// this tests that there are no permissions read in even though
|
||||
// auth is turned on, so we get a 500
|
||||
name: "MW-CreateIndexGood",
|
||||
name: "MW-CreateIndexInsufficientPerms",
|
||||
path: "/index/abcd",
|
||||
kind: "bearer",
|
||||
method: gohttp.MethodPost,
|
||||
|
|
@ -622,3 +616,172 @@ admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func TestChkAuthN(t *testing.T) {
|
||||
a := NewTestAuth(t)
|
||||
h := Handler{
|
||||
logger: logger.NewStandardLogger(os.Stdout),
|
||||
queryLogger: logger.NewStandardLogger(os.Stdout),
|
||||
auth: a,
|
||||
}
|
||||
|
||||
// make a valid token
|
||||
tkn := jwt.New(jwt.SigningMethodHS256)
|
||||
claims := tkn.Claims.(jwt.MapClaims)
|
||||
groupString, _ := authn.ToGob64([]authn.Group{{GroupID: "thing", GroupName: "whatever"}})
|
||||
claims["molecula-idp-groups"] = groupString
|
||||
claims["oid"] = "42"
|
||||
claims["name"] = "A. Token"
|
||||
validToken, err := tkn.SignedString(a.SecretKey())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
validToken = "Bearer " + validToken
|
||||
|
||||
// make an invalid token
|
||||
invalidKey, err := hex.DecodeString("DEADBEEDDEADBEEDDEADBEEDDEADBEEDDEADBEEDDEADBEEDDEADBEEDDEADBEED")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
invalidToken, err := tkn.SignedString(invalidKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
invalidToken = "Bearer " + invalidToken
|
||||
|
||||
// make an expired token
|
||||
claims["exp"] = "1"
|
||||
expiredToken, err := tkn.SignedString(a.SecretKey())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expiredToken = "Bearer " + expiredToken
|
||||
|
||||
testingHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("good"))
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
endpoint string
|
||||
token string
|
||||
handler http.HandlerFunc
|
||||
statusCode int
|
||||
}{
|
||||
{
|
||||
name: "Valid",
|
||||
token: validToken,
|
||||
handler: h.chkAuthN(testingHandler),
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "Invalid",
|
||||
token: invalidToken,
|
||||
handler: h.chkAuthN(testingHandler),
|
||||
statusCode: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "Expired",
|
||||
token: expiredToken,
|
||||
handler: h.chkAuthN(testingHandler),
|
||||
statusCode: http.StatusUnauthorized,
|
||||
},
|
||||
}
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/whatever", nil)
|
||||
r.Header.Add("Authorization", test.token)
|
||||
test.handler(w, r)
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != test.statusCode {
|
||||
t.Fatalf("expected %v, got %v", test.statusCode, resp.StatusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChkInternal(t *testing.T) {
|
||||
a := NewTestAuth(t)
|
||||
authKey := "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
|
||||
h := Handler{
|
||||
logger: logger.NewStandardLogger(os.Stdout),
|
||||
queryLogger: logger.NewStandardLogger(os.Stdout),
|
||||
auth: a,
|
||||
}
|
||||
|
||||
testingHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("good"))
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
statusCode int
|
||||
handler http.HandlerFunc
|
||||
key string
|
||||
}{
|
||||
{
|
||||
name: "happyPath",
|
||||
statusCode: http.StatusOK,
|
||||
handler: h.chkInternal(testingHandler),
|
||||
key: authKey,
|
||||
},
|
||||
{
|
||||
name: "unhappyPath-empty",
|
||||
statusCode: http.StatusUnauthorized,
|
||||
handler: h.chkInternal(testingHandler),
|
||||
key: "",
|
||||
},
|
||||
{
|
||||
name: "unhappyPath-wrong",
|
||||
statusCode: http.StatusUnauthorized,
|
||||
handler: h.chkInternal(testingHandler),
|
||||
key: "BEABBEEFBEABBEEFBEABBEEFBEABBEEFBEABBEEFBEABBEEFBEABBEEFBEABBEEF",
|
||||
},
|
||||
}
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/whatever", nil)
|
||||
if test.key != "" {
|
||||
r.Header.Add("X-Feature-Key", test.key)
|
||||
}
|
||||
test.handler(w, r)
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != test.statusCode {
|
||||
t.Fatalf("expected %v, got %v", test.statusCode, resp.StatusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func NewTestAuth(t *testing.T) *authn.Auth {
|
||||
t.Helper()
|
||||
var (
|
||||
ClientID = "e9088663-eb08-41d7-8f65-efb5f54bbb71"
|
||||
ClientSecret = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
|
||||
AuthorizeURL = "https://login.microsoftonline.com/4a137d66-d161-4ae4-b1e6-07e9920874b8/oauth2/v2.0/authorize"
|
||||
TokenURL = "https://login.microsoftonline.com/4a137d66-d161-4ae4-b1e6-07e9920874b8/oauth2/v2.0/token"
|
||||
GroupEndpointURL = "https://graph.microsoft.com/v1.0/me/transitiveMemberOf/microsoft.graph.group?$count=true"
|
||||
LogoutURL = "https://login.microsoftonline.com/common/oauth2/v2.0/logout"
|
||||
Scopes = []string{"https://graph.microsoft.com/.default", "offline_access"}
|
||||
Key = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
|
||||
)
|
||||
|
||||
a, err := authn.NewAuth(
|
||||
logger.NewStandardLogger(os.Stdout),
|
||||
"http://localhost:10101/",
|
||||
Scopes,
|
||||
AuthorizeURL,
|
||||
TokenURL,
|
||||
GroupEndpointURL,
|
||||
LogoutURL,
|
||||
ClientID,
|
||||
ClientSecret,
|
||||
Key,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("building auth object%s", err)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
package server_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
|
@ -1427,6 +1428,47 @@ func TestCRUDIndexes(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestLogQuery(t *testing.T) {
|
||||
method := "test!"
|
||||
uinfo := authn.UserInfo{
|
||||
UserID: "ID",
|
||||
UserName: "name",
|
||||
}
|
||||
ctx := context.WithValue(context.Background(), "userinfo", &uinfo)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
req interface{}
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "nonQueryReq",
|
||||
req: "nope",
|
||||
expected: fmt.Sprintf("GRPC: %v, %v, %v, %v, %v\n", "", []string{}, "test!", uinfo.UserID, uinfo.UserName),
|
||||
},
|
||||
{
|
||||
name: "QuerySQLReq",
|
||||
req: &pb.QuerySQLRequest{Sql: "show fields from table"},
|
||||
expected: fmt.Sprintf("GRPC: %v, %v, %v, %v, %v, %v\n", "", []string{}, "test!", uinfo.UserID, uinfo.UserName, "show fields from table"),
|
||||
},
|
||||
{
|
||||
name: "QueryPQLReq",
|
||||
req: &pb.QueryPQLRequest{Pql: "Count(All())"},
|
||||
expected: fmt.Sprintf("GRPC: %v, %v, %v, %v, %v, %v\n", "", []string{}, "test!", uinfo.UserID, uinfo.UserName, "Count(All())"),
|
||||
},
|
||||
}
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
l := logger.NewStandardLogger(buf)
|
||||
server.LogQuery(ctx, method, test.req, l)
|
||||
if !strings.HasSuffix(buf.String(), test.expected) {
|
||||
t.Errorf("expected '%v', got '%v'", test.expected, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setUpTestQuerySQLUnary(ctx context.Context, t *testing.T) (gh *server.GRPCHandler, tearDownFunc func()) {
|
||||
t.Helper()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue