From 8fa72fa35b1f51fcb30d94262ae6569b7f5380f7 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Mon, 27 Aug 2018 14:18:11 -0500 Subject: [PATCH 1/4] feat(auth): allow authorizations to be enabled/disabled --- auth.go | 26 +++- bolt/authorization.go | 31 +++++ http/auth_service.go | 135 ++++++++++++++++++- http/bucket_service.go | 10 +- http/org_service.go | 8 +- http/platform_handler.go | 2 +- http/proxy_query_service.go | 2 +- http/query_service.go | 2 +- http/source_service.go | 10 +- http/{token_parser.go => tokens.go} | 14 +- http/{token_parse_test.go => tokens_test.go} | 37 ++++- http/user_service.go | 10 +- prometheus/auth_service.go | 29 ++++ prometheus/auth_service_test.go | 8 ++ task/validator.go | 2 +- 15 files changed, 286 insertions(+), 40 deletions(-) rename http/{token_parser.go => tokens.go} (53%) rename http/{token_parse_test.go => tokens_test.go} (60%) diff --git a/auth.go b/auth.go index fdefeb4709..b339d14801 100644 --- a/auth.go +++ b/auth.go @@ -9,6 +9,7 @@ import ( type Authorization struct { ID ID `json:"id"` Token string `json:"token"` + Disabled bool `json:"disabled"` User string `json:"user,omitempty"` UserID ID `json:"userID,omitempty"` Permissions []Permission `json:"permissions"` @@ -29,6 +30,12 @@ type AuthorizationService interface { // Creates a new authorization and sets a.Token and a.UserID with the new identifier. CreateAuthorization(ctx context.Context, a *Authorization) error + // DisableAuthorization updates the token to be disabled. + DisableAuthorization(ctx context.Context, id ID) error + + // EnableAuthorization updates the token to be enabled. + EnableAuthorization(ctx context.Context, id ID) error + // Removes a authorization by token. DeleteAuthorization(ctx context.Context, id ID) error } @@ -85,19 +92,19 @@ func (p Permission) String() string { } var ( - // CreateUser is a permission for creating users. + // CreateUserPermission is a permission for creating users. CreateUserPermission = Permission{ Action: CreateAction, Resource: UserResource, } - // DeleteUser is a permission for deleting users. + // DeleteUserPermission is a permission for deleting users. DeleteUserPermission = Permission{ Action: DeleteAction, Resource: UserResource, } ) -// ReadBucket constructs a permission for reading a bucket. +// ReadBucketPermission constructs a permission for reading a bucket. func ReadBucketPermission(id ID) Permission { return Permission{ Action: ReadAction, @@ -105,7 +112,7 @@ func ReadBucketPermission(id ID) Permission { } } -// WriteBucket constructs a permission for writing to a bucket. +// WriteBucketPermission constructs a permission for writing to a bucket. func WriteBucketPermission(id ID) Permission { return Permission{ Action: WriteAction, @@ -113,9 +120,14 @@ func WriteBucketPermission(id ID) Permission { } } -// Allowed returns true if the permission exists in a list of permissions. -func Allowed(req Permission, ps []Permission) bool { - for _, p := range ps { +// Allowed returns true if the authorization is enabled and requested permission level +// exists in the authorization's list of permissions. +func Allowed(req Permission, auth *Authorization) bool { + if auth.Disabled { + return false + } + + for _, p := range auth.Permissions { if p.Action == req.Action && p.Resource == req.Resource { return true } diff --git a/bolt/authorization.go b/bolt/authorization.go index ee013190e6..10a08759cc 100644 --- a/bolt/authorization.go +++ b/bolt/authorization.go @@ -281,3 +281,34 @@ func (c *Client) deleteAuthorization(ctx context.Context, tx *bolt.Tx, id platfo } return tx.Bucket(authorizationBucket).Delete(id) } + +// DisableAuthorization disables the authorization by setting the disabled field to true. +func (c *Client) DisableAuthorization(ctx context.Context, id platform.ID) error { + return c.db.Update(func(tx *bolt.Tx) error { + isDisabled := true + return c.updateAuthorization(ctx, tx, id, isDisabled) + }) +} + +// EnableAuthorization enables the authorization by setting the disabled field to false. +func (c *Client) EnableAuthorization(ctx context.Context, id platform.ID) error { + return c.db.Update(func(tx *bolt.Tx) error { + isDisabled := false + return c.updateAuthorization(ctx, tx, id, isDisabled) + }) +} + +func (c *Client) updateAuthorization(ctx context.Context, tx *bolt.Tx, id platform.ID, isDisabled bool) error { + a, err := c.findAuthorizationByID(ctx, tx, id) + if err != nil { + return err + } + + a.Disabled = isDisabled + b, err := json.Marshal(a) + if err != nil { + return err + } + + return tx.Bucket(authorizationBucket).Put(a.ID, b) +} diff --git a/http/auth_service.go b/http/auth_service.go index 090ddcc368..db93557b1e 100644 --- a/http/auth_service.go +++ b/http/auth_service.go @@ -32,6 +32,7 @@ func NewAuthorizationHandler() *AuthorizationHandler { h.HandlerFunc("POST", "/v1/authorizations", h.handlePostAuthorization) h.HandlerFunc("GET", "/v1/authorizations", h.handleGetAuthorizations) h.HandlerFunc("GET", "/v1/authorizations/:id", h.handleGetAuthorization) + h.HandlerFunc("PATCH", "/v1/authorizations/:id", h.handleEnableDisableAuthorization) h.HandlerFunc("DELETE", "/v1/authorizations/:id", h.handleDeleteAuthorization) return h } @@ -181,6 +182,79 @@ func decodeGetAuthorizationRequest(ctx context.Context, r *http.Request) (*getAu }, nil } +// handleEnableDisableAuthorization is the HTTP handler for the PATCH /v1/authorizations/:id route. +func (h *AuthorizationHandler) handleEnableDisableAuthorization(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + req, err := decodeUpdateAuthorizationRequest(ctx, r) + if err != nil { + h.Logger.Info("failed to decode request", zap.String("handler", "updateAuthorization"), zap.Error(err)) + EncodeError(ctx, err, w) + return + } + + a, err := h.AuthorizationService.FindAuthorizationByID(ctx, req.ID) + if err != nil { + EncodeError(ctx, err, w) + return + } + + if req.Disabled == a.Disabled { + if err := encodeResponse(ctx, w, http.StatusOK, a); err != nil { + h.Logger.Info("failed to encode response", zap.String("handler", "updateAuthorization"), zap.Error(err)) + EncodeError(ctx, err, w) + return + } + return + } + + if req.Disabled { + if err := h.AuthorizationService.DisableAuthorization(ctx, req.ID); err != nil { + EncodeError(ctx, err, w) + return + } + } else { + if err := h.AuthorizationService.EnableAuthorization(ctx, req.ID); err != nil { + EncodeError(ctx, err, w) + return + } + } + + if err := encodeResponse(ctx, w, http.StatusOK, a); err != nil { + h.Logger.Info("failed to encode response", zap.String("handler", "updateAuthorization"), zap.Error(err)) + EncodeError(ctx, err, w) + return + } +} + +type updateAuthorizationRequest struct { + ID platform.ID + Disabled bool +} + +func decodeUpdateAuthorizationRequest(ctx context.Context, r *http.Request) (*updateAuthorizationRequest, error) { + params := httprouter.ParamsFromContext(ctx) + id := params.ByName("id") + if id == "" { + return nil, kerrors.InvalidDataf("url missing id") + } + + var i platform.ID + if err := i.DecodeFromString(id); err != nil { + return nil, err + } + + a := &platform.Authorization{} + if err := json.NewDecoder(r.Body).Decode(a); err != nil { + return nil, err + } + + return &updateAuthorizationRequest{ + ID: i, + Disabled: a.Disabled, + }, nil +} + // handleDeleteAuthorization is the HTTP handler for the DELETE /v1/authorizations/:id route. func (h *AuthorizationHandler) handleDeleteAuthorization(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -231,6 +305,7 @@ type AuthorizationService struct { var _ platform.AuthorizationService = (*AuthorizationService)(nil) +// FindAuthorizationByID finds the authorization against a remote influx server. func (s *AuthorizationService) FindAuthorizationByID(ctx context.Context, id platform.ID) (*platform.Authorization, error) { u, err := newURL(s.Addr, authorizationIDPath(id)) if err != nil { @@ -241,7 +316,7 @@ func (s *AuthorizationService) FindAuthorizationByID(ctx context.Context, id pla if err != nil { return nil, err } - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) @@ -295,7 +370,7 @@ func (s *AuthorizationService) FindAuthorizations(ctx context.Context, filter pl } req.URL.RawQuery = query.Encode() - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) @@ -338,7 +413,7 @@ func (s *AuthorizationService) CreateAuthorization(ctx context.Context, a *platf } req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) @@ -359,6 +434,58 @@ func (s *AuthorizationService) CreateAuthorization(ctx context.Context, a *platf return nil } +type enableDisableAuthorizationRequest struct { + Disabled bool `json:"disabled"` +} + +// updateAuthorization enables or disables authorization. +func (s *AuthorizationService) updateAuthorization(ctx context.Context, id platform.ID, disabled bool) error { + u, err := newURL(s.Addr, authorizationIDPath(id)) + if err != nil { + return err + } + + b, err := json.Marshal(enableDisableAuthorizationRequest{ + Disabled: disabled, + }) + if err != nil { + return err + } + + req, err := http.NewRequest("PATCH", u.String(), bytes.NewReader(b)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + SetToken(s.Token, req) + + hc := newClient(u.Scheme, s.InsecureSkipVerify) + + resp, err := hc.Do(req) + if err != nil { + return err + } + + if err := CheckError(resp); err != nil { + return err + } + + return nil +} + +// EnableAuthorization enables a disabled authorization. +func (s *AuthorizationService) EnableAuthorization(ctx context.Context, id platform.ID) error { + isDisabled := false + return s.updateAuthorization(ctx, id, isDisabled) +} + +// DisableAuthorization disables an enabled authorization. +func (s *AuthorizationService) DisableAuthorization(ctx context.Context, id platform.ID) error { + isDisabled := true + return s.updateAuthorization(ctx, id, isDisabled) +} + // DeleteAuthorization removes a authorization by id. func (s *AuthorizationService) DeleteAuthorization(ctx context.Context, id platform.ID) error { u, err := newURL(s.Addr, authorizationIDPath(id)) @@ -370,7 +497,7 @@ func (s *AuthorizationService) DeleteAuthorization(ctx context.Context, id platf if err != nil { return err } - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) diff --git a/http/bucket_service.go b/http/bucket_service.go index f241f3cedb..e44ee9f56a 100644 --- a/http/bucket_service.go +++ b/http/bucket_service.go @@ -281,7 +281,7 @@ func (s *BucketService) FindBucketByID(ctx context.Context, id platform.ID) (*pl if err != nil { return nil, err } - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) @@ -344,7 +344,7 @@ func (s *BucketService) FindBuckets(ctx context.Context, filter platform.BucketF } req.URL.RawQuery = query.Encode() - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) @@ -383,7 +383,7 @@ func (s *BucketService) CreateBucket(ctx context.Context, b *platform.Bucket) er } req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) @@ -423,7 +423,7 @@ func (s *BucketService) UpdateBucket(ctx context.Context, id platform.ID, upd pl } req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) @@ -456,7 +456,7 @@ func (s *BucketService) DeleteBucket(ctx context.Context, id platform.ID) error if err != nil { return err } - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) diff --git a/http/org_service.go b/http/org_service.go index 166aa6ebf6..847a2f7583 100644 --- a/http/org_service.go +++ b/http/org_service.go @@ -298,7 +298,7 @@ func (s *OrganizationService) FindOrganizations(ctx context.Context, filter plat return nil, 0, err } - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(url.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) @@ -341,7 +341,7 @@ func (s *OrganizationService) CreateOrganization(ctx context.Context, o *platfor } req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(url.Scheme, s.InsecureSkipVerify) @@ -379,7 +379,7 @@ func (s *OrganizationService) UpdateOrganization(ctx context.Context, id platfor } req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) @@ -411,7 +411,7 @@ func (s *OrganizationService) DeleteOrganization(ctx context.Context, id platfor if err != nil { return err } - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) diff --git a/http/platform_handler.go b/http/platform_handler.go index 75b2600714..2df3f775f8 100644 --- a/http/platform_handler.go +++ b/http/platform_handler.go @@ -143,7 +143,7 @@ func (h *PlatformHandler) PrometheusCollectors() []prometheus.Collector { } func extractAuthorization(ctx context.Context, r *nethttp.Request) (context.Context, error) { - t, err := ParseAuthHeaderToken(r) + t, err := GetToken(r) if err != nil { return ctx, err } diff --git a/http/proxy_query_service.go b/http/proxy_query_service.go index a5e3b69cf6..c95d0781f9 100644 --- a/http/proxy_query_service.go +++ b/http/proxy_query_service.go @@ -103,7 +103,7 @@ func (s *ProxyQueryService) Query(ctx context.Context, w io.Writer, req *query.P if err != nil { return 0, err } - hreq.Header.Set("Authorization", s.Token) + SetToken(s.Token, hreq) hreq = hreq.WithContext(ctx) hc := newClient(u.Scheme, s.InsecureSkipVerify) diff --git a/http/query_service.go b/http/query_service.go index c49fea027e..2276e26b34 100644 --- a/http/query_service.go +++ b/http/query_service.go @@ -139,7 +139,7 @@ func (s *QueryService) Query(ctx context.Context, req *query.Request) (query.Res if err != nil { return nil, err } - hreq.Header.Set("Authorization", s.Token) + SetToken(s.Token, hreq) hreq = hreq.WithContext(ctx) hc := newClient(u.Scheme, s.InsecureSkipVerify) diff --git a/http/source_service.go b/http/source_service.go index bc3a5c61ed..b848351a3a 100644 --- a/http/source_service.go +++ b/http/source_service.go @@ -467,7 +467,7 @@ func (s *SourceService) FindSourceByID(ctx context.Context, id platform.ID) (*pl if err != nil { return nil, err } - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) @@ -501,7 +501,7 @@ func (s *SourceService) FindSources(ctx context.Context, opt platform.FindOption return nil, 0, err } - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) @@ -540,7 +540,7 @@ func (s *SourceService) CreateSource(ctx context.Context, b *platform.Source) er } req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) @@ -580,7 +580,7 @@ func (s *SourceService) UpdateSource(ctx context.Context, id platform.ID, upd pl } req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) @@ -613,7 +613,7 @@ func (s *SourceService) DeleteSource(ctx context.Context, id platform.ID) error if err != nil { return err } - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(u.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) diff --git a/http/token_parser.go b/http/tokens.go similarity index 53% rename from http/token_parser.go rename to http/tokens.go index c780e988b2..980bce7b7d 100644 --- a/http/token_parser.go +++ b/http/tokens.go @@ -2,20 +2,21 @@ package http import ( "errors" + "fmt" "net/http" "strings" ) -const tokenScheme = "Token " +const tokenScheme = "Token " // TODO(goller): I'd like this to be Bearer // errors var ( ErrAuthHeaderMissing = errors.New("Authorization Header is missing") - ErrAuthBadScheme = errors.New("Authorization Header Scheme has to Token") + ErrAuthBadScheme = errors.New("Authorization Header Scheme is invalid") ) -// ParseAuthHeaderToken will parse the token from http Authorization Header. -func ParseAuthHeaderToken(r *http.Request) (string, error) { +// GetToken will parse the token from http Authorization Header. +func GetToken(r *http.Request) (string, error) { header := r.Header.Get("Authorization") if header == "" { return "", ErrAuthHeaderMissing @@ -25,3 +26,8 @@ func ParseAuthHeaderToken(r *http.Request) (string, error) { } return header[len(tokenScheme):], nil } + +// SetToken adds the token to the request. +func SetToken(token string, req *http.Request) { + req.Header.Set("Authorization", fmt.Sprintf("%s%s", tokenScheme, token)) +} diff --git a/http/token_parse_test.go b/http/tokens_test.go similarity index 60% rename from http/token_parse_test.go rename to http/tokens_test.go index 3f798c12cb..8b33ac6ece 100644 --- a/http/token_parse_test.go +++ b/http/tokens_test.go @@ -2,10 +2,19 @@ package http import ( "net/http" + "net/http/httptest" "testing" ) -func TestParseAuthHeader(t *testing.T) { +func tokenRequest(token string) *http.Request { + req := httptest.NewRequest("GET", "/", nil) + if token != "" { + SetToken(token, req) + } + return req +} + +func TestGetToken(t *testing.T) { type args struct { header string } @@ -62,7 +71,7 @@ func TestParseAuthHeader(t *testing.T) { Header: make(http.Header), } req.Header.Set("Authorization", tt.args.header) - result, err := ParseAuthHeaderToken(req) + result, err := GetToken(req) if err != tt.wants.err { t.Errorf("err incorrect want %v, got %v", tt.wants.err, err) return @@ -74,3 +83,27 @@ func TestParseAuthHeader(t *testing.T) { } } + +func TestSetToken(t *testing.T) { + tests := []struct { + name string + token string + req *http.Request + want string + }{ + { + name: "adding token to Authorization header", + token: "1234", + req: httptest.NewRequest("GET", "/", nil), + want: "Token 1234", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + SetToken(tt.token, tt.req) + if got := tt.req.Header.Get("Authorization"); got != tt.want { + t.Errorf("SetToken() want %s, got %s", tt.want, got) + } + }) + } +} diff --git a/http/user_service.go b/http/user_service.go index 4445e948e7..6730a883d9 100644 --- a/http/user_service.go +++ b/http/user_service.go @@ -265,7 +265,7 @@ func (s *UserService) FindUserByID(ctx context.Context, id platform.ID) (*platfo if err != nil { return nil, err } - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(url.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) @@ -322,7 +322,7 @@ func (s *UserService) FindUsers(ctx context.Context, filter platform.UserFilter, } req.URL.RawQuery = query.Encode() - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(url.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) @@ -364,7 +364,7 @@ func (s *UserService) CreateUser(ctx context.Context, u *platform.User) error { } req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(url.Scheme, s.InsecureSkipVerify) @@ -404,7 +404,7 @@ func (s *UserService) UpdateUser(ctx context.Context, id platform.ID, upd platfo } req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(url.Scheme, s.InsecureSkipVerify) @@ -437,7 +437,7 @@ func (s *UserService) DeleteUser(ctx context.Context, id platform.ID) error { if err != nil { return err } - req.Header.Set("Authorization", s.Token) + SetToken(s.Token, req) hc := newClient(url.Scheme, s.InsecureSkipVerify) resp, err := hc.Do(req) diff --git a/prometheus/auth_service.go b/prometheus/auth_service.go index fee0bf15c7..93254e83a5 100644 --- a/prometheus/auth_service.go +++ b/prometheus/auth_service.go @@ -109,6 +109,35 @@ func (s *AuthorizationService) DeleteAuthorization(ctx context.Context, id platf return s.AuthorizationService.DeleteAuthorization(ctx, id) } +// DisableAuthorization disables an authorization, records function call latency, and counts function calls. +func (s *AuthorizationService) DisableAuthorization(ctx context.Context, id platform.ID) (err error) { + defer func(start time.Time) { + labels := prometheus.Labels{ + "method": "DisableAuthorization", + "error": fmt.Sprint(err != nil), + } + s.requestCount.With(labels).Add(1) + s.requestDuration.With(labels).Observe(time.Since(start).Seconds()) + }(time.Now()) + + return s.AuthorizationService.DisableAuthorization(ctx, id) +} + +// EnableAuthorization enables an authorization, records function call latency, and counts function calls. +func (s *AuthorizationService) EnableAuthorization(ctx context.Context, id platform.ID) (err error) { + defer func(start time.Time) { + labels := prometheus.Labels{ + "method": "EnableAuthorization", + "error": fmt.Sprint(err != nil), + } + s.requestCount.With(labels).Add(1) + s.requestDuration.With(labels).Observe(time.Since(start).Seconds()) + }(time.Now()) + + return s.AuthorizationService.EnableAuthorization(ctx, id) +} + +// PrometheusCollectors returns all authorization service prometheus collectors. func (s *AuthorizationService) PrometheusCollectors() []prometheus.Collector { return []prometheus.Collector{ s.requestCount, diff --git a/prometheus/auth_service_test.go b/prometheus/auth_service_test.go index 38a6569e28..4221c545a3 100644 --- a/prometheus/auth_service_test.go +++ b/prometheus/auth_service_test.go @@ -38,6 +38,14 @@ func (a *authzSvc) DeleteAuthorization(context.Context, platform.ID) error { return a.Err } +func (a *authzSvc) DisableAuthorization(context.Context, platform.ID) error { + return a.Err +} + +func (a *authzSvc) EnableAuthorization(context.Context, platform.ID) error { + return a.Err +} + func TestAuthorizationService_Metrics(t *testing.T) { a := new(authzSvc) diff --git a/task/validator.go b/task/validator.go index d677aa772d..133b7dfcfe 100644 --- a/task/validator.go +++ b/task/validator.go @@ -47,7 +47,7 @@ func validatePermission(ctx context.Context, perm platform.Permission) error { return err } - if !platform.Allowed(perm, auth.Permissions) { + if !platform.Allowed(perm, auth) { return authError{error: ErrFailedPermission, perm: perm, auth: auth} } From d3afb82323f65a2e62dce0221dd8c6821b118ec1 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Mon, 27 Aug 2018 14:29:50 -0500 Subject: [PATCH 2/4] feat(cmd/influx): add enable/disable from influx cli tool --- cmd/influx/authorization.go | 144 ++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/cmd/influx/authorization.go b/cmd/influx/authorization.go index 1f74719b0c..b9c43fe2e5 100644 --- a/cmd/influx/authorization.go +++ b/cmd/influx/authorization.go @@ -98,6 +98,7 @@ func authorizationCreateF(cmd *cobra.Command, args []string) { w.WriteHeaders( "ID", "Token", + "Disabled", "User", "UserID", "Permissions", @@ -111,6 +112,7 @@ func authorizationCreateF(cmd *cobra.Command, args []string) { w.Write(map[string]interface{}{ "ID": authorization.ID.String(), "Token": authorization.Token, + "Disabled": authorization.Disabled, "User": authorization.User, "UserID": authorization.UserID.String(), "Permissions": ps, @@ -178,6 +180,7 @@ func authorizationFindF(cmd *cobra.Command, args []string) { w.WriteHeaders( "ID", "Token", + "Disabled", "User", "UserID", "Permissions", @@ -192,6 +195,7 @@ func authorizationFindF(cmd *cobra.Command, args []string) { w.Write(map[string]interface{}{ "ID": a.ID, "Token": a.Token, + "Disabled": a.Disabled, "User": a.User, "UserID": a.UserID.String(), "Permissions": permissions, @@ -269,3 +273,143 @@ func authorizationDeleteF(cmd *cobra.Command, args []string) { }) w.Flush() } + +// AuthorizationEnableFlags are command line args used when enabling an authorization +type AuthorizationEnableFlags struct { + id string +} + +var authorizationEnableFlags AuthorizationEnableFlags + +func init() { + authorizationEnableCmd := &cobra.Command{ + Use: "enable", + Short: "enable authorization", + Run: authorizationEnableF, + } + + authorizationEnableCmd.Flags().StringVarP(&authorizationEnableFlags.id, "id", "i", "", "authorization id (required)") + authorizationEnableCmd.MarkFlagRequired("id") + + authorizationCmd.AddCommand(authorizationEnableCmd) +} + +func authorizationEnableF(cmd *cobra.Command, args []string) { + s := &http.AuthorizationService{ + Addr: flags.host, + Token: flags.token, + } + + id := platform.ID{} + if err := id.DecodeFromString(authorizationEnableFlags.id); err != nil { + fmt.Println(err) + os.Exit(1) + } + + ctx := context.TODO() + a, err := s.FindAuthorizationByID(ctx, id) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := s.EnableAuthorization(context.Background(), id); err != nil { + fmt.Println(err) + os.Exit(1) + } + + w := internal.NewTabWriter(os.Stdout) + w.WriteHeaders( + "ID", + "Token", + "Disabled", + "User", + "UserID", + "Permissions", + ) + + ps := []string{} + for _, p := range a.Permissions { + ps = append(ps, p.String()) + } + + w.Write(map[string]interface{}{ + "ID": a.ID.String(), + "Token": a.Token, + "Disabled": false, + "User": a.User, + "UserID": a.UserID.String(), + "Permissions": ps, + }) + w.Flush() +} + +// AuthorizationDisableFlags are command line args used when disabling an authorization +type AuthorizationDisableFlags struct { + id string +} + +var authorizationDisableFlags AuthorizationDisableFlags + +func init() { + authorizationDisableCmd := &cobra.Command{ + Use: "disable", + Short: "disable authorization", + Run: authorizationDisableF, + } + + authorizationDisableCmd.Flags().StringVarP(&authorizationDisableFlags.id, "id", "i", "", "authorization id (required)") + authorizationDisableCmd.MarkFlagRequired("id") + + authorizationCmd.AddCommand(authorizationDisableCmd) +} + +func authorizationDisableF(cmd *cobra.Command, args []string) { + s := &http.AuthorizationService{ + Addr: flags.host, + Token: flags.token, + } + + id := platform.ID{} + if err := id.DecodeFromString(authorizationDisableFlags.id); err != nil { + fmt.Println(err) + os.Exit(1) + } + + ctx := context.TODO() + a, err := s.FindAuthorizationByID(ctx, id) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := s.DisableAuthorization(context.Background(), id); err != nil { + fmt.Println(err) + os.Exit(1) + } + + w := internal.NewTabWriter(os.Stdout) + w.WriteHeaders( + "ID", + "Token", + "Disabled", + "User", + "UserID", + "Permissions", + ) + + ps := []string{} + for _, p := range a.Permissions { + ps = append(ps, p.String()) + } + + w.Write(map[string]interface{}{ + "ID": a.ID.String(), + "Token": a.Token, + "Disabled": true, + "User": a.User, + "UserID": a.UserID.String(), + "Permissions": ps, + }) + w.Flush() +} From 40e56333e19e6ab9eec0bae04d31c5657c1d0f62 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Tue, 28 Aug 2018 12:58:38 -0500 Subject: [PATCH 3/4] refactor(http): update authorization to use active/inactive status --- auth.go | 45 +++++++++++---------- bolt/authorization.go | 20 +++------- cmd/influx/authorization.go | 68 ++++++++++++++++---------------- http/auth_service.go | 69 ++++++++++++--------------------- prometheus/auth_service.go | 23 +++-------- prometheus/auth_service_test.go | 6 +-- 6 files changed, 95 insertions(+), 136 deletions(-) diff --git a/auth.go b/auth.go index b339d14801..9dce90d003 100644 --- a/auth.go +++ b/auth.go @@ -9,12 +9,32 @@ import ( type Authorization struct { ID ID `json:"id"` Token string `json:"token"` - Disabled bool `json:"disabled"` + Status Status `json:"status"` User string `json:"user,omitempty"` UserID ID `json:"userID,omitempty"` Permissions []Permission `json:"permissions"` } +// Allowed returns true if the authorization is active and requested permission level +// exists in the authorization's list of permissions. +func Allowed(req Permission, auth *Authorization) bool { + if !IsActive(auth) { + return false + } + + for _, p := range auth.Permissions { + if p.Action == req.Action && p.Resource == req.Resource { + return true + } + } + return false +} + +// IsActive returns true if the authorization active. +func IsActive(auth *Authorization) bool { + return auth.Status == Active +} + // AuthorizationService represents a service for managing authorization data. type AuthorizationService interface { // Returns a single authorization by ID. @@ -30,11 +50,9 @@ type AuthorizationService interface { // Creates a new authorization and sets a.Token and a.UserID with the new identifier. CreateAuthorization(ctx context.Context, a *Authorization) error - // DisableAuthorization updates the token to be disabled. - DisableAuthorization(ctx context.Context, id ID) error - - // EnableAuthorization updates the token to be enabled. - EnableAuthorization(ctx context.Context, id ID) error + // SetAuthorizationStatus updates the status of the authorization. Useful + // for setting an authorization to inactive or active. + SetAuthorizationStatus(ctx context.Context, id ID, status Status) error // Removes a authorization by token. DeleteAuthorization(ctx context.Context, id ID) error @@ -119,18 +137,3 @@ func WriteBucketPermission(id ID) Permission { Resource: BucketResource(id), } } - -// Allowed returns true if the authorization is enabled and requested permission level -// exists in the authorization's list of permissions. -func Allowed(req Permission, auth *Authorization) bool { - if auth.Disabled { - return false - } - - for _, p := range auth.Permissions { - if p.Action == req.Action && p.Resource == req.Resource { - return true - } - } - return false -} diff --git a/bolt/authorization.go b/bolt/authorization.go index 10a08759cc..cab7a43565 100644 --- a/bolt/authorization.go +++ b/bolt/authorization.go @@ -282,29 +282,21 @@ func (c *Client) deleteAuthorization(ctx context.Context, tx *bolt.Tx, id platfo return tx.Bucket(authorizationBucket).Delete(id) } -// DisableAuthorization disables the authorization by setting the disabled field to true. -func (c *Client) DisableAuthorization(ctx context.Context, id platform.ID) error { +// SetAuthorizationStatus updates the status of the authorization. Useful +// for setting an authorization to inactive or active. +func (c *Client) SetAuthorizationStatus(ctx context.Context, id platform.ID, status platform.Status) error { return c.db.Update(func(tx *bolt.Tx) error { - isDisabled := true - return c.updateAuthorization(ctx, tx, id, isDisabled) + return c.updateAuthorization(ctx, tx, id, status) }) } -// EnableAuthorization enables the authorization by setting the disabled field to false. -func (c *Client) EnableAuthorization(ctx context.Context, id platform.ID) error { - return c.db.Update(func(tx *bolt.Tx) error { - isDisabled := false - return c.updateAuthorization(ctx, tx, id, isDisabled) - }) -} - -func (c *Client) updateAuthorization(ctx context.Context, tx *bolt.Tx, id platform.ID, isDisabled bool) error { +func (c *Client) updateAuthorization(ctx context.Context, tx *bolt.Tx, id platform.ID, status platform.Status) error { a, err := c.findAuthorizationByID(ctx, tx, id) if err != nil { return err } - a.Disabled = isDisabled + a.Status = status b, err := json.Marshal(a) if err != nil { return err diff --git a/cmd/influx/authorization.go b/cmd/influx/authorization.go index b9c43fe2e5..376c4e1364 100644 --- a/cmd/influx/authorization.go +++ b/cmd/influx/authorization.go @@ -98,7 +98,7 @@ func authorizationCreateF(cmd *cobra.Command, args []string) { w.WriteHeaders( "ID", "Token", - "Disabled", + "Status", "User", "UserID", "Permissions", @@ -112,7 +112,7 @@ func authorizationCreateF(cmd *cobra.Command, args []string) { w.Write(map[string]interface{}{ "ID": authorization.ID.String(), "Token": authorization.Token, - "Disabled": authorization.Disabled, + "Status": authorization.Status, "User": authorization.User, "UserID": authorization.UserID.String(), "Permissions": ps, @@ -180,7 +180,7 @@ func authorizationFindF(cmd *cobra.Command, args []string) { w.WriteHeaders( "ID", "Token", - "Disabled", + "Status", "User", "UserID", "Permissions", @@ -195,7 +195,7 @@ func authorizationFindF(cmd *cobra.Command, args []string) { w.Write(map[string]interface{}{ "ID": a.ID, "Token": a.Token, - "Disabled": a.Disabled, + "Status": a.Status, "User": a.User, "UserID": a.UserID.String(), "Permissions": permissions, @@ -274,34 +274,34 @@ func authorizationDeleteF(cmd *cobra.Command, args []string) { w.Flush() } -// AuthorizationEnableFlags are command line args used when enabling an authorization -type AuthorizationEnableFlags struct { +// AuthorizationActiveFlags are command line args used when enabling an authorization +type AuthorizationActiveFlags struct { id string } -var authorizationEnableFlags AuthorizationEnableFlags +var authorizationActiveFlags AuthorizationActiveFlags func init() { - authorizationEnableCmd := &cobra.Command{ - Use: "enable", - Short: "enable authorization", - Run: authorizationEnableF, + authorizationActiveCmd := &cobra.Command{ + Use: "active", + Short: "active authorization", + Run: authorizationActiveF, } - authorizationEnableCmd.Flags().StringVarP(&authorizationEnableFlags.id, "id", "i", "", "authorization id (required)") - authorizationEnableCmd.MarkFlagRequired("id") + authorizationActiveCmd.Flags().StringVarP(&authorizationActiveFlags.id, "id", "i", "", "authorization id (required)") + authorizationActiveCmd.MarkFlagRequired("id") - authorizationCmd.AddCommand(authorizationEnableCmd) + authorizationCmd.AddCommand(authorizationActiveCmd) } -func authorizationEnableF(cmd *cobra.Command, args []string) { +func authorizationActiveF(cmd *cobra.Command, args []string) { s := &http.AuthorizationService{ Addr: flags.host, Token: flags.token, } id := platform.ID{} - if err := id.DecodeFromString(authorizationEnableFlags.id); err != nil { + if err := id.DecodeFromString(authorizationActiveFlags.id); err != nil { fmt.Println(err) os.Exit(1) } @@ -313,7 +313,7 @@ func authorizationEnableF(cmd *cobra.Command, args []string) { os.Exit(1) } - if err := s.EnableAuthorization(context.Background(), id); err != nil { + if err := s.SetAuthorizationStatus(context.Background(), id, platform.Active); err != nil { fmt.Println(err) os.Exit(1) } @@ -322,7 +322,7 @@ func authorizationEnableF(cmd *cobra.Command, args []string) { w.WriteHeaders( "ID", "Token", - "Disabled", + "Status", "User", "UserID", "Permissions", @@ -336,7 +336,7 @@ func authorizationEnableF(cmd *cobra.Command, args []string) { w.Write(map[string]interface{}{ "ID": a.ID.String(), "Token": a.Token, - "Disabled": false, + "Status": a.Status, "User": a.User, "UserID": a.UserID.String(), "Permissions": ps, @@ -344,34 +344,34 @@ func authorizationEnableF(cmd *cobra.Command, args []string) { w.Flush() } -// AuthorizationDisableFlags are command line args used when disabling an authorization -type AuthorizationDisableFlags struct { +// AuthorizationInactiveFlags are command line args used when disabling an authorization +type AuthorizationInactiveFlags struct { id string } -var authorizationDisableFlags AuthorizationDisableFlags +var authorizationInactiveFlags AuthorizationInactiveFlags func init() { - authorizationDisableCmd := &cobra.Command{ - Use: "disable", - Short: "disable authorization", - Run: authorizationDisableF, + authorizationInactiveCmd := &cobra.Command{ + Use: "inactive", + Short: "inactive authorization", + Run: authorizationInactiveF, } - authorizationDisableCmd.Flags().StringVarP(&authorizationDisableFlags.id, "id", "i", "", "authorization id (required)") - authorizationDisableCmd.MarkFlagRequired("id") + authorizationInactiveCmd.Flags().StringVarP(&authorizationInactiveFlags.id, "id", "i", "", "authorization id (required)") + authorizationInactiveCmd.MarkFlagRequired("id") - authorizationCmd.AddCommand(authorizationDisableCmd) + authorizationCmd.AddCommand(authorizationInactiveCmd) } -func authorizationDisableF(cmd *cobra.Command, args []string) { +func authorizationInactiveF(cmd *cobra.Command, args []string) { s := &http.AuthorizationService{ Addr: flags.host, Token: flags.token, } id := platform.ID{} - if err := id.DecodeFromString(authorizationDisableFlags.id); err != nil { + if err := id.DecodeFromString(authorizationInactiveFlags.id); err != nil { fmt.Println(err) os.Exit(1) } @@ -383,7 +383,7 @@ func authorizationDisableF(cmd *cobra.Command, args []string) { os.Exit(1) } - if err := s.DisableAuthorization(context.Background(), id); err != nil { + if err := s.SetAuthorizationStatus(context.Background(), id, platform.Inactive); err != nil { fmt.Println(err) os.Exit(1) } @@ -392,7 +392,7 @@ func authorizationDisableF(cmd *cobra.Command, args []string) { w.WriteHeaders( "ID", "Token", - "Disabled", + "Status", "User", "UserID", "Permissions", @@ -406,7 +406,7 @@ func authorizationDisableF(cmd *cobra.Command, args []string) { w.Write(map[string]interface{}{ "ID": a.ID.String(), "Token": a.Token, - "Disabled": true, + "Status": a.Status, "User": a.User, "UserID": a.UserID.String(), "Permissions": ps, diff --git a/http/auth_service.go b/http/auth_service.go index db93557b1e..81ae2281ec 100644 --- a/http/auth_service.go +++ b/http/auth_service.go @@ -32,7 +32,7 @@ func NewAuthorizationHandler() *AuthorizationHandler { h.HandlerFunc("POST", "/v1/authorizations", h.handlePostAuthorization) h.HandlerFunc("GET", "/v1/authorizations", h.handleGetAuthorizations) h.HandlerFunc("GET", "/v1/authorizations/:id", h.handleGetAuthorization) - h.HandlerFunc("PATCH", "/v1/authorizations/:id", h.handleEnableDisableAuthorization) + h.HandlerFunc("PATCH", "/v1/authorizations/:id", h.handleSetAuthorizationStatus) h.HandlerFunc("DELETE", "/v1/authorizations/:id", h.handleDeleteAuthorization) return h } @@ -182,11 +182,11 @@ func decodeGetAuthorizationRequest(ctx context.Context, r *http.Request) (*getAu }, nil } -// handleEnableDisableAuthorization is the HTTP handler for the PATCH /v1/authorizations/:id route. -func (h *AuthorizationHandler) handleEnableDisableAuthorization(w http.ResponseWriter, r *http.Request) { +// handleSetAuthorizationStatus is the HTTP handler for the PATCH /v1/authorizations/:id route that updates the authorization's status. +func (h *AuthorizationHandler) handleSetAuthorizationStatus(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - req, err := decodeUpdateAuthorizationRequest(ctx, r) + req, err := decodeSetAuthorizationStatusRequest(ctx, r) if err != nil { h.Logger.Info("failed to decode request", zap.String("handler", "updateAuthorization"), zap.Error(err)) EncodeError(ctx, err, w) @@ -199,22 +199,9 @@ func (h *AuthorizationHandler) handleEnableDisableAuthorization(w http.ResponseW return } - if req.Disabled == a.Disabled { - if err := encodeResponse(ctx, w, http.StatusOK, a); err != nil { - h.Logger.Info("failed to encode response", zap.String("handler", "updateAuthorization"), zap.Error(err)) - EncodeError(ctx, err, w) - return - } - return - } - - if req.Disabled { - if err := h.AuthorizationService.DisableAuthorization(ctx, req.ID); err != nil { - EncodeError(ctx, err, w) - return - } - } else { - if err := h.AuthorizationService.EnableAuthorization(ctx, req.ID); err != nil { + if req.Status != a.Status { + a.Status = req.Status + if err := h.AuthorizationService.SetAuthorizationStatus(ctx, a.ID, a.Status); err != nil { EncodeError(ctx, err, w) return } @@ -228,11 +215,11 @@ func (h *AuthorizationHandler) handleEnableDisableAuthorization(w http.ResponseW } type updateAuthorizationRequest struct { - ID platform.ID - Disabled bool + ID platform.ID + Status platform.Status } -func decodeUpdateAuthorizationRequest(ctx context.Context, r *http.Request) (*updateAuthorizationRequest, error) { +func decodeSetAuthorizationStatusRequest(ctx context.Context, r *http.Request) (*updateAuthorizationRequest, error) { params := httprouter.ParamsFromContext(ctx) id := params.ByName("id") if id == "" { @@ -244,14 +231,20 @@ func decodeUpdateAuthorizationRequest(ctx context.Context, r *http.Request) (*up return nil, err } - a := &platform.Authorization{} + a := &setAuthorizationStatusRequest{} if err := json.NewDecoder(r.Body).Decode(a); err != nil { return nil, err } + switch a.Status { + case platform.Active, platform.Inactive: + default: + return nil, kerrors.InvalidDataf("unknown status option") + } + return &updateAuthorizationRequest{ - ID: i, - Disabled: a.Disabled, + ID: i, + Status: a.Status, }, nil } @@ -434,19 +427,19 @@ func (s *AuthorizationService) CreateAuthorization(ctx context.Context, a *platf return nil } -type enableDisableAuthorizationRequest struct { - Disabled bool `json:"disabled"` +type setAuthorizationStatusRequest struct { + Status platform.Status `json:"status"` } -// updateAuthorization enables or disables authorization. -func (s *AuthorizationService) updateAuthorization(ctx context.Context, id platform.ID, disabled bool) error { +// SetAuthorizationStatus updates an authorization's status. +func (s *AuthorizationService) SetAuthorizationStatus(ctx context.Context, id platform.ID, status platform.Status) error { u, err := newURL(s.Addr, authorizationIDPath(id)) if err != nil { return err } - b, err := json.Marshal(enableDisableAuthorizationRequest{ - Disabled: disabled, + b, err := json.Marshal(setAuthorizationStatusRequest{ + Status: status, }) if err != nil { return err @@ -474,18 +467,6 @@ func (s *AuthorizationService) updateAuthorization(ctx context.Context, id platf return nil } -// EnableAuthorization enables a disabled authorization. -func (s *AuthorizationService) EnableAuthorization(ctx context.Context, id platform.ID) error { - isDisabled := false - return s.updateAuthorization(ctx, id, isDisabled) -} - -// DisableAuthorization disables an enabled authorization. -func (s *AuthorizationService) DisableAuthorization(ctx context.Context, id platform.ID) error { - isDisabled := true - return s.updateAuthorization(ctx, id, isDisabled) -} - // DeleteAuthorization removes a authorization by id. func (s *AuthorizationService) DeleteAuthorization(ctx context.Context, id platform.ID) error { u, err := newURL(s.Addr, authorizationIDPath(id)) diff --git a/prometheus/auth_service.go b/prometheus/auth_service.go index 93254e83a5..dfaa56864c 100644 --- a/prometheus/auth_service.go +++ b/prometheus/auth_service.go @@ -109,32 +109,19 @@ func (s *AuthorizationService) DeleteAuthorization(ctx context.Context, id platf return s.AuthorizationService.DeleteAuthorization(ctx, id) } -// DisableAuthorization disables an authorization, records function call latency, and counts function calls. -func (s *AuthorizationService) DisableAuthorization(ctx context.Context, id platform.ID) (err error) { +// SetAuthorizationStatus updates the status of the authorization. Useful +// for setting an authorization to inactive or active. +func (s *AuthorizationService) SetAuthorizationStatus(ctx context.Context, id platform.ID, status platform.Status) (err error) { defer func(start time.Time) { labels := prometheus.Labels{ - "method": "DisableAuthorization", + "method": "setAuthorizationStatus", "error": fmt.Sprint(err != nil), } s.requestCount.With(labels).Add(1) s.requestDuration.With(labels).Observe(time.Since(start).Seconds()) }(time.Now()) - return s.AuthorizationService.DisableAuthorization(ctx, id) -} - -// EnableAuthorization enables an authorization, records function call latency, and counts function calls. -func (s *AuthorizationService) EnableAuthorization(ctx context.Context, id platform.ID) (err error) { - defer func(start time.Time) { - labels := prometheus.Labels{ - "method": "EnableAuthorization", - "error": fmt.Sprint(err != nil), - } - s.requestCount.With(labels).Add(1) - s.requestDuration.With(labels).Observe(time.Since(start).Seconds()) - }(time.Now()) - - return s.AuthorizationService.EnableAuthorization(ctx, id) + return s.AuthorizationService.SetAuthorizationStatus(ctx, id, status) } // PrometheusCollectors returns all authorization service prometheus collectors. diff --git a/prometheus/auth_service_test.go b/prometheus/auth_service_test.go index 4221c545a3..e7e5d61302 100644 --- a/prometheus/auth_service_test.go +++ b/prometheus/auth_service_test.go @@ -38,11 +38,7 @@ func (a *authzSvc) DeleteAuthorization(context.Context, platform.ID) error { return a.Err } -func (a *authzSvc) DisableAuthorization(context.Context, platform.ID) error { - return a.Err -} - -func (a *authzSvc) EnableAuthorization(context.Context, platform.ID) error { +func (a *authzSvc) SetAuthorizationStatus(context.Context, platform.ID, platform.Status) error { return a.Err } From 4c42d40ca9d16c08ed37a7cdc55fc799ec65ddfe Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Tue, 28 Aug 2018 13:01:32 -0500 Subject: [PATCH 4/4] docs(http): authorization status is now active or inactive --- bolt/authorization.go | 33 ++++++++++++++++++++++++++++----- http/swagger.yml | 14 +++++++++----- status.go | 11 +++++++++++ testing/auth.go | 16 ++++++++++++++++ zap/auth_service.go | 13 +++++++++++++ 5 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 status.go diff --git a/bolt/authorization.go b/bolt/authorization.go index cab7a43565..fed93031d6 100644 --- a/bolt/authorization.go +++ b/bolt/authorization.go @@ -66,7 +66,7 @@ func (c *Client) findAuthorizationByID(ctx context.Context, tx *bolt.Tx, id plat return nil, fmt.Errorf("authorization not found") } - if err := json.Unmarshal(v, &a); err != nil { + if err := decodeAuthorization(v, &a); err != nil { return nil, err } @@ -221,9 +221,20 @@ func (c *Client) PutAuthorization(ctx context.Context, a *platform.Authorization }) } -func (c *Client) putAuthorization(ctx context.Context, tx *bolt.Tx, a *platform.Authorization) error { +func encodeAuthorization(a *platform.Authorization) ([]byte, error) { a.User = "" - v, err := json.Marshal(a) + switch a.Status { + case platform.Active, platform.Inactive: + case "": + a.Status = platform.Active + default: + return nil, fmt.Errorf("unknown authorization status") + } + return json.Marshal(a) +} + +func (c *Client) putAuthorization(ctx context.Context, tx *bolt.Tx, a *platform.Authorization) error { + v, err := encodeAuthorization(a) if err != nil { return err } @@ -232,6 +243,7 @@ func (c *Client) putAuthorization(ctx context.Context, tx *bolt.Tx, a *platform. return err } if err := tx.Bucket(authorizationBucket).Put(a.ID, v); err != nil { + return err } return c.setUserOnAuthorization(ctx, tx, a) } @@ -240,12 +252,23 @@ func authorizationIndexKey(n string) []byte { return []byte(n) } +func decodeAuthorization(b []byte, a *platform.Authorization) error { + if err := json.Unmarshal(b, a); err != nil { + return err + } + if a.Status == "" { + a.Status = platform.Active + } + return nil +} + // forEachAuthorization will iterate through all authorizations while fn returns true. func (c *Client) forEachAuthorization(ctx context.Context, tx *bolt.Tx, fn func(*platform.Authorization) bool) error { cur := tx.Bucket(authorizationBucket).Cursor() for k, v := cur.First(); k != nil; k, v = cur.Next() { a := &platform.Authorization{} - if err := json.Unmarshal(v, a); err != nil { + + if err := decodeAuthorization(v, a); err != nil { return err } if err := c.setUserOnAuthorization(ctx, tx, a); err != nil { @@ -297,7 +320,7 @@ func (c *Client) updateAuthorization(ctx context.Context, tx *bolt.Tx, id platfo } a.Status = status - b, err := json.Marshal(a) + b, err := encodeAuthorization(a) if err != nil { return err } diff --git a/http/swagger.yml b/http/swagger.yml index 9cb40d02fe..ede3a46ca0 100644 --- a/http/swagger.yml +++ b/http/swagger.yml @@ -901,7 +901,7 @@ paths: patch: tags: - Authorizations - summary: enable/disable authorization + summary: update authorization to be active or inactive. requests using an inactive authorization will be rejected. requestBody: description: authorization to update to apply required: true @@ -918,7 +918,7 @@ paths: description: ID of authorization to update responses: '200': - description: the enabled or disabled authorization + description: the active or inactie authorization content: application/json: schema: @@ -1846,9 +1846,13 @@ components: id: readOnly: true type: string - disabled: - description: if true the token has been disabled and requests will be rejected. - type: boolean + status: + description: if inactive the token is inactive and requests using the token will be rejected. + default: active + type: string + enum: + - active + - inactive token: readOnly: true type: string diff --git a/status.go b/status.go new file mode 100644 index 0000000000..692df54632 --- /dev/null +++ b/status.go @@ -0,0 +1,11 @@ +package platform + +// Status defines if a resource is active or inactive. +type Status string + +const ( + // Active status means that the resource can be used. + Active Status = "active" + // Inactive status means that the resource cannot be used. + Inactive Status = "inactive" +) diff --git a/testing/auth.go b/testing/auth.go index 09f863b9af..d872778f15 100644 --- a/testing/auth.go +++ b/testing/auth.go @@ -92,6 +92,7 @@ func CreateAuthorization( ID: idFromString(t, authOneID), UserID: idFromString(t, userOneID), Token: "rand", + Status: platform.Active, User: "cooluser", Permissions: []platform.Permission{ platform.CreateUserPermission, @@ -146,6 +147,7 @@ func CreateAuthorization( ID: idFromString(t, authOneID), UserID: idFromString(t, userOneID), User: "cooluser", + Status: platform.Active, Token: "supersecret", Permissions: []platform.Permission{ platform.CreateUserPermission, @@ -155,6 +157,7 @@ func CreateAuthorization( { ID: idFromString(t, authTwoID), UserID: idFromString(t, userTwoID), + Status: platform.Active, User: "regularuser", Token: "rand", Permissions: []platform.Permission{ @@ -254,6 +257,7 @@ func FindAuthorizationByID( ID: idFromString(t, authTwoID), UserID: idFromString(t, userTwoID), User: "regularuser", + Status: platform.Active, Token: "rand2", Permissions: []platform.Permission{ platform.CreateUserPermission, @@ -324,6 +328,7 @@ func FindAuthorizationByToken( ID: idFromString(t, authOneID), UserID: idFromString(t, userOneID), Token: "rand1", + Status: platform.Inactive, Permissions: []platform.Permission{ platform.CreateUserPermission, platform.DeleteUserPermission, @@ -364,6 +369,7 @@ func FindAuthorizationByToken( authorization: &platform.Authorization{ ID: idFromString(t, authOneID), UserID: idFromString(t, userOneID), + Status: platform.Inactive, User: "cooluser", Token: "rand1", Permissions: []platform.Permission{ @@ -446,6 +452,7 @@ func FindAuthorizations( { ID: idFromString(t, authTwoID), UserID: idFromString(t, userTwoID), + Status: platform.Active, Token: "rand2", Permissions: []platform.Permission{ platform.CreateUserPermission, @@ -461,6 +468,7 @@ func FindAuthorizations( UserID: idFromString(t, userOneID), User: "cooluser", Token: "rand1", + Status: platform.Active, Permissions: []platform.Permission{ platform.CreateUserPermission, platform.DeleteUserPermission, @@ -471,6 +479,7 @@ func FindAuthorizations( UserID: idFromString(t, userTwoID), User: "regularuser", Token: "rand2", + Status: platform.Active, Permissions: []platform.Permission{ platform.CreateUserPermission, }, @@ -496,6 +505,7 @@ func FindAuthorizations( ID: idFromString(t, authOneID), UserID: idFromString(t, userOneID), Token: "rand1", + Status: platform.Active, Permissions: []platform.Permission{ platform.CreateUserPermission, platform.DeleteUserPermission, @@ -528,6 +538,7 @@ func FindAuthorizations( ID: idFromString(t, authOneID), UserID: idFromString(t, userOneID), User: "cooluser", + Status: platform.Active, Token: "rand1", Permissions: []platform.Permission{ platform.CreateUserPermission, @@ -538,6 +549,7 @@ func FindAuthorizations( ID: idFromString(t, authThreeID), UserID: idFromString(t, userOneID), User: "cooluser", + Status: platform.Active, Token: "rand3", Permissions: []platform.Permission{ platform.DeleteUserPermission, @@ -605,6 +617,7 @@ func FindAuthorizations( UserID: idFromString(t, userTwoID), User: "regularuser", Token: "rand2", + Status: platform.Active, Permissions: []platform.Permission{ platform.CreateUserPermission, }, @@ -710,6 +723,7 @@ func DeleteAuthorization( ID: idFromString(t, authTwoID), UserID: idFromString(t, userTwoID), User: "regularuser", + Status: platform.Active, Token: "rand2", Permissions: []platform.Permission{ platform.CreateUserPermission, @@ -762,6 +776,7 @@ func DeleteAuthorization( UserID: idFromString(t, userOneID), User: "cooluser", Token: "rand1", + Status: platform.Active, Permissions: []platform.Permission{ platform.CreateUserPermission, platform.DeleteUserPermission, @@ -772,6 +787,7 @@ func DeleteAuthorization( UserID: idFromString(t, userTwoID), User: "regularuser", Token: "rand2", + Status: platform.Active, Permissions: []platform.Permission{ platform.CreateUserPermission, }, diff --git a/zap/auth_service.go b/zap/auth_service.go index 0dd978ea0a..fa2cdcd82b 100644 --- a/zap/auth_service.go +++ b/zap/auth_service.go @@ -8,6 +8,8 @@ import ( "github.com/influxdata/platform" ) +var _ platform.AuthorizationService = (*AuthorizationService)(nil) + // AuthorizationService manages authorizations. type AuthorizationService struct { Logger *zap.Logger @@ -68,3 +70,14 @@ func (s *AuthorizationService) DeleteAuthorization(ctx context.Context, id platf return s.AuthorizationService.DeleteAuthorization(ctx, id) } + +// SetAuthorizationStatus updates an authorization's status and logs any errors. +func (s *AuthorizationService) SetAuthorizationStatus(ctx context.Context, id platform.ID, status platform.Status) (err error) { + defer func() { + if err != nil { + s.Logger.Info("error updating authorization", zap.Error(err)) + } + }() + + return s.AuthorizationService.SetAuthorizationStatus(ctx, id, status) +}