-
Notifications
You must be signed in to change notification settings - Fork 289
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature (auth/ldap): add managed groups (#2760)
* 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 fixup! feature (auth/ldap): add managed groups (#2760)
- Loading branch information
Showing
18 changed files
with
2,301 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} | ||
} |
Oops, something went wrong.