Skip to content

Commit

Permalink
Group permissions api relationships (#324)
Browse files Browse the repository at this point in the history
* init events

Signed-off-by: Bailin He <[email protected]>

* add and remove members

Signed-off-by: Bailin He <[email protected]>

* group members replace

Signed-off-by: Bailin He <[email protected]>

* add tests

Signed-off-by: Bailin He <[email protected]>

* applied review suggestions

* enhance rollback errors
* use diff for put
* use permissions-api mock

Signed-off-by: Bailin He <[email protected]>

* Update internal/api/httpsrv/handler.go

Co-authored-by: Mike Mason <[email protected]>
Signed-off-by: Bailin He <[email protected]>

---------

Signed-off-by: Bailin He <[email protected]>
Signed-off-by: Bailin He <[email protected]>
Co-authored-by: Mike Mason <[email protected]>
  • Loading branch information
bailinhe and mikemrm authored Oct 7, 2024
1 parent f7ab15a commit 0cb0908
Show file tree
Hide file tree
Showing 17 changed files with 917 additions and 101 deletions.
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,18 @@ audit.log
identity-api
!chart/identity-api

# vscode stuff
.vscode/*
.vscode/settings.json
!.vscode/tasks.json
!.vscode/extensions.json
!.vscode/*.code-snippets

# Local History for Visual Studio Code
.history/

# Built Visual Studio Code Extensions
*.vsix

# binary files
tmp
23 changes: 18 additions & 5 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import (
"go.infratographer.com/x/crdbx"
"go.infratographer.com/x/echojwtx"
"go.infratographer.com/x/echox"
eventsx "go.infratographer.com/x/events"
"go.infratographer.com/x/otelx"
"go.infratographer.com/x/versionx"
"go.uber.org/zap"

"go.infratographer.com/identity-api/internal/api/httpsrv"
"go.infratographer.com/identity-api/internal/auditx"
"go.infratographer.com/identity-api/internal/config"
"go.infratographer.com/identity-api/internal/events"
"go.infratographer.com/identity-api/internal/fositex"
"go.infratographer.com/identity-api/internal/jwks"
"go.infratographer.com/identity-api/internal/oauth2"
Expand All @@ -42,9 +44,7 @@ var serveCmd = &cobra.Command{
},
}

var (
defaultListen = ":8080"
)
var defaultListen = ":8080"

func init() {
rootCmd.AddCommand(serveCmd)
Expand All @@ -56,6 +56,7 @@ func init() {
echox.MustViperFlags(v, flags, defaultListen)
otelx.MustViperFlags(v, flags)
auditx.MustViperFlags(v, flags)
eventsx.MustViperFlags(v, flags, appName)
}

func serve(ctx context.Context) {
Expand All @@ -73,8 +74,18 @@ func serve(ctx context.Context) {
defer auditCloseFn() //nolint:errcheck // Not needed to check returned error.
}

perms, err := permissions.New(config.Config.Permissions,
nc, err := eventsx.NewNATSConnection(
config.Config.Events.NATS,
eventsx.WithNATSLogger(logger),
)
if err != nil {
logger.Fatal("failed to initialize NATS connection", zap.Error(err))
}

perms, err := permissions.New(
config.Config.Permissions,
permissions.WithLogger(logger),
permissions.WithEventsPublisher(nc),
)
if err != nil {
logger.Fatal("failed to initialize permissions", zap.Error(err))
Expand Down Expand Up @@ -113,7 +124,9 @@ func serve(ctx context.Context) {
oauth2.NewClientCredentialsHandlerFactory,
)

apiHandler, err := httpsrv.NewAPIHandler(storageEngine, auditMiddleware, perms.Middleware())
es := events.NewEvents(events.WithLogger(logger.Desugar()))

apiHandler, err := httpsrv.NewAPIHandler(storageEngine, es, auditMiddleware, perms.Middleware())
if err != nil {
logger.Fatal("error initializing API server: %s", err)
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ require (
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
Expand Down
3 changes: 3 additions & 0 deletions internal/api/httpsrv/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ var (
status: http.StatusNotFound,
message: "not found",
}

// ErrDBRollbackFailed is returned when a database rollback fails
ErrDBRollbackFailed = errors.New("failed to rollback database transaction")
)

func permissionsError(err error) error {
Expand Down
25 changes: 22 additions & 3 deletions internal/api/httpsrv/handler.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package httpsrv

import (
"context"
"fmt"
"net/http"

"github.com/labstack/echo/v4"
"github.com/metal-toolbox/auditevent/middleware/echoaudit"

"go.infratographer.com/identity-api/internal/events"
"go.infratographer.com/identity-api/internal/storage"
)

Expand Down Expand Up @@ -43,7 +46,8 @@ func storageMiddleware(engine storage.Engine) echo.MiddlewareFunc {

// apiHandler represents an API handler.
type apiHandler struct {
engine storage.Engine
engine storage.Engine
eventService events.Service
}

// APIHandler represents an identity-api management API handler.
Expand All @@ -55,14 +59,18 @@ type APIHandler struct {
}

// NewAPIHandler creates an API handler with the given storage engine.
func NewAPIHandler(engine storage.Engine, amw *echoaudit.Middleware, middleware ...echo.MiddlewareFunc) (*APIHandler, error) {
func NewAPIHandler(
engine storage.Engine, es events.Service,
amw *echoaudit.Middleware, middleware ...echo.MiddlewareFunc,
) (*APIHandler, error) {
validationMiddleware, err := oapiValidationMiddleware()
if err != nil {
return nil, err
}

handler := apiHandler{
engine: engine,
engine: engine,
eventService: es,
}

out := &APIHandler{
Expand Down Expand Up @@ -94,3 +102,14 @@ func (h *APIHandler) Routes(rg *echo.Group) {

RegisterHandlers(rg, strictHandler)
}

func (h *apiHandler) rollbackAndReturnError(ctx context.Context, httpcode int, msg string) *echo.HTTPError {
if err := h.engine.RollbackContext(ctx); err != nil {
return echo.NewHTTPError(
http.StatusInternalServerError,
fmt.Errorf("%s and %w", msg, ErrDBRollbackFailed),
).SetInternal(err)
}

return echo.NewHTTPError(httpcode, msg)
}
31 changes: 30 additions & 1 deletion internal/api/httpsrv/handler_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ func (h *apiHandler) CreateGroup(ctx context.Context, req CreateGroupRequestObje
return nil, err
}

if err := h.eventService.CreateGroup(ctx, ownerID, id); err != nil {
resperr := h.rollbackAndReturnError(ctx, http.StatusInternalServerError, "failed to create group in permissions API")
return nil, resperr
}

groupResp, err := g.ToV1Group()
if err != nil {
return nil, err
Expand Down Expand Up @@ -230,7 +235,7 @@ func (h *apiHandler) DeleteGroup(ctx context.Context, req DeleteGroupRequestObje
return nil, permissionsError(err)
}

err := h.engine.DeleteGroup(ctx, gid)
group, err := h.engine.GetGroupByID(ctx, gid)
if err != nil {
if errors.Is(err, types.ErrNotFound) {
err = echo.NewHTTPError(
Expand All @@ -241,12 +246,36 @@ func (h *apiHandler) DeleteGroup(ctx context.Context, req DeleteGroupRequestObje
return nil, err
}

return nil, err
}

mc, err := h.engine.GroupMembersCount(ctx, gid)
if err != nil {
return nil, err
}

if mc > 0 {
err := echo.NewHTTPError(
http.StatusBadRequest,
fmt.Sprintf("cannot delete group %s: still has members", gid),
)

return nil, err
}

err = h.engine.DeleteGroup(ctx, group.ID)
if err != nil {
if errors.Is(err, types.ErrInvalidArgument) {
return nil, echo.NewHTTPError(http.StatusBadRequest, err.Error())
}

return nil, err
}

if err := h.eventService.DeleteGroup(ctx, group.OwnerID, group.ID); err != nil {
resperr := h.rollbackAndReturnError(ctx, http.StatusInternalServerError, "failed to remove group in permissions API")
return nil, resperr
}

return DeleteGroup200JSONResponse{true}, nil
}
29 changes: 25 additions & 4 deletions internal/api/httpsrv/handler_group_members.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,19 @@ func (h *apiHandler) AddGroupMembers(ctx context.Context, req AddGroupMembersReq
return nil, permissionsError(err)
}

if err := h.engine.AddMembers(ctx, gid, reqbody.MemberIDs...); err != nil {
if err := h.engine.AddGroupMembers(ctx, gid, reqbody.MemberIDs...); err != nil {
if errors.Is(err, types.ErrNotFound) {
err = echo.NewHTTPError(http.StatusNotFound, err.Error())
}

return nil, err
}

if err := h.eventService.AddGroupMembers(ctx, gid, reqbody.MemberIDs...); err != nil {
resperr := h.rollbackAndReturnError(ctx, http.StatusInternalServerError, "failed to add group members in permissions API")
return nil, resperr
}

return AddGroupMembers200JSONResponse{Success: true}, nil
}

Expand All @@ -77,7 +82,7 @@ func (h *apiHandler) ListGroupMembers(ctx context.Context, req ListGroupMembersR
return nil, permissionsError(err)
}

members, err := h.engine.ListMembers(ctx, gid, req.Params)
members, err := h.engine.ListGroupMembers(ctx, gid, req.Params)
if err != nil {
if errors.Is(err, types.ErrNotFound) {
err = echo.NewHTTPError(http.StatusNotFound, err.Error())
Expand Down Expand Up @@ -133,14 +138,19 @@ func (h *apiHandler) RemoveGroupMember(ctx context.Context, req RemoveGroupMembe
return nil, permissionsError(err)
}

if err := h.engine.RemoveMember(ctx, gid, sid); err != nil {
if err := h.engine.RemoveGroupMember(ctx, gid, sid); err != nil {
if errors.Is(err, types.ErrNotFound) {
err = echo.NewHTTPError(http.StatusNotFound, err.Error())
}

return nil, err
}

if err := h.eventService.RemoveGroupMembers(ctx, gid, sid); err != nil {
resperr := h.rollbackAndReturnError(ctx, http.StatusInternalServerError, "failed to remove group member in permissions API")
return nil, resperr
}

return RemoveGroupMember200JSONResponse{true}, nil
}

Expand Down Expand Up @@ -173,14 +183,25 @@ func (h *apiHandler) ReplaceGroupMembers(ctx context.Context, req ReplaceGroupMe
return nil, permissionsError(err)
}

if err := h.engine.ReplaceMembers(ctx, gid, reqbody.MemberIDs...); err != nil {
add, rm, err := h.engine.ReplaceGroupMembers(ctx, gid, reqbody.MemberIDs...)
if err != nil {
if errors.Is(err, types.ErrNotFound) {
err = echo.NewHTTPError(http.StatusNotFound, err.Error())
}

return nil, err
}

if err := h.eventService.RemoveGroupMembers(ctx, gid, rm...); err != nil {
resperr := h.rollbackAndReturnError(ctx, http.StatusInternalServerError, "failed to replace group members in permissions API")
return nil, resperr
}

if err := h.eventService.AddGroupMembers(ctx, gid, add...); err != nil {
resperr := h.rollbackAndReturnError(ctx, http.StatusInternalServerError, "failed to replace group members in permissions API")
return nil, resperr
}

return ReplaceGroupMembers200JSONResponse{true}, nil
}

Expand Down
Loading

0 comments on commit 0cb0908

Please sign in to comment.