Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

token based report #6216

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions admin/auth_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,12 @@ type IssueMagicAuthTokenOptions struct {
TTL *time.Duration
CreatedByUserID *string
Attributes map[string]any
ResourceType string
ResourceName string
FilterJSON string
Fields []string
State string
DisplayName string
Internal bool
Resources []database.ResourceName
}

// IssueMagicAuthToken generates and persists a new magic auth token for a project.
Expand All @@ -198,13 +197,12 @@ func (s *Service) IssueMagicAuthToken(ctx context.Context, opts *IssueMagicAuthT
ExpiresOn: expiresOn,
CreatedByUserID: opts.CreatedByUserID,
Attributes: opts.Attributes,
ResourceType: opts.ResourceType,
ResourceName: opts.ResourceName,
FilterJSON: opts.FilterJSON,
Fields: opts.Fields,
State: opts.State,
DisplayName: opts.DisplayName,
Internal: opts.Internal,
Resources: opts.Resources,
})
if err != nil {
return nil, err
Expand Down
11 changes: 7 additions & 4 deletions admin/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -655,13 +655,17 @@ type MagicAuthToken struct {
UsedOn time.Time `db:"used_on"`
CreatedByUserID *string `db:"created_by_user_id"`
Attributes map[string]any `db:"attributes"`
ResourceType string `db:"resource_type"`
ResourceName string `db:"resource_name"`
FilterJSON string `db:"filter_json"`
Fields []string `db:"fields"`
State string `db:"state"`
DisplayName string `db:"display_name"`
Internal bool `db:"internal"`
Resources []ResourceName `db:"resources"`
}

type ResourceName struct {
Type string
Name string
}

// MagicAuthTokenWithUser is a MagicAuthToken with additional information about the user who created it.
Expand All @@ -679,13 +683,12 @@ type InsertMagicAuthTokenOptions struct {
ExpiresOn *time.Time
CreatedByUserID *string
Attributes map[string]any
ResourceType string `validate:"required"`
ResourceName string `validate:"required"`
FilterJSON string
Fields []string
State string
DisplayName string
Internal bool
Resources []ResourceName
}

type ReportToken struct {
Expand Down
11 changes: 11 additions & 0 deletions admin/database/postgres/migrations/0057.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ALTER TABLE magic_auth_tokens ADD COLUMN resources JSONB NOT NULL DEFAULT '[]'::jsonb;

UPDATE magic_auth_tokens
SET resources = jsonb_build_array(
jsonb_build_object(
'Type', resource_type,
'Name', resource_name
)
);

ALTER TABLE magic_auth_tokens DROP COLUMN resource_name, DROP COLUMN resource_type;
21 changes: 18 additions & 3 deletions admin/database/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -1177,11 +1177,16 @@ func (c *connection) InsertMagicAuthToken(ctx context.Context, opts *database.In
return nil, err
}

resources, err := json.Marshal(opts.Resources)
if err != nil {
return nil, err
}

res := &magicAuthTokenDTO{}
err = c.getDB(ctx).QueryRowxContext(ctx, `
INSERT INTO magic_auth_tokens (id, secret_hash, secret, secret_encryption_key_id, project_id, expires_on, created_by_user_id, attributes, resource_type, resource_name, filter_json, fields, state, display_name, internal)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING *`,
opts.ID, opts.SecretHash, encSecret, encKeyID, opts.ProjectID, opts.ExpiresOn, opts.CreatedByUserID, opts.Attributes, opts.ResourceType, opts.ResourceName, opts.FilterJSON, opts.Fields, opts.State, opts.DisplayName, opts.Internal,
INSERT INTO magic_auth_tokens (id, secret_hash, secret, secret_encryption_key_id, project_id, expires_on, created_by_user_id, attributes, filter_json, fields, state, display_name, internal, resources)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING *`,
opts.ID, opts.SecretHash, encSecret, encKeyID, opts.ProjectID, opts.ExpiresOn, opts.CreatedByUserID, opts.Attributes, opts.FilterJSON, opts.Fields, opts.State, opts.DisplayName, opts.Internal, resources,
).StructScan(res)
if err != nil {
return nil, parseErr("magic auth token", err)
Expand Down Expand Up @@ -2372,6 +2377,7 @@ type magicAuthTokenDTO struct {
*database.MagicAuthToken
Attributes pgtype.JSON `db:"attributes"`
Fields pgtype.TextArray `db:"fields"`
Resources pgtype.JSONB `db:"resources"`
}

func (c *connection) magicAuthTokenFromDTO(dto *magicAuthTokenDTO, fetchSecret bool) (*database.MagicAuthToken, error) {
Expand All @@ -2383,6 +2389,10 @@ func (c *connection) magicAuthTokenFromDTO(dto *magicAuthTokenDTO, fetchSecret b
if err != nil {
return nil, err
}
err = dto.Resources.AssignTo(&dto.MagicAuthToken.Resources)
if err != nil {
return nil, err
}

if fetchSecret {
dto.MagicAuthToken.Secret, err = c.decrypt(dto.MagicAuthToken.Secret, dto.MagicAuthToken.SecretEncryptionKeyID)
Expand All @@ -2402,6 +2412,7 @@ type magicAuthTokenWithUserDTO struct {
*database.MagicAuthTokenWithUser
Attributes pgtype.JSON `db:"attributes"`
Fields pgtype.TextArray `db:"fields"`
Resources pgtype.JSONB `db:"resources"`
}

func (c *connection) magicAuthTokenWithUserFromDTO(dto *magicAuthTokenWithUserDTO) (*database.MagicAuthTokenWithUser, error) {
Expand All @@ -2413,6 +2424,10 @@ func (c *connection) magicAuthTokenWithUserFromDTO(dto *magicAuthTokenWithUserDT
if err != nil {
return nil, err
}
err = dto.Resources.AssignTo(&dto.MagicAuthToken.Resources)
if err != nil {
return nil, err
}

dto.MagicAuthTokenWithUser.Secret, err = c.decrypt(dto.MagicAuthTokenWithUser.Secret, dto.MagicAuthTokenWithUser.SecretEncryptionKeyID)
if err != nil {
Expand Down
31 changes: 21 additions & 10 deletions admin/server/magic_tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ func (s *Server) IssueMagicAuthToken(ctx context.Context, req *adminv1.IssueMagi
attribute.String("args.organization", req.Organization),
attribute.String("args.project", req.Project),
attribute.String("args.display_name", req.DisplayName),
attribute.String("args.resource_type", req.ResourceType),
attribute.String("args.resource_name", req.ResourceName),
)

proj, err := s.admin.DB.FindProjectByName(ctx, req.Organization, req.Project)
Expand All @@ -49,13 +47,19 @@ func (s *Server) IssueMagicAuthToken(ctx context.Context, req *adminv1.IssueMagi
return nil, status.Error(codes.PermissionDenied, "not allowed to create a magic auth token")
}

resources := make([]database.ResourceName, len(req.Resources))
for i, r := range req.Resources {
resources[i] = database.ResourceName{
Type: r.Type,
Name: r.Name,
}
}
opts := &admin.IssueMagicAuthTokenOptions{
ProjectID: proj.ID,
ResourceType: req.ResourceType,
ResourceName: req.ResourceName,
Fields: req.Fields,
State: req.State,
DisplayName: req.DisplayName,
ProjectID: proj.ID,
Fields: req.Fields,
State: req.State,
DisplayName: req.DisplayName,
Resources: resources,
}

if req.TtlMinutes != 0 {
Expand Down Expand Up @@ -273,6 +277,14 @@ func (s *Server) magicAuthTokenToPB(tkn *database.MagicAuthTokenWithUser, org *d
url = s.admin.URLs.WithCustomDomain(org.CustomDomain).MagicAuthTokenOpen(org.Name, proj.Name, tokenStr)
}

rs := make([]*adminv1.ResourceName, len(tkn.Resources))
for i, r := range tkn.Resources {
rs[i] = &adminv1.ResourceName{
Type: r.Type,
Name: r.Name,
}
}

res := &adminv1.MagicAuthToken{
Id: tkn.ID,
ProjectId: tkn.ProjectID,
Expand All @@ -284,12 +296,11 @@ func (s *Server) magicAuthTokenToPB(tkn *database.MagicAuthTokenWithUser, org *d
CreatedByUserId: safeStr(tkn.CreatedByUserID),
CreatedByUserEmail: tkn.CreatedByUserEmail,
Attributes: attrs,
ResourceType: tkn.ResourceType,
ResourceName: tkn.ResourceName,
Filter: filter,
Fields: tkn.Fields,
State: tkn.State,
DisplayName: tkn.DisplayName,
Resources: rs,
}
if tkn.ExpiresOn != nil {
res.ExpiresOn = timestamppb.New(*tkn.ExpiresOn)
Expand Down
76 changes: 42 additions & 34 deletions admin/server/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
adminv1 "github.com/rilldata/rill/proto/gen/rill/admin/v1"
runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1"
"github.com/rilldata/rill/runtime"
"github.com/rilldata/rill/runtime/client"
"github.com/rilldata/rill/runtime/pkg/duckdbsql"
"github.com/rilldata/rill/runtime/pkg/email"
"github.com/rilldata/rill/runtime/pkg/env"
Expand Down Expand Up @@ -165,44 +166,51 @@ func (s *Server) GetProject(ctx context.Context, req *adminv1.GetProjectRequest)
var condition strings.Builder
// All themes
condition.WriteString(fmt.Sprintf("'{{.self.kind}}'='%s'", runtime.ResourceKindTheme))
// The magic token's resource
condition.WriteString(fmt.Sprintf(" OR '{{.self.kind}}'=%s AND '{{lower .self.name}}'=%s", duckdbsql.EscapeStringValue(mdl.ResourceType), duckdbsql.EscapeStringValue(strings.ToLower(mdl.ResourceName))))
// If the magic token's resource is an Explore, we also need to include its underlying metrics view
if mdl.ResourceType == runtime.ResourceKindExplore {
client, err := s.admin.OpenRuntimeClient(depl)
if err != nil {
return nil, status.Errorf(codes.Internal, "could not open runtime client: %s", err.Error())
}
defer client.Close()

resp, err := client.GetResource(ctx, &runtimev1.GetResourceRequest{
InstanceId: depl.RuntimeInstanceID,
Name: &runtimev1.ResourceName{
Kind: mdl.ResourceType,
Name: mdl.ResourceName,
},
})
if err != nil {
if status.Code(err) == codes.NotFound {
return nil, status.Errorf(codes.NotFound, "resource for magic token not found (name=%q, type=%q)", mdl.ResourceName, mdl.ResourceType)
var c *client.Client

for _, r := range mdl.Resources {
condition.WriteString(fmt.Sprintf(" OR ('{{.self.kind}}'=%s AND '{{lower .self.name}}'=%s)", duckdbsql.EscapeStringValue(r.Type), duckdbsql.EscapeStringValue(strings.ToLower(r.Name))))

// If the magic token's resource is an Explore, we also need to include its underlying metrics view
if r.Type == runtime.ResourceKindExplore {
if c == nil {
c, err = s.admin.OpenRuntimeClient(depl)
if err != nil {
return nil, status.Errorf(codes.Internal, "could not open runtime client: %s", err.Error())
}
defer c.Close() // nolint:gocritic // client is created only once
}
return nil, fmt.Errorf("could not get resource for magic token: %w", err)
}

spec := resp.Resource.GetExplore().State.ValidSpec
if spec != nil {
condition.WriteString(fmt.Sprintf(" OR '{{.self.kind}}'='%s' AND '{{lower .self.name}}'=%s", runtime.ResourceKindMetricsView, duckdbsql.EscapeStringValue(strings.ToLower(spec.MetricsView))))
}
} else if mdl.ResourceType == runtime.ResourceKindReport {
// adding this rule to allow report resource accessible by non admin users
rules = append(rules, &runtimev1.SecurityRule{
Rule: &runtimev1.SecurityRule_Access{
Access: &runtimev1.SecurityRuleAccess{
Condition: fmt.Sprintf("'{{.self.kind}}'='%s' AND '{{lower .self.name}}'=%s", runtime.ResourceKindReport, duckdbsql.EscapeStringValue(strings.ToLower(mdl.ResourceName))),
Allow: true,
resp, err := c.GetResource(ctx, &runtimev1.GetResourceRequest{
InstanceId: depl.RuntimeInstanceID,
Name: &runtimev1.ResourceName{
Kind: r.Type,
Name: r.Name,
},
},
})
})
if err != nil {
if status.Code(err) == codes.NotFound {
return nil, status.Errorf(codes.NotFound, "resource for magic token not found (name=%q, type=%q)", r.Name, r.Type)
}
return nil, fmt.Errorf("could not get resource for magic token: %w", err)
}

spec := resp.Resource.GetExplore().State.ValidSpec
if spec != nil {
condition.WriteString(fmt.Sprintf(" OR ('{{.self.kind}}'='%s' AND '{{lower .self.name}}'=%s)", runtime.ResourceKindMetricsView, duckdbsql.EscapeStringValue(strings.ToLower(spec.MetricsView))))
}
} else if r.Type == runtime.ResourceKindReport {
// adding this rule to allow report resource accessible by non admin users
rules = append(rules, &runtimev1.SecurityRule{
Rule: &runtimev1.SecurityRule_Access{
Access: &runtimev1.SecurityRuleAccess{
Condition: fmt.Sprintf("'{{.self.kind}}'='%s' AND '{{lower .self.name}}'=%s", runtime.ResourceKindReport, duckdbsql.EscapeStringValue(strings.ToLower(r.Name))),
Allow: true,
},
},
})
}
}

attr = mdl.Attributes
Expand Down
Loading
Loading