feat(session): Use permissions for limiting list results
The session repository can now be passed an optional set of permissions. The permissions are used to restrict results from the repository. Currently this only impacts the List method. This enabled the sessions service to use a single query to retrieve sessions and replaces the previous behavior where the session service would first request all sessions for a given set of scopes, evaluate the permissions, and then request sessions based on a set of session ids.
This commit is contained in:
7 files changed
+184
-414
No files matched your search
@@ -0,0 +1,11 @@
|
||||
begin;
|
||||
-- Partial index to aid session list requests
|
||||
--
|
||||
-- If a session list request is made using the default list request options
|
||||
-- and using the standard grants created by boundary by default,
|
||||
-- it will include where clauses that:
|
||||
-- * include a project_id paired with a user_id
|
||||
-- * and where termination_reason is null
|
||||
create index session_list_pix on session (project_id, user_id, termination_reason) where termination_reason is null;
|
||||
analyze session;
|
||||
end;
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
)
|
||||
|
||||
// getOpts - iterate the inbound Options and return a struct
|
||||
@@ -32,6 +33,7 @@ type options struct {
|
||||
withDbOpts []db.Option
|
||||
withWorkerStateDelay time.Duration
|
||||
withTerminated bool
|
||||
withPermissions *perms.UserPermissions
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -120,3 +122,11 @@ func WithTerminated(withTerminated bool) Option {
|
||||
o.withTerminated = withTerminated
|
||||
}
|
||||
}
|
||||
|
||||
// WithPermissions is used to include user permissions when constructing a
|
||||
// Repository.
|
||||
func WithPermissions(p *perms.UserPermissions) Option {
|
||||
return func(o *options) {
|
||||
o.withPermissions = p
|
||||
}
|
||||
}
|
||||
@@ -117,21 +117,6 @@ session_connection_limit(expiration_time, connection_limit) as (
|
||||
select expiration_time, connection_limit, current_connection_count
|
||||
from
|
||||
session_connection_limit, session_connection_count;
|
||||
`
|
||||
nonTerminatedSessionPublicIdList = `
|
||||
select public_id, project_id, user_id from session
|
||||
where
|
||||
session.termination_reason is null
|
||||
and
|
||||
project_id = any(@project_ids)
|
||||
;
|
||||
`
|
||||
|
||||
sessionPublicIdList = `
|
||||
select public_id, project_id, user_id from session
|
||||
where
|
||||
project_id = any(@project_ids)
|
||||
;
|
||||
`
|
||||
|
||||
sessionList = `
|
||||
|
||||
@@ -2,12 +2,18 @@ package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
)
|
||||
|
||||
// Clonable provides a cloning interface
|
||||
@@ -23,8 +29,13 @@ type Repository struct {
|
||||
|
||||
// defaultLimit provides a default for limiting the number of results returned from the repo
|
||||
defaultLimit int
|
||||
|
||||
permissions *perms.UserPermissions
|
||||
}
|
||||
|
||||
// RepositoryFactory is a function that creates a Repository.
|
||||
type RepositoryFactory func(opt ...Option) (*Repository, error)
|
||||
|
||||
// NewRepository creates a new session Repository. Supports the options: WithLimit
|
||||
// which sets a default limit on results returned by repo operations.
|
||||
func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) {
|
||||
@@ -43,14 +54,60 @@ func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repo
|
||||
// zero signals the boundary defaults should be used.
|
||||
opts.withLimit = db.DefaultLimit
|
||||
}
|
||||
|
||||
if opts.withPermissions != nil {
|
||||
for _, p := range opts.withPermissions.Permissions {
|
||||
if p.Resource != resource.Session {
|
||||
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "permission for incorrect resource")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &Repository{
|
||||
reader: r,
|
||||
writer: w,
|
||||
kms: kms,
|
||||
defaultLimit: opts.withLimit,
|
||||
permissions: opts.withPermissions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) listPermissionWhereClauses() ([]string, []interface{}) {
|
||||
var where []string
|
||||
var args []interface{}
|
||||
|
||||
if r.permissions == nil {
|
||||
return where, args
|
||||
}
|
||||
|
||||
inClauseCnt := 0
|
||||
for _, p := range r.permissions.Permissions {
|
||||
if p.Action != action.List {
|
||||
continue
|
||||
}
|
||||
|
||||
inClauseCnt++
|
||||
|
||||
var clauses []string
|
||||
clauses = append(clauses, fmt.Sprintf("project_id = @project_id_%d", inClauseCnt))
|
||||
args = append(args, sql.Named(fmt.Sprintf("project_id_%d", inClauseCnt), p.ScopeId))
|
||||
|
||||
if len(p.ResourceIds) > 0 {
|
||||
clauses = append(clauses, fmt.Sprintf("public_id = any(@public_id_%d)", inClauseCnt))
|
||||
args = append(args, sql.Named(fmt.Sprintf("public_id_%d", inClauseCnt), "{"+strings.Join(p.ResourceIds, ",")+"}"))
|
||||
}
|
||||
|
||||
if p.OnlySelf {
|
||||
inClauseCnt++
|
||||
clauses = append(clauses, fmt.Sprintf("user_id = @user_id_%d", inClauseCnt))
|
||||
args = append(args, sql.Named(fmt.Sprintf("user_id_%d", inClauseCnt), r.permissions.UserId))
|
||||
}
|
||||
|
||||
where = append(where, fmt.Sprintf("(%s)", strings.Join(clauses, " and ")))
|
||||
}
|
||||
return where, args
|
||||
}
|
||||
|
||||
// list will return a listing of resources and honor the WithLimit option or the
|
||||
// repo defaultLimit. Supports WithOrder option.
|
||||
func (r *Repository) list(ctx context.Context, resources interface{}, where string, args []interface{}, opt ...Option) error {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/boundary"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
@@ -229,89 +228,28 @@ func (r *Repository) LookupSession(ctx context.Context, sessionId string, _ ...O
|
||||
return &session, authzSummary, nil
|
||||
}
|
||||
|
||||
// fetchAuthzProtectedSessionsByProject fetches sessions for the given projects.
|
||||
// Note that the sessions are not fully populated, and only contain the
|
||||
// necessary information to implement the boundary.AuthzProtectedEntity
|
||||
// interface. Supports the WithTerminated option.
|
||||
func (r *Repository) fetchAuthzProtectedSessionsByProject(
|
||||
ctx context.Context, projectIds []string, opt ...Option,
|
||||
) (map[string][]boundary.AuthzProtectedEntity, error) {
|
||||
const op = "session.(Repository).fetchAuthzProtectedSessionsByProject"
|
||||
|
||||
opts := getOpts(opt...)
|
||||
|
||||
if len(projectIds) == 0 {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no project ids given")
|
||||
}
|
||||
|
||||
args := []interface{}{
|
||||
sql.Named("project_ids", "{"+strings.Join(projectIds, ",")+"}"),
|
||||
}
|
||||
|
||||
var query string
|
||||
if opts.withTerminated {
|
||||
query = sessionPublicIdList
|
||||
} else {
|
||||
query = nonTerminatedSessionPublicIdList
|
||||
}
|
||||
|
||||
rows, err := r.reader.Query(ctx, query, args)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
sessionsMap := map[string][]boundary.AuthzProtectedEntity{}
|
||||
for rows.Next() {
|
||||
var ses Session
|
||||
if err := r.reader.ScanRows(ctx, rows, &ses); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("scan row failed"))
|
||||
}
|
||||
sessionsMap[ses.GetProjectId()] = append(sessionsMap[ses.GetProjectId()], ses)
|
||||
}
|
||||
|
||||
return sessionsMap, nil
|
||||
}
|
||||
|
||||
// ListSessions lists sessions. Supports the WithLimit, WithProjectId, and WithSessionIds options.
|
||||
// ListSessions lists sessions. Sessions returned will be limited by the list
|
||||
// permissions of the repository. Supports the WithTerminated, WithLimit,
|
||||
// WithOrderByCreateTime options.
|
||||
func (r *Repository) ListSessions(ctx context.Context, opt ...Option) ([]*Session, error) {
|
||||
const op = "session.(Repository).ListSessions"
|
||||
opts := getOpts(opt...)
|
||||
var where []string
|
||||
var args []interface{}
|
||||
|
||||
inClauseCnt := 0
|
||||
switch len(opts.withProjectIds) {
|
||||
case 0:
|
||||
case 1:
|
||||
inClauseCnt += 1
|
||||
where, args = append(where, fmt.Sprintf("project_id = @%d", inClauseCnt)), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), opts.withProjectIds[0]))
|
||||
default:
|
||||
idsInClause := make([]string, 0, len(opts.withProjectIds))
|
||||
for _, id := range opts.withProjectIds {
|
||||
inClauseCnt += 1
|
||||
idsInClause, args = append(idsInClause, fmt.Sprintf("@%d", inClauseCnt)), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), id))
|
||||
}
|
||||
where = append(where, fmt.Sprintf("project_id in (%s)", strings.Join(idsInClause, ",")))
|
||||
where, args := r.listPermissionWhereClauses()
|
||||
if len(where) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if opts.withUserId != "" {
|
||||
inClauseCnt += 1
|
||||
where, args = append(where, fmt.Sprintf("user_id = @%d", inClauseCnt)), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), opts.withUserId))
|
||||
}
|
||||
|
||||
switch len(opts.withSessionIds) {
|
||||
case 0:
|
||||
case 1:
|
||||
inClauseCnt += 1
|
||||
where, args = append(where, fmt.Sprintf("s.public_id = @%d", inClauseCnt)), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), opts.withSessionIds[0]))
|
||||
default:
|
||||
idsInClause := make([]string, 0, len(opts.withSessionIds))
|
||||
for _, id := range opts.withSessionIds {
|
||||
inClauseCnt += 1
|
||||
idsInClause, args = append(idsInClause, fmt.Sprintf("@%d", inClauseCnt)), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), id))
|
||||
var whereClause string
|
||||
if len(where) > 0 {
|
||||
whereClause = " where (" + strings.Join(where, " or ") + ")"
|
||||
if !opts.withTerminated {
|
||||
whereClause += "and termination_reason is null"
|
||||
}
|
||||
} else {
|
||||
if !opts.withTerminated {
|
||||
whereClause = "where termination_reason is null"
|
||||
}
|
||||
where = append(where, fmt.Sprintf("s.public_id in (%s)", strings.Join(idsInClause, ",")))
|
||||
}
|
||||
|
||||
var limit string
|
||||
@@ -323,7 +261,6 @@ func (r *Repository) ListSessions(ctx context.Context, opt ...Option) ([]*Sessio
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = fmt.Sprintf("limit %d", opts.withLimit)
|
||||
}
|
||||
|
||||
var withOrder string
|
||||
switch opts.withOrderByCreateTime {
|
||||
case db.AscendingOrderBy:
|
||||
@@ -334,10 +271,6 @@ func (r *Repository) ListSessions(ctx context.Context, opt ...Option) ([]*Sessio
|
||||
withOrder = "order by create_time"
|
||||
}
|
||||
|
||||
var whereClause string
|
||||
if len(where) > 0 {
|
||||
whereClause = " where " + strings.Join(where, " and ")
|
||||
}
|
||||
q := sessionList
|
||||
query := fmt.Sprintf(q, whereClause, withOrder, limit, withOrder)
|
||||
|
||||
|
||||
@@ -21,9 +21,12 @@ import (
|
||||
iamStore "github.com/hashicorp/boundary/internal/iam/store"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/target"
|
||||
"github.com/hashicorp/boundary/internal/target/tcp"
|
||||
tcpStore "github.com/hashicorp/boundary/internal/target/tcp/store"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/jackc/pgconn"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -39,10 +42,18 @@ func TestRepository_ListSession(t *testing.T) {
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, WithLimit(testLimit))
|
||||
require.NoError(t, err)
|
||||
composedOf := TestSessionParams(t, conn, wrapper, iamRepo)
|
||||
|
||||
listPerms := &perms.UserPermissions{
|
||||
UserId: composedOf.UserId,
|
||||
Permissions: []perms.Permission{
|
||||
{
|
||||
ScopeId: composedOf.ProjectId,
|
||||
Resource: resource.Session,
|
||||
Action: action.List,
|
||||
},
|
||||
},
|
||||
}
|
||||
type args struct {
|
||||
opt []Option
|
||||
}
|
||||
@@ -50,58 +61,85 @@ func TestRepository_ListSession(t *testing.T) {
|
||||
name string
|
||||
createCnt int
|
||||
args args
|
||||
perms *perms.UserPermissions
|
||||
wantCnt int
|
||||
wantErr bool
|
||||
withConnections int
|
||||
}{
|
||||
{
|
||||
name: "no-limit",
|
||||
createCnt: repo.defaultLimit + 1,
|
||||
createCnt: testLimit + 1,
|
||||
args: args{
|
||||
opt: []Option{WithLimit(-1)},
|
||||
},
|
||||
wantCnt: repo.defaultLimit + 1,
|
||||
perms: listPerms,
|
||||
wantCnt: testLimit + 1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "default-limit",
|
||||
createCnt: repo.defaultLimit + 1,
|
||||
createCnt: testLimit + 1,
|
||||
args: args{},
|
||||
wantCnt: repo.defaultLimit,
|
||||
perms: listPerms,
|
||||
wantCnt: testLimit,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "custom-limit",
|
||||
createCnt: repo.defaultLimit + 1,
|
||||
createCnt: testLimit + 1,
|
||||
args: args{
|
||||
opt: []Option{WithLimit(3)},
|
||||
},
|
||||
perms: listPerms,
|
||||
wantCnt: 3,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "withProjectIds",
|
||||
createCnt: repo.defaultLimit + 1,
|
||||
args: args{
|
||||
opt: []Option{WithProjectIds([]string{composedOf.ProjectId})},
|
||||
name: "withNoPerms",
|
||||
createCnt: testLimit + 1,
|
||||
args: args{},
|
||||
perms: &perms.UserPermissions{},
|
||||
wantCnt: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "withPermsDifferentScopeId",
|
||||
createCnt: testLimit + 1,
|
||||
args: args{},
|
||||
perms: &perms.UserPermissions{
|
||||
Permissions: []perms.Permission{
|
||||
{
|
||||
ScopeId: "o_thisIsNotValid",
|
||||
Resource: resource.Session,
|
||||
Action: action.List,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCnt: repo.defaultLimit,
|
||||
wantCnt: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "bad-withProjectId",
|
||||
createCnt: repo.defaultLimit + 1,
|
||||
args: args{
|
||||
opt: []Option{WithProjectIds([]string{"o_thisIsNotValid"})},
|
||||
name: "withPermsNonListAction",
|
||||
createCnt: testLimit + 1,
|
||||
args: args{},
|
||||
perms: &perms.UserPermissions{
|
||||
Permissions: []perms.Permission{
|
||||
{
|
||||
ScopeId: composedOf.ProjectId,
|
||||
Resource: resource.Session,
|
||||
Action: action.Read,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCnt: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "multiple-connections",
|
||||
createCnt: repo.defaultLimit + 1,
|
||||
createCnt: testLimit + 1,
|
||||
args: args{},
|
||||
wantCnt: repo.defaultLimit,
|
||||
perms: listPerms,
|
||||
wantCnt: testLimit,
|
||||
wantErr: false,
|
||||
withConnections: 3,
|
||||
},
|
||||
@@ -109,6 +147,10 @@ func TestRepository_ListSession(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
repo, err := NewRepository(rw, rw, kms, WithLimit(testLimit), WithPermissions(tt.perms))
|
||||
require.NoError(err)
|
||||
|
||||
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocSession(); return &i }(), "1=1")
|
||||
testSessions := []*Session{}
|
||||
for i := 0; i < tt.createCnt; i++ {
|
||||
@@ -150,6 +192,10 @@ func TestRepository_ListSession(t *testing.T) {
|
||||
for i := 0; i < wantCnt; i++ {
|
||||
_ = TestSession(t, conn, wrapper, composedOf)
|
||||
}
|
||||
|
||||
repo, err := NewRepository(rw, rw, kms, WithLimit(testLimit), WithPermissions(listPerms))
|
||||
require.NoError(err)
|
||||
|
||||
got, err := repo.ListSessions(context.Background(), WithOrderByCreateTime(db.AscendingOrderBy))
|
||||
require.NoError(err)
|
||||
assert.Equal(wantCnt, len(got))
|
||||
@@ -160,7 +206,7 @@ func TestRepository_ListSession(t *testing.T) {
|
||||
assert.True(first.Before(second))
|
||||
}
|
||||
})
|
||||
t.Run("withUserId", func(t *testing.T) {
|
||||
t.Run("onlySelf", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocSession(); return &i }(), "1=1")
|
||||
wantCnt := 5
|
||||
@@ -168,51 +214,25 @@ func TestRepository_ListSession(t *testing.T) {
|
||||
_ = TestSession(t, conn, wrapper, composedOf)
|
||||
}
|
||||
s := TestDefaultSession(t, conn, wrapper, iamRepo)
|
||||
|
||||
p := &perms.UserPermissions{
|
||||
UserId: s.UserId,
|
||||
Permissions: []perms.Permission{
|
||||
{
|
||||
ScopeId: s.ProjectId,
|
||||
Resource: resource.Session,
|
||||
Action: action.List,
|
||||
OnlySelf: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
repo, err := NewRepository(rw, rw, kms, WithLimit(testLimit), WithPermissions(p))
|
||||
require.NoError(err)
|
||||
got, err := repo.ListSessions(context.Background(), WithUserId(s.UserId))
|
||||
require.NoError(err)
|
||||
assert.Equal(1, len(got))
|
||||
assert.Equal(s.UserId, got[0].UserId)
|
||||
})
|
||||
t.Run("withUserIdAndwithScopeId", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocSession(); return &i }(), "1=1")
|
||||
wantCnt := 5
|
||||
for i := 0; i < wantCnt; i++ {
|
||||
// Scope 1 User 1
|
||||
_ = TestSession(t, conn, wrapper, composedOf)
|
||||
}
|
||||
// Scope 2 User 2
|
||||
s := TestDefaultSession(t, conn, wrapper, iamRepo)
|
||||
|
||||
// Scope 1 User 2
|
||||
coDiffUser := composedOf
|
||||
coDiffUser.AuthTokenId = s.AuthTokenId
|
||||
coDiffUser.UserId = s.UserId
|
||||
wantS := TestSession(t, conn, wrapper, coDiffUser)
|
||||
|
||||
got, err := repo.ListSessions(context.Background(), WithUserId(coDiffUser.UserId), WithProjectIds([]string{coDiffUser.ProjectId}))
|
||||
require.NoError(err)
|
||||
assert.Equal(1, len(got))
|
||||
assert.Equal(wantS.UserId, got[0].UserId)
|
||||
assert.Equal(wantS.ProjectId, got[0].ProjectId)
|
||||
})
|
||||
t.Run("WithSessionIds", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocSession(); return &i }(), "1=1")
|
||||
testSessions := []*Session{}
|
||||
for i := 0; i < 10; i++ {
|
||||
s := TestSession(t, conn, wrapper, composedOf)
|
||||
_ = TestState(t, conn, s.PublicId, StatusActive)
|
||||
testSessions = append(testSessions, s)
|
||||
}
|
||||
assert.Equal(10, len(testSessions))
|
||||
withIds := []string{testSessions[0].PublicId, testSessions[1].PublicId}
|
||||
got, err := repo.ListSessions(context.Background(), WithSessionIds(withIds...), WithOrderByCreateTime(db.AscendingOrderBy))
|
||||
require.NoError(err)
|
||||
assert.Equal(2, len(got))
|
||||
assert.Equal(StatusActive, got[0].States[0].Status)
|
||||
assert.Equal(StatusPending, got[0].States[1].Status)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRepository_ListSessions_Multiple_Scopes(t *testing.T) {
|
||||
@@ -222,23 +242,29 @@ func TestRepository_ListSessions_Multiple_Scopes(t *testing.T) {
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocSession(); return &i }(), "1=1")
|
||||
|
||||
const numPerScope = 10
|
||||
var projs []string
|
||||
var p []perms.Permission
|
||||
for i := 0; i < numPerScope; i++ {
|
||||
composedOf := TestSessionParams(t, conn, wrapper, iamRepo)
|
||||
projs = append(projs, composedOf.ProjectId)
|
||||
p = append(p, perms.Permission{
|
||||
ScopeId: composedOf.ProjectId,
|
||||
Resource: resource.Session,
|
||||
Action: action.List,
|
||||
})
|
||||
s := TestSession(t, conn, wrapper, composedOf)
|
||||
_ = TestState(t, conn, s.PublicId, StatusActive)
|
||||
}
|
||||
|
||||
got, err := repo.ListSessions(context.Background(), WithProjectIds(projs))
|
||||
repo, err := NewRepository(rw, rw, kms, WithPermissions(&perms.UserPermissions{
|
||||
Permissions: p,
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(projs), len(got))
|
||||
got, err := repo.ListSessions(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(p), len(got))
|
||||
}
|
||||
|
||||
func TestRepository_CreateSession(t *testing.T) {
|
||||
@@ -1623,232 +1649,3 @@ func TestRepository_deleteTerminated(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchAuthzProtectedSessionsByScopes(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
ctx := context.Background()
|
||||
wrapper := db.TestWrapper(t)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
composedOf := TestSessionParams(t, conn, wrapper, iamRepo)
|
||||
|
||||
_, pWithOtherSessions := iam.TestScopes(t, iamRepo)
|
||||
|
||||
hcOther := static.TestCatalogs(t, conn, pWithOtherSessions.GetPublicId(), 1)[0]
|
||||
hsOther := static.TestSets(t, conn, hcOther.GetPublicId(), 1)[0]
|
||||
hOther := static.TestHosts(t, conn, hcOther.GetPublicId(), 1)[0]
|
||||
static.TestSetMembers(t, conn, hsOther.GetPublicId(), []*static.Host{hOther})
|
||||
tarOther := tcp.TestTarget(ctx, t, conn, pWithOtherSessions.GetPublicId(), "test", target.WithHostSources([]string{hsOther.GetPublicId()}))
|
||||
|
||||
composedOfOther := ComposedOf{
|
||||
UserId: composedOf.UserId,
|
||||
HostId: hOther.GetPublicId(),
|
||||
TargetId: tarOther.GetPublicId(),
|
||||
HostSetId: hsOther.GetPublicId(),
|
||||
AuthTokenId: composedOf.AuthTokenId,
|
||||
ProjectId: pWithOtherSessions.GetPublicId(),
|
||||
Endpoint: "tcp://127.0.0.1:22",
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
createCnt int
|
||||
terminateCnt int
|
||||
otherCnt int
|
||||
otherTerminateCnt int
|
||||
opts []Option
|
||||
reqScopes []string
|
||||
wantCnt int
|
||||
wantOtherCnt int
|
||||
wantErr bool
|
||||
}
|
||||
|
||||
cases := []testCase{
|
||||
{
|
||||
name: "NonTerminated/none",
|
||||
createCnt: 0,
|
||||
reqScopes: []string{composedOf.ProjectId},
|
||||
wantCnt: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "NonTerminated/one",
|
||||
createCnt: 1,
|
||||
reqScopes: []string{composedOf.ProjectId},
|
||||
wantCnt: 1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "NonTerminated/many",
|
||||
createCnt: 5,
|
||||
reqScopes: []string{composedOf.ProjectId},
|
||||
wantCnt: 5,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "NonTerminated/many one terminated",
|
||||
createCnt: 5,
|
||||
terminateCnt: 1,
|
||||
reqScopes: []string{composedOf.ProjectId},
|
||||
wantCnt: 4,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "NonTerminated/many terminated",
|
||||
createCnt: 5,
|
||||
terminateCnt: 3,
|
||||
reqScopes: []string{composedOf.ProjectId},
|
||||
wantCnt: 2,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "NonTerminated/many multiple projects",
|
||||
createCnt: 5,
|
||||
terminateCnt: 3,
|
||||
reqScopes: []string{composedOf.ProjectId, composedOfOther.ProjectId},
|
||||
wantCnt: 2,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "NonTerminated/many multiple projects",
|
||||
createCnt: 5,
|
||||
terminateCnt: 3,
|
||||
otherCnt: 3,
|
||||
otherTerminateCnt: 1,
|
||||
reqScopes: []string{composedOf.ProjectId, composedOfOther.ProjectId},
|
||||
wantCnt: 2,
|
||||
wantOtherCnt: 2,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "NonTerminated/no projects",
|
||||
createCnt: 2,
|
||||
terminateCnt: 1,
|
||||
otherCnt: 2,
|
||||
otherTerminateCnt: 1,
|
||||
reqScopes: []string{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "none",
|
||||
opts: []Option{WithTerminated(true)},
|
||||
createCnt: 0,
|
||||
reqScopes: []string{composedOf.ProjectId},
|
||||
wantCnt: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "one",
|
||||
opts: []Option{WithTerminated(true)},
|
||||
createCnt: 1,
|
||||
reqScopes: []string{composedOf.ProjectId},
|
||||
wantCnt: 1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "many",
|
||||
opts: []Option{WithTerminated(true)},
|
||||
createCnt: 5,
|
||||
reqScopes: []string{composedOf.ProjectId},
|
||||
wantCnt: 5,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "many one terminated",
|
||||
opts: []Option{WithTerminated(true)},
|
||||
createCnt: 5,
|
||||
terminateCnt: 1,
|
||||
reqScopes: []string{composedOf.ProjectId},
|
||||
wantCnt: 5,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "many terminated",
|
||||
opts: []Option{WithTerminated(true)},
|
||||
createCnt: 5,
|
||||
terminateCnt: 3,
|
||||
reqScopes: []string{composedOf.ProjectId},
|
||||
wantCnt: 5,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "many multiple projects",
|
||||
opts: []Option{WithTerminated(true)},
|
||||
createCnt: 5,
|
||||
terminateCnt: 3,
|
||||
reqScopes: []string{composedOf.ProjectId, composedOfOther.ProjectId},
|
||||
wantCnt: 5,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "many multiple projects",
|
||||
opts: []Option{WithTerminated(true)},
|
||||
createCnt: 5,
|
||||
terminateCnt: 3,
|
||||
otherCnt: 3,
|
||||
otherTerminateCnt: 1,
|
||||
reqScopes: []string{composedOf.ProjectId, composedOfOther.ProjectId},
|
||||
wantCnt: 5,
|
||||
wantOtherCnt: 3,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "no projects",
|
||||
opts: []Option{WithTerminated(true)},
|
||||
createCnt: 2,
|
||||
terminateCnt: 1,
|
||||
otherCnt: 2,
|
||||
otherTerminateCnt: 1,
|
||||
reqScopes: []string{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocSession(); return &i }(), "1=1")
|
||||
|
||||
testSessions := []*Session{}
|
||||
for i := 0; i < tt.createCnt; i++ {
|
||||
s := TestSession(t, conn, wrapper, composedOf)
|
||||
_ = TestState(t, conn, s.PublicId, StatusActive)
|
||||
testSessions = append(testSessions, s)
|
||||
}
|
||||
for i := 0; i < tt.terminateCnt; i++ {
|
||||
_, err := repo.CancelSession(ctx, testSessions[i].PublicId, testSessions[i].Version)
|
||||
require.NoError(err)
|
||||
}
|
||||
terminated, err := repo.TerminateCompletedSessions(ctx)
|
||||
require.NoError(err)
|
||||
require.Equal(tt.terminateCnt, terminated)
|
||||
|
||||
otherTestSessions := []*Session{}
|
||||
for i := 0; i < tt.otherCnt; i++ {
|
||||
s := TestSession(t, conn, wrapper, composedOfOther)
|
||||
_ = TestState(t, conn, s.PublicId, StatusActive)
|
||||
otherTestSessions = append(otherTestSessions, s)
|
||||
}
|
||||
for i := 0; i < tt.otherTerminateCnt; i++ {
|
||||
_, err := repo.CancelSession(ctx, otherTestSessions[i].PublicId, otherTestSessions[i].Version)
|
||||
require.NoError(err)
|
||||
}
|
||||
terminated, err = repo.TerminateCompletedSessions(ctx)
|
||||
require.NoError(err)
|
||||
require.Equal(tt.otherTerminateCnt, terminated)
|
||||
|
||||
assert.Equal(tt.otherCnt, len(otherTestSessions))
|
||||
|
||||
got, err := repo.fetchAuthzProtectedSessionsByProject(ctx, tt.reqScopes, tt.opts...)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.wantCnt, len(got[composedOf.ProjectId]))
|
||||
assert.Equal(tt.wantOtherCnt, len(got[composedOfOther.ProjectId]))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/boundary"
|
||||
)
|
||||
|
||||
type listForAuthzCheckFunc func(ctx context.Context, projectIds []string) (map[string][]boundary.AuthzProtectedEntity, error)
|
||||
|
||||
func (a listForAuthzCheckFunc) FetchAuthzProtectedEntitiesByScope(ctx context.Context, projectIds []string) (map[string][]boundary.AuthzProtectedEntity, error) {
|
||||
return a(ctx, projectIds)
|
||||
}
|
||||
|
||||
// ListForAuthzCheck returns a functions that fetches sessions for the given
|
||||
// projects. Note that the sessions are not fully populated, and only contain the
|
||||
// necessary information to implement the boundary.AuthzProtectedEntity
|
||||
// interface. Supports the WithTerminated option.
|
||||
func ListForAuthzCheck(repo *Repository, opt ...Option) listForAuthzCheckFunc {
|
||||
return func(ctx context.Context, projectIds []string) (map[string][]boundary.AuthzProtectedEntity, error) {
|
||||
return repo.fetchAuthzProtectedSessionsByProject(ctx, projectIds, opt...)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user