diff --git a/cmd/influxd/launcher/launcher.go b/cmd/influxd/launcher/launcher.go index 0dafe1a16a5..44909b707c8 100644 --- a/cmd/influxd/launcher/launcher.go +++ b/cmd/influxd/launcher/launcher.go @@ -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 @@ -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, @@ -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) } diff --git a/http/api_handler.go b/http/api_handler.go index ba8f66590d1..505694a3e36 100644 --- a/http/api_handler.go +++ b/http/api_handler.go @@ -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 diff --git a/http/legacy/influx1x_authentication_handler.go b/http/legacy/influx1x_authentication_handler.go index 22bfbd5a7cd..d1d57c8e9f4 100644 --- a/http/legacy/influx1x_authentication_handler.go +++ b/http/legacy/influx1x_authentication_handler.go @@ -2,6 +2,7 @@ package legacy import ( "context" + "errors" "fmt" "net/http" "strings" @@ -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, } } @@ -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 } @@ -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 { @@ -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 -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 @@ -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 @@ -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. @@ -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) -} diff --git a/http/legacy/influx1x_authentication_handler_test.go b/http/legacy/influx1x_authentication_handler_test.go index 4a1038b73f8..86af41be859 100644 --- a/http/legacy/influx1x_authentication_handler_test.go +++ b/http/legacy/influx1x_authentication_handler_test.go @@ -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 @@ -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 { @@ -103,8 +103,8 @@ 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{ @@ -112,13 +112,10 @@ func TestInflux1xAuthenticationHandler(t *testing.T) { }, }, { - 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), @@ -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, }, @@ -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() diff --git a/http/platform_handler.go b/http/platform_handler.go index 6ee89e93ead..31154505742 100644 --- a/http/platform_handler.go +++ b/http/platform_handler.go @@ -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), } }