Skip to content

Commit

Permalink
feat: Use AuthorizerV1 for authorizing 1.x API requests
Browse files Browse the repository at this point in the history
Setup the AuthorizerV1 in the launcher and pass this to the V1
authentication handler for authorizing 1.x HTTP requests.
  • Loading branch information
stuartcarnie committed Oct 30, 2020
1 parent 52e4c7e commit e49bcb4
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 109 deletions.
36 changes: 26 additions & 10 deletions cmd/influxd/launcher/launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,29 @@ func (m *Launcher) run(ctx context.Context) (err error) {
onboardSvc = tenant.NewOnboardingMetrics(m.reg, onboardSvc, metric.WithSuffix("new")) // with metrics
onboardSvc = tenant.NewOnboardingLogger(m.log.With(zap.String("handler", "onboard")), onboardSvc) // with logging

var (
authorizerV1 platform.AuthorizerV1
passwordV1 platform.PasswordsService
authSvcV1 *authv1.Service
)
{
authStore, err := authv1.NewStore(m.kvStore)
if err != nil {
m.log.Error("Failed creating new authorization store", zap.Error(err))
return err
}

authSvcV1 = authv1.NewService(authStore, ts)
passwordV1 = authv1.NewCachingPasswordsService(authSvcV1)

authorizerV1 = &authv1.Authorizer{
AuthV1: authSvcV1,
AuthV2: authSvc,
Comparer: passwordV1,
User: ts,
}
}

// orgIDResolver is a deprecated type which combines the lookups
// of multiple resources into one type, used to resolve the resources
// associated org ID. It is a stop-gap while we move this behaviour
Expand Down Expand Up @@ -1170,6 +1193,7 @@ func (m *Launcher) run(ctx context.Context) (err error) {
BackupService: backupService,
KVBackupService: m.kvService,
AuthorizationService: authSvc,
AuthorizerV1: authorizerV1,
AlgoWProxy: &http.NoopProxyHandler{},
// Wrap the BucketService in a storage backed one that will ensure deleted buckets are removed from the storage engine.
BucketService: ts.BucketService,
Expand Down Expand Up @@ -1284,21 +1308,13 @@ func (m *Launcher) run(ctx context.Context) (err error) {

var v1AuthHTTPServer *authv1.AuthHandler
{
authStore, err := authv1.NewStore(m.kvStore)
if err != nil {
m.log.Error("Failed creating new authorization store", zap.Error(err))
return err
}
v1AuthSvc := authv1.NewService(authStore, ts)

authLogger := m.log.With(zap.String("handler", "v1_authorization"))

var authService platform.AuthorizationService
authService = authorization.NewAuthedAuthorizationService(v1AuthSvc, ts)
authService = authorization.NewAuthedAuthorizationService(authSvcV1, ts)
authService = authorization.NewAuthLogger(authLogger, authService)

passService := authv1.NewAuthedPasswordService(authv1.AuthFinder(v1AuthSvc), authv1.PasswordService(v1AuthSvc))

passService := authv1.NewAuthedPasswordService(authv1.AuthFinder(authSvcV1), passwordV1)
v1AuthHTTPServer = authv1.NewHTTPAuthHandler(m.log, authService, passService, ts)
}

Expand Down
1 change: 1 addition & 0 deletions http/api_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type APIBackend struct {
BackupService influxdb.BackupService
KVBackupService influxdb.KVBackupService
AuthorizationService influxdb.AuthorizationService
AuthorizerV1 influxdb.AuthorizerV1
OnboardingService influxdb.OnboardingService
DBRPService influxdb.DBRPMappingServiceV2
BucketService influxdb.BucketService
Expand Down
83 changes: 24 additions & 59 deletions http/legacy/influx1x_authentication_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package legacy

import (
"context"
"errors"
"fmt"
"net/http"
"strings"
Expand All @@ -11,21 +12,23 @@ import (
"github.com/opentracing/opentracing-go"
)

type Authorizer interface {
Authorize(ctx context.Context, c influxdb.CredentialsV1) (*influxdb.Authorization, error)
}

type Influx1xAuthenticationHandler struct {
influxdb.HTTPErrorHandler
next http.Handler
auth influxdb.AuthorizationService
user influxdb.UserService
auth Authorizer
}

// NewInflux1xAuthenticationHandler creates an authentication handler to process
// InfluxDB 1.x authentication requests.
func NewInflux1xAuthenticationHandler(next http.Handler, auth influxdb.AuthorizationService, user influxdb.UserService, h influxdb.HTTPErrorHandler) *Influx1xAuthenticationHandler {
func NewInflux1xAuthenticationHandler(next http.Handler, auth Authorizer, h influxdb.HTTPErrorHandler) *Influx1xAuthenticationHandler {
return &Influx1xAuthenticationHandler{
HTTPErrorHandler: h,
next: next,
auth: auth,
user: user,
}
}

Expand All @@ -44,37 +47,17 @@ func (h *Influx1xAuthenticationHandler) ServeHTTP(w http.ResponseWriter, r *http
return
}

auth, err := h.auth.FindAuthorizationByToken(ctx, creds.Token)
auth, err := h.auth.Authorize(ctx, creds)
if err != nil {
unauthorizedError(ctx, h, w)
return
}

var user *influxdb.User
if creds.Username != "" {
user, err = h.user.FindUser(ctx, influxdb.UserFilter{Name: &creds.Username})
if err != nil {
unauthorizedError(ctx, h, w)
return
}

if user.ID != auth.UserID {
h.HandleHTTPError(ctx, &influxdb.Error{
Code: influxdb.EForbidden,
Msg: "Username and Token do not match",
}, w)
return
}
} else {
user, err = h.user.FindUserByID(ctx, auth.UserID)
if err != nil {
unauthorizedError(ctx, h, w)
return
var erri *influxdb.Error
if errors.As(err, &erri) {
switch erri.Code {
case influxdb.EForbidden, influxdb.EUnauthorized:
h.HandleHTTPError(ctx, erri, w)
return
}
}
}

if err = h.isUserActive(user); err != nil {
inactiveUserError(ctx, h, w)
unauthorizedError(ctx, h, w)
return
}

Expand All @@ -87,19 +70,6 @@ func (h *Influx1xAuthenticationHandler) ServeHTTP(w http.ResponseWriter, r *http
h.next.ServeHTTP(w, r.WithContext(ctx))
}

func (h *Influx1xAuthenticationHandler) isUserActive(u *influxdb.User) error {
if u.Status != "inactive" {
return nil
}

return &influxdb.Error{Code: influxdb.EForbidden, Msg: "User is inactive"}
}

type credentials struct {
Username string
Token string
}

func parseToken(token string) (user, pass string, ok bool) {
s := strings.IndexByte(token, ':')
if s < 0 {
Expand All @@ -117,12 +87,13 @@ func parseToken(token string) (user, pass string, ok bool) {
// As params: http://127.0.0.1/query?u=username&p=token
// As basic auth: http://username:token@127.0.0.1
// As Token in Authorization header: Token <username:token>
func (h *Influx1xAuthenticationHandler) parseCredentials(r *http.Request) (*credentials, error) {
func (h *Influx1xAuthenticationHandler) parseCredentials(r *http.Request) (influxdb.CredentialsV1, error) {
q := r.URL.Query()

// Check for username and password in URL params.
if u, p := q.Get("u"), q.Get("p"); u != "" && p != "" {
return &credentials{
return influxdb.CredentialsV1{
Scheme: influxdb.SchemeV1URL,
Username: u,
Token: p,
}, nil
Expand All @@ -136,7 +107,8 @@ func (h *Influx1xAuthenticationHandler) parseCredentials(r *http.Request) (*cred
switch strs[0] {
case "Token":
if u, p, ok := parseToken(strs[1]); ok {
return &credentials{
return influxdb.CredentialsV1{
Scheme: influxdb.SchemeV1Token,
Username: u,
Token: p,
}, nil
Expand All @@ -148,14 +120,15 @@ func (h *Influx1xAuthenticationHandler) parseCredentials(r *http.Request) (*cred

// Check for basic auth.
if u, p, ok := r.BasicAuth(); ok {
return &credentials{
return influxdb.CredentialsV1{
Scheme: influxdb.SchemeV1Basic,
Username: u,
Token: p,
}, nil
}
}

return nil, fmt.Errorf("unable to parse authentication credentials")
return influxdb.CredentialsV1{}, fmt.Errorf("unable to parse authentication credentials")
}

// unauthorizedError encodes a error message and status code for unauthorized access.
Expand All @@ -165,11 +138,3 @@ func unauthorizedError(ctx context.Context, h influxdb.HTTPErrorHandler, w http.
Msg: "unauthorized access",
}, w)
}

// inactiveUserError encode a error message and status code for inactive users.
func inactiveUserError(ctx context.Context, h influxdb.HTTPErrorHandler, w http.ResponseWriter) {
h.HandleHTTPError(ctx, &influxdb.Error{
Code: influxdb.EForbidden,
Msg: "User is inactive",
}, w)
}
71 changes: 32 additions & 39 deletions http/legacy/influx1x_authentication_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import (
"net/http/httptest"
"testing"

"github.com/golang/mock/gomock"
"github.com/influxdata/influxdb/v2"
kithttp "github.com/influxdata/influxdb/v2/kit/transport/http"
"github.com/influxdata/influxdb/v2/mock"
itesting "github.com/influxdata/influxdb/v2/testing"
)

const tokenScheme = "Token " // TODO(goller): I'd like this to be Bearer
Expand All @@ -19,12 +21,10 @@ func setToken(token string, req *http.Request) {
}

func TestInflux1xAuthenticationHandler(t *testing.T) {
var one = influxdb.ID(1)
var userID = itesting.MustIDBase16("0000000000001010")

type fields struct {
FindAuthorizationByTokenFn func(context.Context, string) (*influxdb.Authorization, error)
FindUserFn func(context.Context, influxdb.UserFilter) (*influxdb.User, error)
FindUserByIDFn func(context.Context, influxdb.ID) (*influxdb.User, error)
AuthorizeFn func(ctx context.Context, c influxdb.CredentialsV1) (*influxdb.Authorization, error)
}

type exp struct {
Expand Down Expand Up @@ -103,22 +103,19 @@ func TestInflux1xAuthenticationHandler(t *testing.T) {
{
name: "token does not exist",
fields: fields{
FindAuthorizationByTokenFn: func(ctx context.Context, token string) (*influxdb.Authorization, error) {
return nil, fmt.Errorf("authorization not found")
AuthorizeFn: func(ctx context.Context, c influxdb.CredentialsV1) (*influxdb.Authorization, error) {
return nil, &influxdb.Error{Code: influxdb.EUnauthorized}
},
},
exp: exp{
code: http.StatusUnauthorized,
},
},
{
name: "user is inactive",
name: "authorize returns error EForbidden",
fields: fields{
FindAuthorizationByTokenFn: func(ctx context.Context, token string) (*influxdb.Authorization, error) {
return &influxdb.Authorization{UserID: one}, nil
},
FindUserFn: func(ctx context.Context, f influxdb.UserFilter) (*influxdb.User, error) {
return &influxdb.User{ID: one, Status: "inactive"}, nil
AuthorizeFn: func(ctx context.Context, c influxdb.CredentialsV1) (*influxdb.Authorization, error) {
return nil, &influxdb.Error{Code: influxdb.EForbidden}
},
},
auth: basic(User, Token),
Expand All @@ -127,27 +124,32 @@ func TestInflux1xAuthenticationHandler(t *testing.T) {
},
},
{
name: "username and token mismatch",
name: "authorize returns error EUnauthorized",
fields: fields{
FindAuthorizationByTokenFn: func(ctx context.Context, token string) (*influxdb.Authorization, error) {
return &influxdb.Authorization{UserID: one}, nil
},
FindUserFn: func(ctx context.Context, f influxdb.UserFilter) (*influxdb.User, error) {
return &influxdb.User{ID: influxdb.ID(2)}, nil
AuthorizeFn: func(ctx context.Context, c influxdb.CredentialsV1) (*influxdb.Authorization, error) {
return nil, &influxdb.Error{Code: influxdb.EUnauthorized}
},
},
auth: basic(User, Token),
exp: exp{
code: http.StatusForbidden,
code: http.StatusUnauthorized,
},
},
{
name: "no auth provided",
name: "authorize returns error other",
fields: fields{
FindAuthorizationByTokenFn: func(ctx context.Context, token string) (*influxdb.Authorization, error) {
return &influxdb.Authorization{}, nil
AuthorizeFn: func(ctx context.Context, c influxdb.CredentialsV1) (*influxdb.Authorization, error) {
return nil, &influxdb.Error{Code: influxdb.EInvalid}
},
},
auth: basic(User, Token),
exp: exp{
code: http.StatusUnauthorized,
},
},
{
name: "no auth provided",
fields: fields{},
exp: exp{
code: http.StatusUnauthorized,
},
Expand All @@ -156,31 +158,22 @@ func TestInflux1xAuthenticationHandler(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish)

var h *Influx1xAuthenticationHandler
{
auth := &mock.AuthorizationService{FindAuthorizationByTokenFn: tt.fields.FindAuthorizationByTokenFn}
if auth.FindAuthorizationByTokenFn == nil {
auth.FindAuthorizationByTokenFn = func(ctx context.Context, token string) (*influxdb.Authorization, error) {
return &influxdb.Authorization{UserID: one}, nil
}
}

user := &mock.UserService{FindUserFn: tt.fields.FindUserFn, FindUserByIDFn: tt.fields.FindUserByIDFn}
if user.FindUserFn == nil {
user.FindUserFn = func(context.Context, influxdb.UserFilter) (*influxdb.User, error) {
return &influxdb.User{ID: one}, nil
}
}
if user.FindUserByIDFn == nil {
user.FindUserByIDFn = func(_ context.Context, id influxdb.ID) (*influxdb.User, error) {
return &influxdb.User{ID: id}, nil
auth := &mock.AuthorizerV1{AuthorizeFn: tt.fields.AuthorizeFn}
if auth.AuthorizeFn == nil {
auth.AuthorizeFn = func(ctx context.Context, c influxdb.CredentialsV1) (*influxdb.Authorization, error) {
return &influxdb.Authorization{UserID: userID}, nil
}
}
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})

h = NewInflux1xAuthenticationHandler(next, auth, user, kithttp.ErrorHandler(0))
h = NewInflux1xAuthenticationHandler(next, auth, kithttp.ErrorHandler(0))
}

w := httptest.NewRecorder()
Expand Down
2 changes: 1 addition & 1 deletion http/platform_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func NewPlatformHandler(b *APIBackend, opts ...APIHandlerOptFn) *PlatformHandler
AssetHandler: assetHandler,
DocsHandler: Redoc("/api/v2/swagger.json"),
APIHandler: wrappedHandler,
LegacyHandler: legacy.NewInflux1xAuthenticationHandler(lh, b.AuthorizationService, b.UserService, b.HTTPErrorHandler),
LegacyHandler: legacy.NewInflux1xAuthenticationHandler(lh, b.AuthorizerV1, b.HTTPErrorHandler),
}
}

Expand Down

0 comments on commit e49bcb4

Please sign in to comment.