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

feature (auth/ldap): add managed groups #2760

Merged
merged 4 commits into from
Jan 7, 2023
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
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