From b2993b3259953f1056e6ac3d484bedc2e11113ed Mon Sep 17 00:00:00 2001 From: anatoleam Date: Tue, 12 Dec 2023 15:04:43 +0100 Subject: [PATCH 1/3] Create Entitlement Endpoint --- .../entitlements/entitlements.create.go | 100 ++++++++++++++++++ .../v3/routes/entitlements/entitlements.go | 29 +++++ internal/api/rest/v3/routes/root.go | 2 + 3 files changed, 131 insertions(+) create mode 100644 internal/api/rest/v3/routes/entitlements/entitlements.create.go create mode 100644 internal/api/rest/v3/routes/entitlements/entitlements.go diff --git a/internal/api/rest/v3/routes/entitlements/entitlements.create.go b/internal/api/rest/v3/routes/entitlements/entitlements.create.go new file mode 100644 index 00000000..3340cce4 --- /dev/null +++ b/internal/api/rest/v3/routes/entitlements/entitlements.create.go @@ -0,0 +1,100 @@ +package entitlements + +import ( + "encoding/json" + "time" + + "github.com/seventv/api/internal/api/rest/middleware" + "github.com/seventv/api/internal/api/rest/rest" + "github.com/seventv/api/internal/global" + "github.com/seventv/common/errors" + "github.com/seventv/common/mongo" + "github.com/seventv/common/structures/v3" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type create struct { + gctx global.Context +} + +func newCreate(gctx global.Context) rest.Route { + return &create{gctx} +} + +func (r *create) Config() rest.RouteConfig { + return rest.RouteConfig{ + URI: "", + Method: rest.POST, + Middleware: []rest.Middleware{ + middleware.Auth(r.gctx, true), + }, + } +} + +func (r *create) Handler(ctx *rest.Ctx) rest.APIError { + actor, ok := ctx.GetActor() + if !ok || !actor.HasPermission(structures.RolePermissionManageEntitlements) { + return errors.ErrInsufficientPrivilege() + } + + var body createEntitlementData + if err := json.Unmarshal(ctx.Request.Body(), &body); err != nil { + return errors.ErrInvalidRequest() + } + + // Validate ROLE entitlements + // Needs superadmin + if body.Kind == structures.EntitlementKindRole && !actor.HasPermission(structures.RolePermissionSuperAdministrator) { + return errors.ErrInsufficientPrivilege() + } + + id := primitive.NewObjectIDFromTimestamp(time.Now()) + + eb := structures.NewEntitlementBuilder(structures.Entitlement[structures.EntitlementDataBase]{ + ID: id, + }). + SetKind(body.Kind). + SetUserID(body.UserID). + SetCondition(body.Condition). + SetData(structures.EntitlementDataBase{ + RefID: body.ObjectRef, + }). + SetApp(structures.EntitlementApp{ + Name: body.AppName, + ActorID: actor.ID.Hex(), + State: body.AppState, + }) + + // Set claim (only if UserID empty) + if body.Claim != nil && body.UserID.IsZero() { + u, _ := r.gctx.Inst().Loaders.UserByConnectionID(body.Claim.Platform).Load(body.Claim.ID) + + if u.ID.IsZero() { + eb.SetClaim(structures.EntitlementClaim{ + Platform: body.Claim.Platform, + ID: body.Claim.ID, + }) + } else { + // if the user was found by claim, assign the entitlement directly + eb.SetUserID(u.ID) + } + } + + if _, err := r.gctx.Inst().Mongo.Collection(mongo.CollectionNameEntitlements).InsertOne(ctx, eb.Entitlement); err != nil { + ctx.Log().Errorw("mongo, couldn't create entitlement") + + return errors.ErrInternalServerError() + } + + return ctx.JSON(rest.OK, eb.Entitlement) +} + +type createEntitlementData struct { + Kind structures.EntitlementKind `json:"kind"` + ObjectRef primitive.ObjectID `json:"object_ref"` + UserID primitive.ObjectID `json:"user_id"` + Condition structures.EntitlementCondition `json:"condition"` + Claim *structures.EntitlementClaim `json:"claim"` + AppName string `json:"app_name"` + AppState map[string]any `json:"app_state"` +} diff --git a/internal/api/rest/v3/routes/entitlements/entitlements.go b/internal/api/rest/v3/routes/entitlements/entitlements.go new file mode 100644 index 00000000..a7660d90 --- /dev/null +++ b/internal/api/rest/v3/routes/entitlements/entitlements.go @@ -0,0 +1,29 @@ +package entitlements + +import ( + "github.com/seventv/api/internal/api/rest/rest" + "github.com/seventv/api/internal/global" +) + +type entitlementRoute struct { + gctx global.Context +} + +func New(gctx global.Context) rest.Route { + return &entitlementRoute{gctx} +} + +func (r *entitlementRoute) Config() rest.RouteConfig { + return rest.RouteConfig{ + URI: "/entitlements", + Method: rest.GET, + Children: []rest.Route{ + newCreate(r.gctx), + }, + Middleware: []rest.Middleware{}, + } +} + +func (r *entitlementRoute) Handler(ctx *rest.Ctx) rest.APIError { + return nil +} diff --git a/internal/api/rest/v3/routes/root.go b/internal/api/rest/v3/routes/root.go index 23f28dcc..6d962dc3 100644 --- a/internal/api/rest/v3/routes/root.go +++ b/internal/api/rest/v3/routes/root.go @@ -11,6 +11,7 @@ import ( "github.com/seventv/api/internal/api/rest/v3/routes/docs" emote_sets "github.com/seventv/api/internal/api/rest/v3/routes/emote-sets" "github.com/seventv/api/internal/api/rest/v3/routes/emotes" + "github.com/seventv/api/internal/api/rest/v3/routes/entitlements" "github.com/seventv/api/internal/api/rest/v3/routes/users" "github.com/seventv/api/internal/global" ) @@ -36,6 +37,7 @@ func (r *Route) Config() rest.RouteConfig { emotes.New(r.Ctx), emote_sets.New(r.Ctx), users.New(r.Ctx), + entitlements.New(r.Ctx), }, Middleware: []rest.Middleware{ middleware.SetCacheControl(r.Ctx, 30, nil), From 56236949d1549378c2850412bd1e082ea2aaebd2 Mon Sep 17 00:00:00 2001 From: anatoleam Date: Tue, 12 Dec 2023 15:09:10 +0100 Subject: [PATCH 2/3] update common --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 21df57fe..78c36a32 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/nats-io/nats.go v1.28.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.14.0 - github.com/seventv/common v0.0.0-20231212092216-2658fa1b97ba + github.com/seventv/common v0.0.0-20231212140741-9412bffd7525 github.com/seventv/compactdisc v0.0.0-20221006190906-ccfe99954e48 github.com/seventv/image-processor/go v0.0.0-20221128171540-d050701ac324 github.com/seventv/message-queue/go v0.0.0-20231201171845-1bb9d5db6881 diff --git a/go.sum b/go.sum index 04cec794..093d0662 100644 --- a/go.sum +++ b/go.sum @@ -352,6 +352,8 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/seventv/common v0.0.0-20231212092216-2658fa1b97ba h1:byOf2HOo7LZyyfg8j7LePLtODt1vBjOk4D6dBGNETo8= github.com/seventv/common v0.0.0-20231212092216-2658fa1b97ba/go.mod h1:jHHFe3uNMyzb/ReDqMvaI/A1euvV/PW/G+2PhcWQqWw= +github.com/seventv/common v0.0.0-20231212140741-9412bffd7525 h1:3LdJj533sxENKoUkJLx3p7WHj0YuLko+CyitAYvC5pk= +github.com/seventv/common v0.0.0-20231212140741-9412bffd7525/go.mod h1:jHHFe3uNMyzb/ReDqMvaI/A1euvV/PW/G+2PhcWQqWw= github.com/seventv/compactdisc v0.0.0-20221006190906-ccfe99954e48 h1:IqWrtt/yob45YnOQ5Wwkbf8qP22eKVtg0WzfyEkGnFg= github.com/seventv/compactdisc v0.0.0-20221006190906-ccfe99954e48/go.mod h1:T+ldp0YQe03s44+A5HHHI/jB3ZmWqOIaNouEqAS+1Dk= github.com/seventv/image-processor/go v0.0.0-20221128171540-d050701ac324 h1:iU3wWepRTbkNoTAPR23m6TAW6Yb9pOMCYVr0K++OBAw= From 67f4588633723c4fb9f9b76956cdc8da42fa1bc2 Mon Sep 17 00:00:00 2001 From: anatoleam Date: Tue, 12 Dec 2023 15:40:43 +0100 Subject: [PATCH 3/3] check entitlement claims upon user creation --- data/mutate/user.delete.mutation.go | 2 -- go.mod | 2 +- go.sum | 2 ++ .../api/rest/v3/routes/auth/auth.route.go | 33 +++++++++++++++++-- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/data/mutate/user.delete.mutation.go b/data/mutate/user.delete.mutation.go index 3d7a5699..d6e1f79c 100644 --- a/data/mutate/user.delete.mutation.go +++ b/data/mutate/user.delete.mutation.go @@ -2,7 +2,6 @@ package mutate import ( "context" - "fmt" "github.com/seventv/common/errors" "github.com/seventv/common/mongo" @@ -22,7 +21,6 @@ func (m *Mutate) DeleteUser(ctx context.Context, opt DeleteUserOptions) (int, er if opt.Actor.GetHighestRole().Position <= opt.Victim.GetHighestRole().Position { return 0, errors.ErrInsufficientPrivilege() } - fmt.Println(opt.Victim, opt.Actor) // Delete all EUD for _, query := range userDeleteQueries(opt.Victim.ID) { diff --git a/go.mod b/go.mod index 78c36a32..1e22c55d 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/nats-io/nats.go v1.28.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.14.0 - github.com/seventv/common v0.0.0-20231212140741-9412bffd7525 + github.com/seventv/common v0.0.0-20231212143655-048a247f3aa4 github.com/seventv/compactdisc v0.0.0-20221006190906-ccfe99954e48 github.com/seventv/image-processor/go v0.0.0-20221128171540-d050701ac324 github.com/seventv/message-queue/go v0.0.0-20231201171845-1bb9d5db6881 diff --git a/go.sum b/go.sum index 093d0662..88b96b69 100644 --- a/go.sum +++ b/go.sum @@ -354,6 +354,8 @@ github.com/seventv/common v0.0.0-20231212092216-2658fa1b97ba h1:byOf2HOo7LZyyfg8 github.com/seventv/common v0.0.0-20231212092216-2658fa1b97ba/go.mod h1:jHHFe3uNMyzb/ReDqMvaI/A1euvV/PW/G+2PhcWQqWw= github.com/seventv/common v0.0.0-20231212140741-9412bffd7525 h1:3LdJj533sxENKoUkJLx3p7WHj0YuLko+CyitAYvC5pk= github.com/seventv/common v0.0.0-20231212140741-9412bffd7525/go.mod h1:jHHFe3uNMyzb/ReDqMvaI/A1euvV/PW/G+2PhcWQqWw= +github.com/seventv/common v0.0.0-20231212143655-048a247f3aa4 h1:TNDOnWqZfRJSdnjD4jq8RueLQD8rL52+6HgPXg9W5s0= +github.com/seventv/common v0.0.0-20231212143655-048a247f3aa4/go.mod h1:jHHFe3uNMyzb/ReDqMvaI/A1euvV/PW/G+2PhcWQqWw= github.com/seventv/compactdisc v0.0.0-20221006190906-ccfe99954e48 h1:IqWrtt/yob45YnOQ5Wwkbf8qP22eKVtg0WzfyEkGnFg= github.com/seventv/compactdisc v0.0.0-20221006190906-ccfe99954e48/go.mod h1:T+ldp0YQe03s44+A5HHHI/jB3ZmWqOIaNouEqAS+1Dk= github.com/seventv/image-processor/go v0.0.0-20221128171540-d050701ac324 h1:iU3wWepRTbkNoTAPR23m6TAW6Yb9pOMCYVr0K++OBAw= diff --git a/internal/api/rest/v3/routes/auth/auth.route.go b/internal/api/rest/v3/routes/auth/auth.route.go index e4091c9e..e70ee2e4 100644 --- a/internal/api/rest/v3/routes/auth/auth.route.go +++ b/internal/api/rest/v3/routes/auth/auth.route.go @@ -170,14 +170,23 @@ func setupUser( platform structures.UserConnectionPlatform, grant auth.OAuth2AuthorizedResponse, ) errors.APIError { + var ( + userID primitive.ObjectID + userConn structures.UserConnection[bson.Raw] + ) + // Create the user if ub.User.ID.IsZero() { + userID = primitive.NewObjectIDFromTimestamp(time.Now()) + + userConn = formatUserConnection(id, platform, b, grant) + ub.User = structures.User{ - ID: primitive.NewObjectIDFromTimestamp(time.Now()), + ID: userID, TokenVersion: 1.0, RoleIDs: []primitive.ObjectID{}, Editors: []structures.UserEditor{}, - Connections: []structures.UserConnection[bson.Raw]{formatUserConnection(id, platform, b, grant)}, + Connections: []structures.UserConnection[bson.Raw]{userConn}, State: structures.UserState{ LastLoginDate: time.Now(), LastVisitDate: time.Now(), @@ -213,6 +222,10 @@ func setupUser( con := formatUserConnection(id, platform, b, grant) ub.AddConnection(con) + userID = ub.User.ID + + userConn = con + // eventapi: dispatch the connection create event gctx.Inst().Events.Dispatch(events.EventTypeUpdateUser, events.ChangeMap{ ID: ub.User.ID, @@ -238,6 +251,22 @@ func setupUser( } } + if !userID.IsZero() && userConn.ID != "" { + // Check entitlement claims + if _, err := gctx.Inst().Mongo.Collection(mongo.CollectionNameEntitlements).UpdateMany(ctx, bson.M{ + "claim": bson.M{"$exists": true}, + "claim.platform": platform, + "claim.id": userConn.ID, + }, bson.M{ + "$unset": bson.M{"claim": 1}, + "$set": bson.M{ + "user_id": userID, + }, + }); err != nil { + ctx.Log().Errorw("mongo, failed to update entitlement claims") + } + } + return nil }