Skip to content

Commit

Permalink
feature (auth/ldap): add managed groups (#2760)
Browse files Browse the repository at this point in the history
* tests (auth/ldap): add missing unit test to Repository.DeleteAccount(...)

Add bits to test the delete operation when you're not able to generate
oplog metadata

* feature (auth/ldap): add managed groups
  • Loading branch information
jimlambrt committed Jan 7, 2023
1 parent 9d42f2f commit af92eca
Show file tree
Hide file tree
Showing 18 changed files with 2,301 additions and 42 deletions.
17 changes: 9 additions & 8 deletions internal/auth/ldap/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@ func NewAccount(ctx context.Context, scopeId, authMethodId, loginName string, op
}
a := &Account{
Account: &store.Account{
ScopeId: scopeId,
AuthMethodId: authMethodId,
LoginName: loginName,
Dn: opts.withDn,
Name: opts.withName,
Description: opts.withDescription,
FullName: opts.withFullName,
Email: opts.withEmail,
ScopeId: scopeId,
AuthMethodId: authMethodId,
LoginName: loginName,
Dn: opts.withDn,
Name: opts.withName,
Description: opts.withDescription,
FullName: opts.withFullName,
Email: opts.withEmail,
MemberOfGroups: opts.withMemberOfGroups,
},
}
if err := a.validate(ctx, op); err != nil {
Expand Down
18 changes: 10 additions & 8 deletions internal/auth/ldap/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,19 @@ func TestNewAccount(t *testing.T) {
WithEmail(testCtx, "alice@bob.com"),
WithFullName(testCtx, "alice eve smith"),
WithDn(testCtx, "uid=alice, ou=people, o=test org"),
WithMemberOfGroups(testCtx, "test-group"),
},
want: &Account{
Account: &store.Account{
AuthMethodId: testAuthMethodId,
ScopeId: "global",
LoginName: "test-login-name",
Name: "test-name",
Description: "test-description",
Email: "alice@bob.com",
FullName: "alice eve smith",
Dn: "uid=alice, ou=people, o=test org",
AuthMethodId: testAuthMethodId,
ScopeId: "global",
LoginName: "test-login-name",
Name: "test-name",
Description: "test-description",
Email: "alice@bob.com",
FullName: "alice eve smith",
Dn: "uid=alice, ou=people, o=test org",
MemberOfGroups: "[\"test-group\"]",
},
},
},
Expand Down
10 changes: 10 additions & 0 deletions internal/auth/ldap/ids.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/intglobals"
"github.com/hashicorp/boundary/internal/types/subtypes"
)

Expand Down Expand Up @@ -42,3 +43,12 @@ func newAccountId(ctx context.Context, authMethodId, loginName string) (string,
}
return id, nil
}

func newManagedGroupId(ctx context.Context) (string, error) {
const op = "ldap.newManagedGroupId"
id, err := db.NewPublicId(intglobals.LdapManagedGroupPrefix)
if err != nil {
return "", errors.Wrap(ctx, err, op)
}
return id, nil
}
103 changes: 103 additions & 0 deletions internal/auth/ldap/managed_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package ldap

import (
"context"
"encoding/json"

"github.com/hashicorp/boundary/internal/auth/ldap/store"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/oplog"
"google.golang.org/protobuf/proto"
)

// managedGroupTableName defines the default table name for a Managed Group
const managedGroupTableName = "auth_ldap_managed_group"

// ManagedGroup contains an LDAP managed group. It is assigned to an LDAP AuthMethod
// and updates/deletes to that AuthMethod are cascaded to its Managed Groups.
type ManagedGroup struct {
*store.ManagedGroup
tableName string
}

// NewManagedGroup creates a new in memory ManagedGroup assigned to LDAP
// AuthMethod. Supported options are WithName and WithDescription.
func NewManagedGroup(ctx context.Context, authMethodId string, groupNames []string, opt ...Option) (*ManagedGroup, error) {
const op = "ldap.NewManagedGroup"
switch {
case authMethodId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing auth method id")
case len(groupNames) == 0:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing group names")
}
opts, err := getOpts(opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
n, err := json.Marshal(groupNames)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to marshal group names"))
}
mg := &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
AuthMethodId: authMethodId,
Name: opts.withName,
Description: opts.withDescription,
GroupNames: string(n),
},
}
return mg, nil
}

// AllocManagedGroup makes an empty one in memory
func AllocManagedGroup() *ManagedGroup {
return &ManagedGroup{
ManagedGroup: &store.ManagedGroup{},
}
}

// clone a ManagedGroup.
func (mg *ManagedGroup) clone() *ManagedGroup {
cp := proto.Clone(mg.ManagedGroup)
return &ManagedGroup{
ManagedGroup: cp.(*store.ManagedGroup),
}
}

// TableName returns the table name.
func (mg *ManagedGroup) TableName() string {
if mg.tableName != "" {
return mg.tableName
}
return managedGroupTableName
}

// SetTableName sets the table name.
func (mg *ManagedGroup) SetTableName(n string) {
mg.tableName = n
}

// oplog will create oplog metadata for the ManagedGroup.
func (mg *ManagedGroup) oplog(ctx context.Context, opType oplog.OpType, authMethodScopeId string) (oplog.Metadata, error) {
const op = "ldap.(ManagedGroup).oplog"
switch {
case mg == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing managed group")
case opType == oplog.OpType_OP_TYPE_UNSPECIFIED:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing op type")
case mg.PublicId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing public id")
case mg.AuthMethodId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing auth method id")
case authMethodScopeId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
metadata := oplog.Metadata{
"resource-public-id": []string{mg.PublicId},
"resource-type": []string{"ldap managed group"},
"op-type": []string{opType.String()},
"scope-id": []string{authMethodScopeId},
"auth-method-id": []string{mg.AuthMethodId},
}
return metadata, nil
}
219 changes: 219 additions & 0 deletions internal/auth/ldap/managed_group_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package ldap

import (
"context"
"testing"

"github.com/hashicorp/boundary/internal/auth/ldap/store"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewManagedGroup(t *testing.T) {
t.Parallel()
testCtx := context.Background()
tests := []struct {
name string
ctx context.Context
authMethodId string
groupNames []string
opt []Option
want *ManagedGroup
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "success",
ctx: testCtx,
authMethodId: "test-auth-method-id",
groupNames: []string{"admin"},
opt: []Option{WithName(testCtx, "success"), WithDescription(testCtx, "description")},
want: &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
Name: "success",
Description: "description",
AuthMethodId: "test-auth-method-id",
GroupNames: TestEncodedGrpNames(t, "admin"),
},
},
},
{
name: "missing-auth-method-id",
ctx: testCtx,
groupNames: []string{"admin"},
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing auth method id",
},
{
name: "missing-group-names",
ctx: testCtx,
authMethodId: "test-auth-method-id",
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing group names",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := NewManagedGroup(tc.ctx, tc.authMethodId, tc.groupNames, tc.opt...)
if tc.wantErrMatch != nil {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(tc.wantErrMatch, err))
if tc.wantErrContains != "" {
assert.Contains(err.Error(), tc.wantErrContains)
}
return
}
require.NoError(err)
assert.Equal(tc.want, got)
})
}
}

func TestManagedGroup_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := managedGroupTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := AllocManagedGroup()
require.Equal(defaultTableName, def.TableName())
m := AllocManagedGroup()
m.SetTableName(tc.setNameTo)
assert.Equal(tc.want, m.TableName())
})
}
}

func TestManagedGroup_oplog(t *testing.T) {
t.Parallel()
testCtx := context.Background()
testMg, err := NewManagedGroup(testCtx, "test-id", []string{"admin"})
testMg.PublicId = "test-public-id"
require.NoError(t, err)
tests := []struct {
name string
ctx context.Context
mg *ManagedGroup
opType oplog.OpType
scopeId string
want oplog.Metadata
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "create",
ctx: testCtx,
mg: testMg,
opType: oplog.OpType_OP_TYPE_CREATE,
scopeId: "global",
want: oplog.Metadata{
"auth-method-id": {"test-id"},
"resource-public-id": {"test-public-id"},
"scope-id": {"global"},
"op-type": {oplog.OpType_OP_TYPE_CREATE.String()},
"resource-type": {"ldap managed group"},
},
},
{
name: "update",
ctx: testCtx,
mg: testMg,
opType: oplog.OpType_OP_TYPE_UPDATE,
scopeId: "global",
want: oplog.Metadata{
"auth-method-id": {"test-id"},
"resource-public-id": {"test-public-id"},
"scope-id": {"global"},
"op-type": {oplog.OpType_OP_TYPE_UPDATE.String()},
"resource-type": {"ldap managed group"},
},
},
{
name: "missing-auth-method-id",
ctx: testCtx,
mg: func() *ManagedGroup {
cp := testMg.clone()
cp.AuthMethodId = ""
return cp
}(),
opType: oplog.OpType_OP_TYPE_UPDATE,
scopeId: "global",
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing auth method id",
},
{
name: "missing-scope-id",
ctx: testCtx,
mg: testMg,
opType: oplog.OpType_OP_TYPE_UPDATE,
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing scope id",
},
{
name: "missing-public-id",
ctx: testCtx,
mg: func() *ManagedGroup {
cp := testMg.clone()
cp.PublicId = ""
return cp
}(),
opType: oplog.OpType_OP_TYPE_UPDATE,
scopeId: "global",
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing public id",
},
{
name: "missing-op-type",
ctx: testCtx,
mg: testMg,
scopeId: "global",
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing op type",
},
{
name: "missing-managed-group",
ctx: testCtx,
opType: oplog.OpType_OP_TYPE_UPDATE,
scopeId: "global",
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing managed group",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := tc.mg.oplog(tc.ctx, tc.opType, tc.scopeId)
if tc.wantErrMatch != nil {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(tc.wantErrMatch, err))
if tc.wantErrContains != "" {
assert.Contains(err.Error(), tc.wantErrContains)
}
return
}
require.NoError(err)
assert.Equal(tc.want, got)
})
}
}
Loading

0 comments on commit af92eca

Please sign in to comment.