Pass/fail was the main device UI signal, but operators need the agent's observed value. Expose a formatted value per check, show current postures on the device page, and replace the Postures tab with paginated report history grouped by agent push time. Status stays in the model for later rulesets. Signed-off-by: Ludovic Vielle <ludovic@probo.com>
281 lines
8.6 KiB
Go
281 lines
8.6 KiB
Go
package console_v1
|
|
|
|
// This file will be automatically regenerated based on the schema, any resolver
|
|
// implementations
|
|
// will be copied through when generating and any unknown code will be moved to the end.
|
|
// Code generated by github.com/99designs/gqlgen version v0.17.94
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/vikstrous/dataloadgen"
|
|
"go.gearno.de/kit/log"
|
|
"go.probo.inc/probo/pkg/coredata"
|
|
"go.probo.inc/probo/pkg/iam"
|
|
"go.probo.inc/probo/pkg/itam"
|
|
"go.probo.inc/probo/pkg/page"
|
|
"go.probo.inc/probo/pkg/server/api/authn"
|
|
"go.probo.inc/probo/pkg/server/api/authz"
|
|
"go.probo.inc/probo/pkg/server/api/console/v1/dataloader"
|
|
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
|
|
"go.probo.inc/probo/pkg/server/api/console/v1/types"
|
|
"go.probo.inc/probo/pkg/server/gqlutils"
|
|
)
|
|
|
|
// Owner is the resolver for the owner field.
|
|
func (r *deviceResolver) Owner(ctx context.Context, obj *types.Device) (*types.Profile, error) {
|
|
if obj.Owner == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
if _, err := r.authorize(ctx, obj.Owner.ID, iam.ActionMembershipProfileGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
loaders := dataloader.FromContext(ctx)
|
|
|
|
owner, err := loaders.Profile.Load(ctx, obj.Owner.ID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get device owner profile", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewProfile(owner), nil
|
|
}
|
|
|
|
// LatestPostures is the resolver for the Device.latestPostures field.
|
|
func (r *deviceResolver) LatestPostures(ctx context.Context, obj *types.Device) ([]*types.DevicePosture, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, itam.ActionDevicePostureList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
postures, err := r.itam.GetLatestPostures(ctx, scope, obj.ID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot load latest device postures", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDevicePostures(postures), nil
|
|
}
|
|
|
|
// PostureReports is the resolver for the postureReports field.
|
|
func (r *deviceResolver) PostureReports(ctx context.Context, obj *types.Device, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DevicePostureReportOrderBy) (*types.DevicePostureReportConnection, error) {
|
|
scope, err := r.authorize(ctx, obj.ID, itam.ActionDevicePostureList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.DevicePostureReportOrderField]{
|
|
Field: coredata.DevicePostureReportOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.DevicePostureReportOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
p, err := r.itam.ListPostureReports(ctx, scope, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list device posture reports", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewDevicePostureReportConnection(p, r, obj.ID), nil
|
|
}
|
|
|
|
// TotalCount is the resolver for the DeviceConnection.totalCount field.
|
|
func (r *deviceConnectionResolver) TotalCount(ctx context.Context, obj *types.DeviceConnection) (int, error) {
|
|
scope, err := r.authorize(ctx, obj.ParentID, itam.ActionDeviceList)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
switch obj.Resolver.(type) {
|
|
case *organizationResolver:
|
|
count, err := r.itam.CountForOrganizationID(ctx, scope, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count devices", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *devicePostureReportConnectionResolver) TotalCount(ctx context.Context, obj *types.DevicePostureReportConnection) (int, error) {
|
|
scope, err := r.authorize(ctx, obj.ParentID, itam.ActionDevicePostureList)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
count, err := r.itam.CountPostureReports(ctx, scope, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count device posture reports", log.Error(err))
|
|
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// EnrollDevice is the resolver for the enrollDevice field.
|
|
// SkipAssumptionCheck: self-enrollment from /enroll runs before the viewer
|
|
// assumes the target organization.
|
|
func (r *mutationResolver) EnrollDevice(ctx context.Context, input types.EnrollDeviceInput) (*types.CreateDevicePayload, error) {
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
scope, err := r.authorize(
|
|
ctx,
|
|
input.OrganizationID,
|
|
itam.ActionDeviceEnroll,
|
|
authz.WithSkipAssumptionCheck(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result, err := r.itam.EnrollDevice(
|
|
ctx, scope,
|
|
itam.EnrollDeviceRequest{
|
|
OrganizationID: input.OrganizationID,
|
|
IdentityID: identity.ID,
|
|
},
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot enroll device", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
urls, err := buildEnrollmentURLs(r.baseURL, result.EnrollmentToken)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot build enrollment URLs", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.CreateDevicePayload{
|
|
Device: types.NewDevice(result.Device),
|
|
EnrollmentToken: result.EnrollmentToken,
|
|
ServerURL: urls.ServerURL,
|
|
EnrollmentURL: urls.EnrollmentURL,
|
|
}, nil
|
|
}
|
|
|
|
// CreateDevice is the resolver for the createDevice field.
|
|
func (r *mutationResolver) CreateDevice(ctx context.Context, input types.CreateDeviceInput) (*types.CreateDevicePayload, error) {
|
|
scope, err := r.authorize(ctx, input.OrganizationID, itam.ActionDeviceCreate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result, err := r.itam.CreateDevice(
|
|
ctx, scope,
|
|
itam.CreateDeviceRequest{
|
|
OrganizationID: input.OrganizationID,
|
|
OwnerID: input.OwnerID,
|
|
},
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot create device", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
urls, err := buildEnrollmentURLs(r.baseURL, result.EnrollmentToken)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot build enrollment URLs", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.CreateDevicePayload{
|
|
Device: types.NewDevice(result.Device),
|
|
EnrollmentToken: result.EnrollmentToken,
|
|
ServerURL: urls.ServerURL,
|
|
EnrollmentURL: urls.EnrollmentURL,
|
|
}, nil
|
|
}
|
|
|
|
// RevokeDevice is the resolver for the revokeDevice field.
|
|
func (r *mutationResolver) RevokeDevice(ctx context.Context, input types.RevokeDeviceInput) (*types.RevokeDevicePayload, error) {
|
|
scope, err := r.authorize(ctx, input.DeviceID, itam.ActionDeviceRevoke)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
d, err := r.itam.RevokeDevice(ctx, scope, input.DeviceID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot revoke device", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.RevokeDevicePayload{Device: types.NewDevice(d)}, nil
|
|
}
|
|
|
|
// SetDeviceOwner is the resolver for the setDeviceOwner field.
|
|
func (r *mutationResolver) SetDeviceOwner(ctx context.Context, input types.SetDeviceOwnerInput) (*types.SetDeviceOwnerPayload, error) {
|
|
scope, err := r.authorize(ctx, input.DeviceID, itam.ActionDeviceAssignOwner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
d, err := r.itam.SetDeviceOwner(ctx, scope, input.DeviceID, input.OwnerID)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot set device owner", log.Error(err))
|
|
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.SetDeviceOwnerPayload{Device: types.NewDevice(d)}, nil
|
|
}
|
|
|
|
// Device returns schema.DeviceResolver implementation.
|
|
func (r *Resolver) Device() schema.DeviceResolver { return &deviceResolver{r} }
|
|
|
|
// DeviceConnection returns schema.DeviceConnectionResolver implementation.
|
|
func (r *Resolver) DeviceConnection() schema.DeviceConnectionResolver {
|
|
return &deviceConnectionResolver{r}
|
|
}
|
|
|
|
// DevicePostureReportConnection returns schema.DevicePostureReportConnectionResolver implementation.
|
|
func (r *Resolver) DevicePostureReportConnection() schema.DevicePostureReportConnectionResolver {
|
|
return &devicePostureReportConnectionResolver{r}
|
|
}
|
|
|
|
type (
|
|
deviceResolver struct{ *Resolver }
|
|
deviceConnectionResolver struct{ *Resolver }
|
|
devicePostureReportConnectionResolver struct{ *Resolver }
|
|
)
|