Skip to content
Merged
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
12 changes: 6 additions & 6 deletions api/client/accesslist/accesslist.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ func (c *Client) GetAccessList(ctx context.Context, name string) (*accesslist.Ac
return nil, trail.FromGRPC(err)
}

accessList, err := conv.FromProto(resp)
return accessList, trail.FromGRPC(err)
accessList, err := conv.FromProto(resp, conv.WithOwnersIneligibleStatusField(resp.GetSpec().GetOwners()))
return accessList, trace.Wrap(err)
}

// UpsertAccessList creates or updates an access list resource.
Expand Down Expand Up @@ -129,9 +129,9 @@ func (c *Client) ListAccessListMembers(ctx context.Context, accessList string, p
}

members = make([]*accesslist.AccessListMember, len(resp.Members))
for i, accessList := range resp.Members {
for i, member := range resp.Members {
var err error
members[i], err = conv.FromMemberProto(accessList)
members[i], err = conv.FromMemberProto(member, conv.WithMemberIneligibleStatusField(member))
if err != nil {
return nil, "", trail.FromGRPC(err)
}
Expand All @@ -150,8 +150,8 @@ func (c *Client) GetAccessListMember(ctx context.Context, accessList string, mem
return nil, trail.FromGRPC(err)
}

member, err := conv.FromMemberProto(resp)
return member, trail.FromGRPC(err)
member, err := conv.FromMemberProto(resp, conv.WithMemberIneligibleStatusField(resp))
return member, trace.Wrap(err)
}

// UpsertAccessListMember creates or updates an access list member resource.
Expand Down
287 changes: 200 additions & 87 deletions api/gen/proto/go/teleport/accesslist/v1/accesslist.pb.go

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions api/proto/teleport/accesslist/v1/accesslist.proto
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ message AccessListOwner {

// description is the plaintext description of the owner and why they are an owner.
string description = 2;

// ineligible_status describes if this owner is eligible or not
// and if not, describes how they're lacking eligibility.
IneligibleStatus ineligible_status = 3;
}

// AccessListAudit describes the audit configuration for an access list.
Expand Down Expand Up @@ -130,4 +134,25 @@ message MemberSpec {

// added_by is the user that added this user to the access list.
string added_by = 6;

// ineligible_status describes if this member is eligible or not
// and if not, describes how they're lacking eligibility.
IneligibleStatus ineligible_status = 7;
}

// IneligibleStatus describes how the user is ineligible.
enum IneligibleStatus {
// INELIGIBLE_STATUS_UNSPECIFIED means eligiblity is unknown.
INELIGIBLE_STATUS_UNSPECIFIED = 0;
// INELIGIBLE_STATUS_ELIGIBLE means checks were done and user met all requirements.
INELIGIBLE_STATUS_ELIGIBLE = 1;
// INELIGIBLE_STATUS_USER_NOT_EXIST means user was not found in backend.
INELIGIBLE_STATUS_USER_NOT_EXIST = 2;
// INELIGIBLE_STATUS_MISSING_REQUIREMENTS means user is missing some requirements
// defined by AccessListRequires (fields can be either ownership_requires
// or membership_requires)
INELIGIBLE_STATUS_MISSING_REQUIREMENTS = 3;
// INELIGIBLE_STATUS_EXPIRED means user is expired.
// Only applicable to members.
INELIGIBLE_STATUS_EXPIRED = 4;
}
8 changes: 8 additions & 0 deletions api/types/accesslist/accesslist.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ type Owner struct {

// Description is the plaintext description of the owner and why they are an owner.
Description string `json:"description" yaml:"description"`

// IneligibleStatus describes the reason why this owner is not eligible.
IneligibleStatus string `json:"ineligible_status" yaml:"ineligible_status"`
}

// Audit describes the audit configuration for an access list.
Expand Down Expand Up @@ -178,6 +181,11 @@ func (a *AccessList) GetOwners() []Owner {
return a.Spec.Owners
}

// GetOwners returns the list of owners from the access list.
func (a *AccessList) SetOwners(owners []Owner) {
a.Spec.Owners = owners
}

// GetAuditFrequency returns the audit frequency from the access list.
func (a *AccessList) GetAuditFrequency() time.Duration {
return a.Spec.Audit.Frequency
Expand Down
42 changes: 38 additions & 4 deletions api/types/accesslist/convert/v1/accesslist.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ import (
traitv1 "github.com/gravitational/teleport/api/types/trait/convert/v1"
)

type AccessListOption func(*accesslist.AccessList)

// FromProto converts a v1 access list into an internal access list object.
func FromProto(msg *accesslistv1.AccessList) (*accesslist.AccessList, error) {
func FromProto(msg *accesslistv1.AccessList, opts ...AccessListOption) (*accesslist.AccessList, error) {
if msg == nil {
return nil, trace.BadParameter("access list message is nil")
}
Expand All @@ -54,6 +56,9 @@ func FromProto(msg *accesslistv1.AccessList) (*accesslist.AccessList, error) {
owners[i] = accesslist.Owner{
Name: owner.Name,
Description: owner.Description,
// Set it to empty as default.
// Must provide as options to set it with the provided value.
IneligibleStatus: "",
}
}

Expand All @@ -78,17 +83,29 @@ func FromProto(msg *accesslistv1.AccessList) (*accesslist.AccessList, error) {
Traits: traitv1.FromProto(msg.Spec.Grants.Traits),
},
})
if err != nil {
return nil, trace.Wrap(err)
}

for _, opt := range opts {
opt(accessList)
}

return accessList, trace.Wrap(err)
return accessList, nil
}

// ToProto converts an internal access list into a v1 access list object.
func ToProto(accessList *accesslist.AccessList) *accesslistv1.AccessList {
owners := make([]*accesslistv1.AccessListOwner, len(accessList.Spec.Owners))
for i, owner := range accessList.Spec.Owners {
var ineligibleStatus accesslistv1.IneligibleStatus
if enumVal, ok := accesslistv1.IneligibleStatus_value[owner.IneligibleStatus]; ok {
ineligibleStatus = accesslistv1.IneligibleStatus(enumVal)
}
owners[i] = &accesslistv1.AccessListOwner{
Name: owner.Name,
Description: owner.Description,
Name: owner.Name,
Description: owner.Description,
IneligibleStatus: ineligibleStatus,
}
}

Expand Down Expand Up @@ -117,3 +134,20 @@ func ToProto(accessList *accesslist.AccessList) *accesslistv1.AccessList {
},
}
}

// WithOwnersIneligibleStatusField sets the "ineligibleStatus" field to the provided proto value.
func WithOwnersIneligibleStatusField(protoOwners []*accesslistv1.AccessListOwner) AccessListOption {
return func(a *accesslist.AccessList) {
updatedOwners := make([]accesslist.Owner, len(a.GetOwners()))
for i, owner := range a.GetOwners() {
protoIneligibleStatus := protoOwners[i].GetIneligibleStatus()
ineligibleStatus := ""
if protoIneligibleStatus != accesslistv1.IneligibleStatus_INELIGIBLE_STATUS_UNSPECIFIED {
ineligibleStatus = protoIneligibleStatus.String()
}
owner.IneligibleStatus = ineligibleStatus
updatedOwners[i] = owner
}
a.SetOwners(updatedOwners)
}
}
48 changes: 48 additions & 0 deletions api/types/accesslist/convert/v1/accesslist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,58 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"

accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
"github.com/gravitational/teleport/api/types/accesslist"
"github.com/gravitational/teleport/api/types/header"
)

func TestWithOwnersIneligibleStatusField(t *testing.T) {
proto := []*accesslistv1.AccessListOwner{
{
Name: "expired",
IneligibleStatus: accesslistv1.IneligibleStatus_INELIGIBLE_STATUS_EXPIRED,
},
{
Name: "missing",
IneligibleStatus: accesslistv1.IneligibleStatus_INELIGIBLE_STATUS_MISSING_REQUIREMENTS,
},
{
Name: "dne",
IneligibleStatus: accesslistv1.IneligibleStatus_INELIGIBLE_STATUS_USER_NOT_EXIST,
},
}

owners := []accesslist.Owner{
{Name: "expired"},
{Name: "missing"},
{Name: "dne"},
}
al := &accesslist.AccessList{
Spec: accesslist.Spec{
Owners: owners,
},
}
require.Empty(t, cmp.Diff(al.Spec.Owners, owners))

fn := WithOwnersIneligibleStatusField(proto)
fn(al)

require.Empty(t, cmp.Diff(al.Spec.Owners, []accesslist.Owner{
{
Name: "expired",
IneligibleStatus: accesslistv1.IneligibleStatus_INELIGIBLE_STATUS_EXPIRED.String(),
},
{
Name: "missing",
IneligibleStatus: accesslistv1.IneligibleStatus_INELIGIBLE_STATUS_MISSING_REQUIREMENTS.String(),
},
{
Name: "dne",
IneligibleStatus: accesslistv1.IneligibleStatus_INELIGIBLE_STATUS_USER_NOT_EXIST.String(),
},
}))
}

func TestRoundtrip(t *testing.T) {
accessList := newAccessList(t, "access-list")

Expand Down
46 changes: 38 additions & 8 deletions api/types/accesslist/convert/v1/member.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import (
headerv1 "github.com/gravitational/teleport/api/types/header/convert/v1"
)

type MemberOption func(*accesslist.AccessListMember)

// FromMemberProto converts a v1 access list member into an internal access list member object.
func FromMemberProto(msg *accesslistv1.Member) (*accesslist.AccessListMember, error) {
func FromMemberProto(msg *accesslistv1.Member, opts ...MemberOption) (*accesslist.AccessListMember, error) {
if msg == nil {
return nil, trace.BadParameter("access list message is nil")
}
Expand All @@ -42,9 +44,19 @@ func FromMemberProto(msg *accesslistv1.Member) (*accesslist.AccessListMember, er
Expires: msg.Spec.Expires.AsTime(),
Reason: msg.Spec.Reason,
AddedBy: msg.Spec.AddedBy,
// Set it to empty as default.
// Must provide as options to set it with the provided value.
IneligibleStatus: "",
})
if err != nil {
return nil, trace.Wrap(err)
}

return member, trace.Wrap(err)
for _, opt := range opts {
opt(member)
}

return member, nil
}

// FromMembersProto converts a list of v1 access list members into a list of internal access list members.
Expand All @@ -62,15 +74,21 @@ func FromMembersProto(msgs []*accesslistv1.Member) ([]*accesslist.AccessListMemb

// ToMemberProto converts an internal access list member into a v1 access list member object.
func ToMemberProto(member *accesslist.AccessListMember) *accesslistv1.Member {
var ineligibleStatus accesslistv1.IneligibleStatus
if enumVal, ok := accesslistv1.IneligibleStatus_value[member.Spec.IneligibleStatus]; ok {
ineligibleStatus = accesslistv1.IneligibleStatus(enumVal)
}

return &accesslistv1.Member{
Header: headerv1.ToResourceHeaderProto(member.ResourceHeader),
Spec: &accesslistv1.MemberSpec{
AccessList: member.Spec.AccessList,
Name: member.Spec.Name,
Joined: timestamppb.New(member.Spec.Joined),
Expires: timestamppb.New(member.Spec.Expires),
Reason: member.Spec.Reason,
AddedBy: member.Spec.AddedBy,
AccessList: member.Spec.AccessList,
Name: member.Spec.Name,
Joined: timestamppb.New(member.Spec.Joined),
Expires: timestamppb.New(member.Spec.Expires),
Reason: member.Spec.Reason,
AddedBy: member.Spec.AddedBy,
IneligibleStatus: ineligibleStatus,
},
}
}
Expand All @@ -83,3 +101,15 @@ func ToMembersProto(members []*accesslist.AccessListMember) []*accesslistv1.Memb
}
return out
}

// WithMemberIneligibleStatusField sets the "ineligibleStatus" field to the provided proto value.
func WithMemberIneligibleStatusField(protoMember *accesslistv1.Member) MemberOption {
return func(m *accesslist.AccessListMember) {
protoIneligibleStatus := protoMember.GetSpec().GetIneligibleStatus()
ineligibleStatus := ""
if protoIneligibleStatus != accesslistv1.IneligibleStatus_INELIGIBLE_STATUS_UNSPECIFIED {
ineligibleStatus = protoIneligibleStatus.String()
}
m.Spec.IneligibleStatus = ineligibleStatus
}
}
19 changes: 19 additions & 0 deletions api/types/accesslist/convert/v1/member_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"

accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
"github.com/gravitational/teleport/api/types/accesslist"
"github.com/gravitational/teleport/api/types/header"
)
Expand All @@ -36,6 +37,24 @@ func TestMemberRoundtrip(t *testing.T) {
require.Empty(t, cmp.Diff(member, converted))
}

func TestWithMemberIneligibleStatusField(t *testing.T) {
proto := &accesslistv1.Member{
Spec: &accesslistv1.MemberSpec{
IneligibleStatus: accesslistv1.IneligibleStatus_INELIGIBLE_STATUS_EXPIRED,
},
}

alMember := &accesslist.AccessListMember{
Spec: accesslist.AccessListMemberSpec{},
}
require.Empty(t, alMember.Spec.IneligibleStatus)

fn := WithMemberIneligibleStatusField(proto)
fn(alMember)

require.Equal(t, accesslistv1.IneligibleStatus_INELIGIBLE_STATUS_EXPIRED.Enum().String(), alMember.Spec.IneligibleStatus)
}

// Make sure that we don't panic if any of the message fields are missing.
func TestMemberFromProtoNils(t *testing.T) {
// Spec is nil
Expand Down
3 changes: 3 additions & 0 deletions api/types/accesslist/member.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ type AccessListMemberSpec struct {

// added_by is the user that added this user to the access list.
AddedBy string `json:"added_by" yaml:"added_by"`

// IneligibleStatus describes the reason why this member is not eligible.
IneligibleStatus string `json:"ineligible_status" yaml:"ineligible_status"`
}

// NewAccessListMember will create a new access listm member.
Expand Down