mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 02:44:59 +00:00
Stop using string keys in contexts
This fixes the OriginalIP and RequestUserID in the main featurebase
package, and the Access and Refresh tokens, the UserInfo, and the
[]string of Indexes passed with context.Context(s) in the authn package.
An empty struct was used for all of these keys (and relevant helper
functions we added) to avoid allocations where possible while still
using the context functionality.
Some of the logic in the server.GetIndexes function was fixed.
(cherry picked from commit 0f5a56c958)
This commit is contained in:
parent
202501f7bf
commit
ab2b48da0d
19 changed files with 178 additions and 162 deletions
12
api.go
12
api.go
|
|
@ -218,10 +218,7 @@ func (api *API) CreateIndex(ctx context.Context, indexName string, options Index
|
|||
|
||||
// get the requestUserID from the context -- assumes the http handler has populated this from
|
||||
// authN/Z info
|
||||
requestUserID, ok := ctx.Value(ContextRequestUserIdKey).(string)
|
||||
if !ok {
|
||||
requestUserID = ""
|
||||
}
|
||||
requestUserID, _ := UserIDFromContext(ctx) // requestUserID is "" if not in ctx
|
||||
|
||||
if err := api.validate(apiCreateIndex); err != nil {
|
||||
return nil, errors.Wrap(err, "validating api method")
|
||||
|
|
@ -310,10 +307,7 @@ func (api *API) CreateField(ctx context.Context, indexName string, fieldName str
|
|||
|
||||
// get the requestUserID from the context -- assumes the http handler has populated this from
|
||||
// authN/Z info
|
||||
requestUserID, ok := ctx.Value(ContextRequestUserIdKey).(string)
|
||||
if !ok {
|
||||
requestUserID = ""
|
||||
}
|
||||
requestUserID, _ := UserIDFromContext(ctx) // requestUserID is "" if not in ctx
|
||||
|
||||
// Apply and validate functional options.
|
||||
fo, err := newFieldOptions(opts...)
|
||||
|
|
@ -369,7 +363,7 @@ func (api *API) UpdateField(ctx context.Context, indexName, fieldName string, up
|
|||
|
||||
// get the requestUserID from the context -- assumes the http handler has populated this from
|
||||
// authN/Z info
|
||||
requestUserID, _ := ctx.Value(ContextRequestUserIdKey).(string)
|
||||
requestUserID, _ := UserIDFromContext(ctx)
|
||||
|
||||
cfm, err := index.UpdateField(ctx, fieldName, requestUserID, update)
|
||||
if err != nil {
|
||||
|
|
|
|||
18
api_test.go
18
api_test.go
|
|
@ -1403,23 +1403,11 @@ func TestAuth_MultiNode(t *testing.T) {
|
|||
"test": "write"
|
||||
admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
||||
adminUser := makeUser(t, []authn.Group{{GroupID: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe", GroupName: "adminGroup"}}, "admin", "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
|
||||
adminCtx := context.WithValue(
|
||||
context.Background(),
|
||||
"userinfo",
|
||||
adminUser,
|
||||
)
|
||||
adminCtx := authn.WithUserInfo(context.Background(), adminUser)
|
||||
readUser := makeUser(t, []authn.Group{{GroupID: "dca35310-ecda-4f23-86cd-876aee55906b", GroupName: "readGroup"}}, "reader", "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
|
||||
readCtx := context.WithValue(
|
||||
context.Background(),
|
||||
"userinfo",
|
||||
readUser,
|
||||
)
|
||||
readCtx := authn.WithUserInfo(context.Background(), readUser)
|
||||
writeUser := makeUser(t, []authn.Group{{GroupID: "dca35310-ecda-4f23-86cd-876aee55906f", GroupName: "writeGroup"}}, "writer", "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEED")
|
||||
writeCtx := context.WithValue(
|
||||
context.Background(),
|
||||
"userinfo",
|
||||
writeUser,
|
||||
)
|
||||
writeCtx := authn.WithUserInfo(context.Background(), writeUser)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
token, ok := r.Header["Authorization"]
|
||||
if !ok || len(token) == 0 {
|
||||
|
|
|
|||
54
authn/context.go
Normal file
54
authn/context.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2022 Molecula Corp (DBA FeatureBase). All rights reserved.
|
||||
package authn
|
||||
|
||||
import "context"
|
||||
|
||||
// Empty struct to avoid allocations
|
||||
type contextKeyAccessToken struct{}
|
||||
type contextKeyRefreshToken struct{}
|
||||
type contextKeyUserInfo struct{}
|
||||
type contextKeyIndexes struct{}
|
||||
|
||||
// GetAccessToken gets the access token from a context.
|
||||
func GetAccessToken(ctx context.Context) (token string, ok bool) {
|
||||
token, ok = ctx.Value(contextKeyAccessToken{}).(string)
|
||||
return
|
||||
}
|
||||
|
||||
// WithAccessToken makes a new Context with an access token.
|
||||
func WithAccessToken(ctx context.Context, token string) context.Context {
|
||||
return context.WithValue(ctx, contextKeyAccessToken{}, token)
|
||||
}
|
||||
|
||||
// GetRefreshToken gets the refresh token from a context.
|
||||
func GetRefreshToken(ctx context.Context) (token string, ok bool) {
|
||||
token, ok = ctx.Value(contextKeyRefreshToken{}).(string)
|
||||
return
|
||||
}
|
||||
|
||||
// WithRefreshToken makes a new Context with a refresh token.
|
||||
func WithRefreshToken(ctx context.Context, token string) context.Context {
|
||||
return context.WithValue(ctx, contextKeyRefreshToken{}, token)
|
||||
}
|
||||
|
||||
// GetUserInfo gets the UserInfo from a context.
|
||||
func GetUserInfo(ctx context.Context) (userInfo *UserInfo, ok bool) {
|
||||
userInfo, ok = ctx.Value(contextKeyUserInfo{}).(*UserInfo)
|
||||
return
|
||||
}
|
||||
|
||||
// WithUserInfo makes a new Context with UserInfo.
|
||||
func WithUserInfo(ctx context.Context, userInfo *UserInfo) context.Context {
|
||||
return context.WithValue(ctx, contextKeyUserInfo{}, userInfo)
|
||||
}
|
||||
|
||||
// GetIndexes get the indices from a context.
|
||||
func GetIndexes(ctx context.Context) (indexes []string, ok bool) {
|
||||
indexes, ok = ctx.Value(contextKeyIndexes{}).([]string)
|
||||
return
|
||||
}
|
||||
|
||||
// WithIndexes makes a new Context with a []string containing the indicies.
|
||||
func WithIndexes(ctx context.Context, indexes []string) context.Context {
|
||||
return context.WithValue(ctx, contextKeyUserInfo{}, indexes)
|
||||
}
|
||||
28
context.go
Normal file
28
context.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2022 Molecula Corp (DBA FeatureBase). All rights reserved.
|
||||
package pilosa
|
||||
|
||||
import "context"
|
||||
|
||||
// Empty struct to avoid allocations
|
||||
type contextKeyOriginalIP struct{}
|
||||
type contextKeyRequestUserID struct{}
|
||||
|
||||
// OriginalIPFromContext gets the original IP from the context.
|
||||
func OriginalIPFromContext(ctx context.Context) (originalIP string, ok bool) {
|
||||
originalIP, ok = ctx.Value(contextKeyOriginalIP{}).(string)
|
||||
return
|
||||
}
|
||||
|
||||
// WithOriginalIP makes a new context with the originalIP in the context.
|
||||
func WithOriginalIP(ctx context.Context, originalIP string) context.Context {
|
||||
return context.WithValue(ctx, contextKeyOriginalIP{}, originalIP)
|
||||
}
|
||||
|
||||
func UserIDFromContext(ctx context.Context) (userID string, ok bool) {
|
||||
userID, ok = ctx.Value(contextKeyRequestUserID{}).(string)
|
||||
return
|
||||
}
|
||||
|
||||
func WithUserID(ctx context.Context, userID string) context.Context {
|
||||
return context.WithValue(ctx, contextKeyRequestUserID{}, userID)
|
||||
}
|
||||
|
|
@ -113,11 +113,7 @@ func (cmd *BackupCommand) Run(ctx context.Context) (err error) {
|
|||
cmd.client = client
|
||||
|
||||
if cmd.AuthToken != "" {
|
||||
ctx = context.WithValue(
|
||||
ctx,
|
||||
authn.ContextValueAccessToken,
|
||||
"Bearer "+cmd.AuthToken,
|
||||
)
|
||||
ctx = authn.WithAccessToken(ctx, "Bearer "+cmd.AuthToken)
|
||||
}
|
||||
|
||||
// Determine the field type in order to correctly handle the input data.
|
||||
|
|
|
|||
|
|
@ -102,11 +102,7 @@ func (cmd *BackupTarCommand) Run(ctx context.Context) (err error) {
|
|||
cmd.client = client
|
||||
|
||||
if cmd.AuthToken != "" {
|
||||
ctx = context.WithValue(
|
||||
ctx,
|
||||
authn.ContextValueAccessToken,
|
||||
"Bearer "+cmd.AuthToken,
|
||||
)
|
||||
ctx = authn.WithAccessToken(ctx, "Bearer "+cmd.AuthToken)
|
||||
}
|
||||
|
||||
// Determine the field type in order to correctly handle the input data.
|
||||
|
|
|
|||
|
|
@ -94,11 +94,7 @@ func (cmd *ImportCommand) Run(ctx context.Context) error {
|
|||
cmd.client = client
|
||||
|
||||
if cmd.AuthToken != "" {
|
||||
ctx = context.WithValue(
|
||||
ctx,
|
||||
authn.ContextValueAccessToken,
|
||||
"Bearer "+cmd.AuthToken,
|
||||
)
|
||||
ctx = authn.WithAccessToken(ctx, "Bearer "+cmd.AuthToken)
|
||||
}
|
||||
|
||||
if cmd.CreateSchema {
|
||||
|
|
|
|||
|
|
@ -722,11 +722,7 @@ func TestImport_AuthOn(t *testing.T) {
|
|||
cm.Field = test.Field
|
||||
cm.CreateSchema = test.CreateSchema
|
||||
cm.Paths = []string{file.Name()}
|
||||
ctx := context.WithValue(
|
||||
context.Background(),
|
||||
authn.ContextValueAccessToken,
|
||||
test.Token,
|
||||
)
|
||||
ctx := authn.WithAccessToken(context.Background(), test.Token)
|
||||
err = cm.Run(ctx)
|
||||
if test.Err != nil {
|
||||
if !strings.Contains(err.Error(), test.Err.Error()) {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ func (cmd *RestoreCommand) Run(ctx context.Context) (err error) {
|
|||
cmd.client = client
|
||||
|
||||
if cmd.AuthToken != "" {
|
||||
ctx = context.WithValue(ctx, authn.ContextValueAccessToken, "Bearer "+cmd.AuthToken)
|
||||
ctx = authn.WithAccessToken(ctx, "Bearer "+cmd.AuthToken)
|
||||
}
|
||||
|
||||
nodes, err := cmd.client.Nodes(ctx)
|
||||
|
|
@ -157,7 +157,7 @@ func (cmd *RestoreCommand) restoreSchema(ctx context.Context, primary *disco.Nod
|
|||
req = req.WithContext(ctx)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
token, ok := ctx.Value(authn.ContextValueAccessToken).(string)
|
||||
token, ok := authn.GetAccessToken(ctx)
|
||||
if ok && token != "" {
|
||||
req.Header.Set("Authorization", token)
|
||||
}
|
||||
|
|
@ -327,7 +327,7 @@ func (cmd *RestoreCommand) restoreShard(ctx context.Context, filename string) er
|
|||
req = req.WithContext(ctx)
|
||||
req.Header.Set("Content-Type", "application/octet-stream")
|
||||
|
||||
token, ok := ctx.Value(authn.ContextValueAccessToken).(string)
|
||||
token, ok := authn.GetAccessToken(ctx)
|
||||
if ok && token != "" {
|
||||
req.Header.Set("Authorization", token)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ func (cmd *RestoreTarCommand) Run(ctx context.Context) (err error) {
|
|||
cmd.client = client
|
||||
|
||||
if cmd.AuthToken != "" {
|
||||
ctx = context.WithValue(ctx, authn.ContextValueAccessToken, "Bearer "+cmd.AuthToken)
|
||||
ctx = authn.WithAccessToken(ctx, "Bearer "+cmd.AuthToken)
|
||||
}
|
||||
|
||||
var tarReader *tar.Reader
|
||||
|
|
|
|||
|
|
@ -49,13 +49,6 @@ import (
|
|||
"github.com/zeebo/blake3"
|
||||
)
|
||||
|
||||
type ContextRequestUserIdKeyType string
|
||||
|
||||
const (
|
||||
// ContextRequestUserIdKey is request userid key for a request ctx
|
||||
ContextRequestUserIdKey = ContextRequestUserIdKeyType("request-user-id")
|
||||
)
|
||||
|
||||
const (
|
||||
// HeaderRequestUserID is request userid header
|
||||
HeaderRequestUserID = "X-Request-Userid"
|
||||
|
|
@ -692,7 +685,7 @@ func (h *Handler) chkAllowedNetworks(r *http.Request) (bool, context.Context) {
|
|||
// if client IP is in allowed networks
|
||||
// add it to the context for key X-Molecula-Original-IP
|
||||
if h.auth.CheckAllowedNetworks(reqIP) {
|
||||
ctx := context.WithValue(r.Context(), OriginalIPHeader, reqIP)
|
||||
ctx := WithOriginalIP(r.Context(), reqIP)
|
||||
return true, ctx
|
||||
}
|
||||
return false, r.Context()
|
||||
|
|
@ -704,7 +697,7 @@ func (h *Handler) chkAuthN(handler http.HandlerFunc) http.HandlerFunc {
|
|||
|
||||
//if the request is unauthenticated and we have the appropriate header get the userid from the header
|
||||
requestUserID := r.Header.Get(HeaderRequestUserID)
|
||||
ctx = context.WithValue(ctx, ContextRequestUserIdKey, requestUserID)
|
||||
ctx = WithUserID(ctx, requestUserID)
|
||||
|
||||
if h.auth == nil {
|
||||
handler.ServeHTTP(w, r.WithContext(ctx))
|
||||
|
|
@ -724,12 +717,13 @@ func (h *Handler) chkAuthN(handler http.HandlerFunc) http.HandlerFunc {
|
|||
http.Error(w, errors.Wrap(err, "authenticating").Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// prefer the user id from an authenticated request over one in a header
|
||||
ctx = context.WithValue(ctx, ContextRequestUserIdKey, uinfo.UserID)
|
||||
ctx = WithUserID(ctx, uinfo.UserID)
|
||||
|
||||
// just in case it got refreshed
|
||||
ctx = context.WithValue(ctx, authn.ContextValueAccessToken, "Bearer "+access)
|
||||
ctx = context.WithValue(ctx, authn.ContextValueRefreshToken, refresh)
|
||||
ctx = authn.WithAccessToken(ctx, "Bearer"+access)
|
||||
ctx = authn.WithRefreshToken(ctx, refresh)
|
||||
h.auth.SetCookie(w, uinfo.Token, uinfo.RefreshToken, uinfo.Expiry)
|
||||
|
||||
handler.ServeHTTP(w, r.WithContext(ctx))
|
||||
|
|
@ -742,7 +736,7 @@ func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http
|
|||
|
||||
//if the request is unauthenticated and we have the appropriate header get the userid from the header
|
||||
requestUserID := r.Header.Get(HeaderRequestUserID)
|
||||
ctx = context.WithValue(ctx, ContextRequestUserIdKey, requestUserID)
|
||||
ctx = WithUserID(ctx, requestUserID)
|
||||
|
||||
// handle the case when auth is not turned on
|
||||
if h.auth == nil {
|
||||
|
|
@ -771,18 +765,18 @@ func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http
|
|||
}
|
||||
|
||||
// prefer the user id from an authenticated request over one in a header
|
||||
ctx = context.WithValue(ctx, ContextRequestUserIdKey, uinfo.UserID)
|
||||
ctx = WithUserID(ctx, uinfo.UserID)
|
||||
|
||||
ctx = context.WithValue(ctx, authn.ContextValueAccessToken, "Bearer "+access)
|
||||
ctx = context.WithValue(ctx, authn.ContextValueRefreshToken, refresh)
|
||||
ctx = authn.WithAccessToken(ctx, "Bearer "+access)
|
||||
ctx = authn.WithRefreshToken(ctx, refresh)
|
||||
|
||||
// just in case it got refreshed
|
||||
h.auth.SetCookie(w, uinfo.Token, uinfo.RefreshToken, uinfo.Expiry)
|
||||
|
||||
// put the user's authN/Z info in the context
|
||||
ctx = context.WithValue(ctx, contextKeyGroupMembership, uinfo.Groups)
|
||||
ctx = context.WithValue(ctx, authn.ContextValueAccessToken, "Bearer "+uinfo.Token)
|
||||
ctx = context.WithValue(ctx, authn.ContextValueRefreshToken, uinfo.RefreshToken)
|
||||
ctx = authn.WithAccessToken(ctx, "Bearer "+uinfo.Token)
|
||||
ctx = authn.WithRefreshToken(ctx, uinfo.RefreshToken)
|
||||
// unlikely h.permissions will be nil, but we'll check to be safe
|
||||
if h.permissions == nil {
|
||||
h.logger.Errorf("authentication is turned on without authorization permissions set")
|
||||
|
|
@ -869,7 +863,7 @@ func GetIP(r *http.Request) string {
|
|||
}
|
||||
|
||||
// check if original IP is in the context
|
||||
if ogIP, ok := r.Context().Value(OriginalIPHeader).(string); ok && ogIP != "" {
|
||||
if ogIP, ok := OriginalIPFromContext(r.Context()); ok && ogIP != "" {
|
||||
return ogIP
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,11 +103,7 @@ func TestClusterStuff(t *testing.T) {
|
|||
// generate auth token and add to context
|
||||
if auth {
|
||||
token = GetAuthToken(t)
|
||||
ctx = context.WithValue(
|
||||
ctx,
|
||||
authn.ContextValueAccessToken,
|
||||
"Bearer "+token,
|
||||
)
|
||||
ctx = authn.WithAccessToken(ctx, "Bearer"+token)
|
||||
}
|
||||
|
||||
if err := cli[0].CreateIndex(ctx, "testidx", pilosa.IndexOptions{}); err != nil {
|
||||
|
|
@ -330,11 +326,7 @@ func TestRetryLogic(t *testing.T) {
|
|||
}
|
||||
if auth {
|
||||
token := GetAuthToken(t)
|
||||
ctx = context.WithValue(
|
||||
ctx,
|
||||
authn.ContextValueAccessToken,
|
||||
"Bearer "+token,
|
||||
)
|
||||
ctx = authn.WithAccessToken(ctx, "Bearer "+token)
|
||||
}
|
||||
|
||||
var addrs = []string{"pilosa1:10101", "pilosa2:10101", "pilosa3:10101"}
|
||||
|
|
|
|||
|
|
@ -302,11 +302,7 @@ func TestPauseReplica(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
if auth {
|
||||
token := GetAuthToken(t)
|
||||
ctx = context.WithValue(
|
||||
ctx,
|
||||
authn.ContextValueAccessToken,
|
||||
"Bearer "+token,
|
||||
)
|
||||
ctx = authn.WithAccessToken(ctx, "Bearer "+token)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
|
|
|||
|
|
@ -228,29 +228,29 @@ func NewInternalClientFromURI(defaultURI *pnet.URI, remoteClient *http.Client, o
|
|||
// same for refresh tokens as well.
|
||||
func AddAuthToken(ctx context.Context, header *http.Header) {
|
||||
var access, refresh string
|
||||
if token, ok := ctx.Value(authn.ContextValueAccessToken).(string); ok {
|
||||
if token, ok := authn.GetAccessToken(ctx); ok {
|
||||
// the AccessToken value should be prefixed with "Bearer"
|
||||
access = token
|
||||
}
|
||||
if token, ok := ctx.Value(authn.ContextValueRefreshToken).(string); ok {
|
||||
if token, ok := authn.GetRefreshToken(ctx); ok {
|
||||
refresh = token
|
||||
}
|
||||
|
||||
// not combining these ifs so we don't call ctx.Value unless we have to
|
||||
if access == "" || refresh == "" {
|
||||
if uinfo := ctx.Value("userinfo"); uinfo != nil {
|
||||
if uinfo, _ := authn.GetUserInfo(ctx); uinfo != nil {
|
||||
if access == "" {
|
||||
// UserInfo.Token is not prefixed with "Bearer"
|
||||
access = "Bearer " + uinfo.(*authn.UserInfo).Token
|
||||
access = "Bearer " + uinfo.Token
|
||||
}
|
||||
if refresh == "" {
|
||||
refresh = uinfo.(*authn.UserInfo).RefreshToken
|
||||
refresh = uinfo.RefreshToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set ogIP to request for remote calls
|
||||
if ogIP, ok := ctx.Value(OriginalIPHeader).(string); ok && ogIP != "" {
|
||||
if ogIP, ok := OriginalIPFromContext(ctx); ok && ogIP != "" {
|
||||
header.Set(OriginalIPHeader, ogIP)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1608,7 +1608,7 @@ func TestAddAuthToken(t *testing.T) {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
uinfo := &authn.UserInfo{Token: "ayo"}
|
||||
pilosa.AddAuthToken(context.WithValue(context.Background(), "userinfo", uinfo), &req.Header)
|
||||
pilosa.AddAuthToken(authn.WithUserInfo(context.Background(), uinfo), &req.Header)
|
||||
if got := req.Header.Get("Authorization"); got != "Bearer "+uinfo.Token {
|
||||
t.Fatalf("got '%v', expected 'Bearer %v'", got, uinfo.Token)
|
||||
}
|
||||
|
|
@ -1620,10 +1620,7 @@ func TestAddAuthToken(t *testing.T) {
|
|||
}
|
||||
tok := "Bearer thisisatoken"
|
||||
pilosa.AddAuthToken(
|
||||
context.WithValue(context.Background(),
|
||||
authn.ContextValueAccessToken,
|
||||
tok,
|
||||
),
|
||||
authn.WithAccessToken(context.Background(), tok),
|
||||
&req.Header,
|
||||
)
|
||||
if got := req.Header.Get("Authorization"); got != tok {
|
||||
|
|
@ -1636,7 +1633,7 @@ func TestAddAuthToken(t *testing.T) {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
ogIP := "10.0.0.1"
|
||||
pilosa.AddAuthToken(context.WithValue(context.Background(), pilosa.OriginalIPHeader, ogIP), &req.Header)
|
||||
pilosa.AddAuthToken(pilosa.WithOriginalIP(context.Background(), ogIP), &req.Header)
|
||||
if got := req.Header.Get(pilosa.OriginalIPHeader); got != ogIP {
|
||||
t.Fatalf("got '%v', expected '%v'", got, ogIP)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,8 +166,7 @@ func isAllowed(requested []string, allowed []string) bool {
|
|||
// QuerySQL handles the SQL request and sends RowResponses to the stream.
|
||||
func (h *GRPCHandler) QuerySQL(req *pb.QuerySQLRequest, stream pb.Pilosa_QuerySQLServer) error {
|
||||
ctx := stream.Context()
|
||||
uinfo, ok := ctx.Value("userinfo").(*authn.UserInfo)
|
||||
if ok && uinfo != nil {
|
||||
if uinfo, ok := authn.GetUserInfo(ctx); ok && uinfo != nil {
|
||||
// authz
|
||||
m := sql.NewMapper()
|
||||
parsed, err := m.MapSQL(req.Sql)
|
||||
|
|
@ -186,7 +185,7 @@ func (h *GRPCHandler) QuerySQL(req *pb.QuerySQLRequest, stream pb.Pilosa_QuerySQ
|
|||
if !isAllowed(parsed.Tables, allowed) {
|
||||
return status.Error(codes.PermissionDenied, "insufficient permissions to access requested tables")
|
||||
}
|
||||
ctx = context.WithValue(ctx, "indices", allowed)
|
||||
ctx = authn.WithIndexes(ctx, allowed)
|
||||
}
|
||||
LogQuery(ctx, "QuerySQL", req, h.queryLogger)
|
||||
}
|
||||
|
|
@ -230,8 +229,7 @@ func (h *GRPCHandler) QuerySQL(req *pb.QuerySQLRequest, stream pb.Pilosa_QuerySQ
|
|||
// https://github.com/molecula/pilosa/pull/644
|
||||
func (h *GRPCHandler) QuerySQLUnary(ctx context.Context, req *pb.QuerySQLRequest) (*pb.TableResponse, error) {
|
||||
start := time.Now()
|
||||
uinfo := ctx.Value("userinfo")
|
||||
if uinfo != nil {
|
||||
if uinfo, _ := authn.GetUserInfo(ctx); uinfo != nil {
|
||||
// authz
|
||||
m := sql.NewMapper()
|
||||
parsed, err := m.MapSQL(req.Sql)
|
||||
|
|
@ -245,12 +243,12 @@ func (h *GRPCHandler) QuerySQLUnary(ctx context.Context, req *pb.QuerySQLRequest
|
|||
perm = authz.Admin
|
||||
}
|
||||
|
||||
allowed := h.perms.GetAuthorizedIndexList(uinfo.(*authn.UserInfo).Groups, perm)
|
||||
if !h.perms.IsAdmin(uinfo.(*authn.UserInfo).Groups) {
|
||||
allowed := h.perms.GetAuthorizedIndexList(uinfo.Groups, perm)
|
||||
if !h.perms.IsAdmin(uinfo.Groups) {
|
||||
if !isAllowed(parsed.Tables, allowed) {
|
||||
return nil, status.Error(codes.PermissionDenied, "insufficient permissions to access requested tables")
|
||||
}
|
||||
ctx = context.WithValue(ctx, "indices", allowed)
|
||||
ctx = authn.WithIndexes(ctx, allowed)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -289,8 +287,7 @@ func (h *GRPCHandler) QueryPQL(req *pb.QueryPQLRequest, stream pb.Pilosa_QueryPQ
|
|||
}
|
||||
|
||||
ctx := stream.Context()
|
||||
uinfo := ctx.Value("userinfo")
|
||||
if uinfo != nil {
|
||||
if uinfo, _ := authn.GetUserInfo(ctx); uinfo != nil {
|
||||
lperm := authz.Read
|
||||
q, err := pql.ParseString(req.Pql)
|
||||
if err != nil {
|
||||
|
|
@ -299,8 +296,8 @@ func (h *GRPCHandler) QueryPQL(req *pb.QueryPQLRequest, stream pb.Pilosa_QueryPQ
|
|||
if q.WriteCallN() > 0 {
|
||||
lperm = authz.Write
|
||||
}
|
||||
if !h.perms.IsAdmin(uinfo.(*authn.UserInfo).Groups) {
|
||||
if !isAllowed([]string{req.Index}, h.perms.GetAuthorizedIndexList(uinfo.(*authn.UserInfo).Groups, lperm)) {
|
||||
if !h.perms.IsAdmin(uinfo.Groups) {
|
||||
if !isAllowed([]string{req.Index}, h.perms.GetAuthorizedIndexList(uinfo.Groups, lperm)) {
|
||||
return status.Error(codes.PermissionDenied, "insufficient permissions to access requested indexes")
|
||||
}
|
||||
}
|
||||
|
|
@ -358,8 +355,7 @@ func (h *GRPCHandler) QueryPQLUnary(ctx context.Context, req *pb.QueryPQLRequest
|
|||
Index: req.Index,
|
||||
Query: req.Pql,
|
||||
}
|
||||
uinfo := ctx.Value("userinfo")
|
||||
if uinfo != nil {
|
||||
if uinfo, _ := authn.GetUserInfo(ctx); uinfo != nil {
|
||||
lperm := authz.Read
|
||||
q, err := pql.ParseString(req.Pql)
|
||||
if err != nil {
|
||||
|
|
@ -368,8 +364,8 @@ func (h *GRPCHandler) QueryPQLUnary(ctx context.Context, req *pb.QueryPQLRequest
|
|||
if q.WriteCallN() > 0 {
|
||||
lperm = authz.Write
|
||||
}
|
||||
if !h.perms.IsAdmin(uinfo.(*authn.UserInfo).Groups) {
|
||||
if !isAllowed([]string{req.Index}, h.perms.GetAuthorizedIndexList(uinfo.(*authn.UserInfo).Groups, lperm)) {
|
||||
if !h.perms.IsAdmin(uinfo.Groups) {
|
||||
if !isAllowed([]string{req.Index}, h.perms.GetAuthorizedIndexList(uinfo.Groups, lperm)) {
|
||||
return nil, status.Error(codes.PermissionDenied, fmt.Sprintf("insufficient permissions for %v", req.Index))
|
||||
}
|
||||
}
|
||||
|
|
@ -419,9 +415,8 @@ func (h *GRPCHandler) QueryPQLUnary(ctx context.Context, req *pb.QueryPQLRequest
|
|||
|
||||
// CreateIndex creates a new Index
|
||||
func (h *GRPCHandler) CreateIndex(ctx context.Context, req *pb.CreateIndexRequest) (*pb.CreateIndexResponse, error) {
|
||||
uinfo := ctx.Value("userinfo")
|
||||
if uinfo != nil {
|
||||
if !h.perms.IsAdmin(uinfo.(*authn.UserInfo).Groups) {
|
||||
if uinfo, _ := authn.GetUserInfo(ctx); uinfo != nil {
|
||||
if !h.perms.IsAdmin(uinfo.Groups) {
|
||||
return nil, status.Error(codes.PermissionDenied, "must be admin to create index")
|
||||
}
|
||||
}
|
||||
|
|
@ -439,13 +434,11 @@ func (h *GRPCHandler) CreateIndex(ctx context.Context, req *pb.CreateIndexReques
|
|||
|
||||
// GetIndex returns a single Index given a name
|
||||
func (h *GRPCHandler) GetIndex(ctx context.Context, req *pb.GetIndexRequest) (*pb.GetIndexResponse, error) {
|
||||
uinfo := ctx.Value("userinfo")
|
||||
if uinfo != nil {
|
||||
pp, ok := uinfo.(*authn.UserInfo)
|
||||
if uinfo, ok := authn.GetUserInfo(ctx); uinfo != nil {
|
||||
if !ok {
|
||||
return nil, status.Error(codes.InvalidArgument, "malformed auth header")
|
||||
}
|
||||
p, err := h.perms.GetPermissions(pp, req.Name)
|
||||
p, err := h.perms.GetPermissions(uinfo, req.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -471,14 +464,9 @@ func (h *GRPCHandler) GetIndex(ctx context.Context, req *pb.GetIndexRequest) (*p
|
|||
|
||||
// GetIndexes returns a list of all Indexes
|
||||
func (h *GRPCHandler) GetIndexes(ctx context.Context, req *pb.GetIndexesRequest) (*pb.GetIndexesResponse, error) {
|
||||
uinfo := ctx.Value("userinfo")
|
||||
var userInfo *authn.UserInfo
|
||||
if uinfo != nil {
|
||||
var ok bool
|
||||
userInfo, ok = uinfo.(*authn.UserInfo)
|
||||
if !ok {
|
||||
return nil, status.Error(codes.InvalidArgument, "malformed auth header")
|
||||
}
|
||||
userInfo, ok := authn.GetUserInfo(ctx)
|
||||
if !ok {
|
||||
return nil, status.Error(codes.InvalidArgument, "malformed auth header")
|
||||
}
|
||||
schema, err := h.api.Schema(ctx, false)
|
||||
if err != nil {
|
||||
|
|
@ -505,9 +493,8 @@ func (h *GRPCHandler) GetIndexes(ctx context.Context, req *pb.GetIndexesRequest)
|
|||
|
||||
// DeleteIndex deletes an Index
|
||||
func (h *GRPCHandler) DeleteIndex(ctx context.Context, req *pb.DeleteIndexRequest) (*pb.DeleteIndexResponse, error) {
|
||||
uinfo := ctx.Value("userinfo")
|
||||
if uinfo != nil {
|
||||
if !h.perms.IsAdmin(uinfo.(*authn.UserInfo).Groups) {
|
||||
if uinfo, _ := authn.GetUserInfo(ctx); uinfo != nil {
|
||||
if !h.perms.IsAdmin(uinfo.Groups) {
|
||||
return nil, status.Error(codes.PermissionDenied, "must be admin to delete index")
|
||||
}
|
||||
}
|
||||
|
|
@ -742,8 +729,7 @@ func (h *GRPCHandler) Inspect(req *pb.InspectRequest, stream pb.Pilosa_InspectSe
|
|||
})
|
||||
|
||||
ctx := stream.Context()
|
||||
uinfo := ctx.Value("userinfo")
|
||||
if uinfo != nil {
|
||||
if uinfo, _ := authn.GetUserInfo(ctx); uinfo != nil {
|
||||
LogQuery(stream.Context(), "Inspect", req, h.queryLogger)
|
||||
}
|
||||
|
||||
|
|
@ -1628,7 +1614,7 @@ func NewGRPCServer(opts ...grpcServerOption) (*grpcServer, error) {
|
|||
|
||||
// reset the molecula-chip cookie just in case the token was refreshed
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if uinfo, yeah := ctx.Value("userinfo").(*authn.UserInfo); ok && yeah {
|
||||
if uinfo, yeah := authn.GetUserInfo(ctx); ok && yeah {
|
||||
server.auth.SetGRPCMetadata(ctx, md, uinfo.Token, uinfo.RefreshToken)
|
||||
}
|
||||
return handler(ctx, req)
|
||||
|
|
@ -1640,7 +1626,7 @@ func NewGRPCServer(opts ...grpcServerOption) (*grpcServer, error) {
|
|||
}
|
||||
// reset the molecula-chip cookie just in case the token was refreshed
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if uinfo, yeah := ctx.Value("userinfo").(*authn.UserInfo); ok && yeah {
|
||||
if uinfo, yeah := authn.GetUserInfo(ctx); ok && yeah {
|
||||
server.auth.SetGRPCMetadata(ctx, md, uinfo.Token, uinfo.RefreshToken)
|
||||
}
|
||||
return handler(srv, &wrappedStream{ss, ctx})
|
||||
|
|
@ -1729,7 +1715,7 @@ func Valid(ctx context.Context, auth *authn.Auth) (context.Context, error) {
|
|||
return ctx, status.Errorf(codes.Unauthenticated, err.Error())
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, "userinfo", uinfo), nil
|
||||
return authn.WithUserInfo(ctx, uinfo), nil
|
||||
}
|
||||
|
||||
func getTokensFromMetadata(md metadata.MD) (string, string) {
|
||||
|
|
|
|||
|
|
@ -1184,23 +1184,11 @@ admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
|||
}
|
||||
|
||||
user := makeUser([]authn.Group{{GroupID: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe", GroupName: "adminGroup"}}, "admin")
|
||||
adminCtx := context.WithValue(
|
||||
ctx,
|
||||
"userinfo",
|
||||
user,
|
||||
)
|
||||
adminCtx := authn.WithUserInfo(ctx, user)
|
||||
readuser := makeUser([]authn.Group{{GroupID: "dca35310-ecda-4f23-86cd-876aee55906b", GroupName: "readers"}}, "reader")
|
||||
readCtx := context.WithValue(
|
||||
ctx,
|
||||
"userinfo",
|
||||
readuser,
|
||||
)
|
||||
readCtx := authn.WithUserInfo(ctx, readuser)
|
||||
writeuser := makeUser([]authn.Group{{GroupID: "dca35310-ecda-4f23-86cd-876aee55906f", GroupName: "writers"}}, "admin")
|
||||
writeCtx := context.WithValue(
|
||||
ctx,
|
||||
"userinfo",
|
||||
writeuser,
|
||||
)
|
||||
writeCtx := authn.WithUserInfo(ctx, writeuser)
|
||||
|
||||
sql := "select * from grouper"
|
||||
t.Run("test-auth-with-admin-sqlUnary", func(t *testing.T) {
|
||||
|
|
@ -1537,11 +1525,11 @@ func TestCRUDIndexes(t *testing.T) {
|
|||
|
||||
func TestLogQuery(t *testing.T) {
|
||||
method := "test!"
|
||||
uinfo := authn.UserInfo{
|
||||
uinfo := &authn.UserInfo{
|
||||
UserID: "ID",
|
||||
UserName: "name",
|
||||
}
|
||||
ctx := context.WithValue(context.Background(), "userinfo", &uinfo)
|
||||
ctx := authn.WithUserInfo(context.Background(), uinfo)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
|
|
@ -1963,8 +1951,8 @@ type MockStream struct {
|
|||
context context.Context
|
||||
}
|
||||
|
||||
func (ms MockStream) SetHeader(md metadata.MD) error {
|
||||
ms.context = context.WithValue(context.Background(), "metadata", md)
|
||||
func (ms *MockStream) SetHeader(md metadata.MD) error {
|
||||
ms.context = contextWithMetadata(context.Background(), md)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -1977,7 +1965,7 @@ func (ms MockStream) SetTrailer(metadata.MD) {}
|
|||
func (ms MockStream) Context() context.Context {
|
||||
if ms.context == nil {
|
||||
md := metadata.New(map[string]string{})
|
||||
ms.context = context.WithValue(context.Background(), "metadata", md)
|
||||
ms.context = contextWithMetadata(context.Background(), md)
|
||||
}
|
||||
return ms.context
|
||||
}
|
||||
|
|
@ -1990,21 +1978,34 @@ func (ms MockStream) RecvMsg(m interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func fromIncomingContext(ctx context.Context) metadata.MD {
|
||||
return ctx.Value("metadata").(metadata.MD)
|
||||
type contextKeyMetadata struct{}
|
||||
|
||||
func contextWithMetadata(ctx context.Context, metadata metadata.MD) context.Context {
|
||||
return context.WithValue(ctx, contextKeyMetadata{}, metadata)
|
||||
}
|
||||
|
||||
func metadataFromContext(ctx context.Context) (meta metadata.MD, ok bool) {
|
||||
meta, ok = ctx.Value(contextKeyMetadata{}).(metadata.MD)
|
||||
return
|
||||
}
|
||||
|
||||
func Test_ChainStreamInterceptor(t *testing.T) {
|
||||
|
||||
salt := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
md := fromIncomingContext(ss.Context())
|
||||
md, ok := metadataFromContext(ss.Context())
|
||||
if !ok {
|
||||
t.Fatal("metadata was not in context")
|
||||
}
|
||||
md.Append("ingredient", "with salt")
|
||||
err := ss.SetHeader(md)
|
||||
assert.NoError(t, err)
|
||||
return handler(srv, ss)
|
||||
}
|
||||
pepper := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
md := fromIncomingContext(ss.Context())
|
||||
md, ok := metadataFromContext(ss.Context())
|
||||
if !ok {
|
||||
t.Fatal("metadata was not in context")
|
||||
}
|
||||
md.Append("ingredient", "and pepper")
|
||||
err := ss.SetHeader(md)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -2028,10 +2029,13 @@ func Test_ChainStreamInterceptor(t *testing.T) {
|
|||
result := make([]string, 0)
|
||||
srv := "asdf"
|
||||
md := metadata.New(map[string]string{})
|
||||
ss := MockStream{context: context.WithValue(context.Background(), "metadata", md)}
|
||||
ss := &MockStream{context: contextWithMetadata(context.Background(), md)}
|
||||
info := &grpc.StreamServerInfo{}
|
||||
handler := func(srv interface{}, stream grpc.ServerStream) error {
|
||||
md := fromIncomingContext(stream.Context())
|
||||
md, ok := metadataFromContext(stream.Context())
|
||||
if !ok {
|
||||
t.Fatal("metadata was not in context")
|
||||
}
|
||||
vals := md.Get("ingredient")
|
||||
result = append(result, "Soup")
|
||||
result = append(result, vals...)
|
||||
|
|
|
|||
|
|
@ -177,7 +177,6 @@ func TestHandler_Endpoints(t *testing.T) {
|
|||
})
|
||||
|
||||
i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{})
|
||||
const shard = 0
|
||||
tx0 := holder.Txf().NewWritableQcx()
|
||||
defer tx0.Abort()
|
||||
if f, err := i0.CreateFieldIfNotExists("f1", "", pilosa.OptFieldTypeDefault()); err != nil {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func (s *ShowHandler) execShowTables(ctx context.Context, showStmt *sqlparser.Sh
|
|||
return nil, errors.Wrap(err, "getting schema")
|
||||
}
|
||||
|
||||
allowed, ok := ctx.Value("indices").([]string)
|
||||
allowed, ok := authn.GetIndexes(ctx)
|
||||
|
||||
result := make(pproto.ConstRowser, 0)
|
||||
for _, ii := range indexInfo {
|
||||
|
|
@ -83,7 +83,7 @@ func (s *ShowHandler) execShowTables(ctx context.Context, showStmt *sqlparser.Sh
|
|||
|
||||
func (s *ShowHandler) execShowFields(ctx context.Context, showStmt *sqlparser.Show) (pproto.ToRowser, error) {
|
||||
indexName := showStmt.OnTable.ToViewName().Name.String()
|
||||
allowed, ok := ctx.Value("indices").([]string)
|
||||
allowed, ok := authn.GetIndexes(ctx)
|
||||
if ok {
|
||||
found := false
|
||||
for _, idx := range allowed {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue