Files
probo/pkg/server/api/console/v1/device_resolvers.go
Ludovic Vielle ad615a47a0 Show posture values and report history
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>
2026-07-29 16:27:23 +02:00

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 }
)