Add Output Fields support (#1192)
This commit is contained in:
1 parent
1948984ca2
commit
22d5c88a0b
84 files changed
+5688
-2122
No files matched your search
+37
-23
@@ -9,14 +9,14 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
|
||||
* API `delete` actions now result in a `204` status code and no body when
|
||||
successful. This was not the case previously due to a technical limitation
|
||||
which has now been solved.
|
||||
* When using a `delete` command we now either show success or treat the `404`
|
||||
error the same as any other `404` error, that is, it results in a non-zero
|
||||
status code and an error message. This makes `delete` actions behave the same
|
||||
as other commands, all of which pass through errors to the CLI. Given `-format
|
||||
json` capability, it's relatively easy to perform a check to see whether an
|
||||
error was `404` or something else from within scripts, in conjunction with
|
||||
checking that the returned status code matches the API error status code
|
||||
(`1`).
|
||||
* When using a `delete` command within the CLI we now either show success or
|
||||
treat the `404` error the same as any other `404` error, that is, it results
|
||||
in a non-zero status code and an error message. This makes `delete` actions
|
||||
behave the same as other commands, all of which pass through errors to the
|
||||
CLI. Given `-format json` capability, it's relatively easy to perform a check
|
||||
to see whether an error was `404` or something else from within scripts, in
|
||||
conjunction with checking that the returned status code matches the API error
|
||||
status code (`1`).
|
||||
* When outputting from the CLI in JSON format, the resource information under
|
||||
`item` or `items` (depending on the action) now exactly matches the JSON sent
|
||||
across the wire by the controller, as opposed to matching the Go SDK
|
||||
@@ -34,6 +34,20 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
|
||||
|
||||
### New and Improved
|
||||
|
||||
* permissions: Improving upon the work put into 0.2.0 to limit the fields that
|
||||
are returned when listing as the anonymous user, grants now support a new
|
||||
`output_fields` section. This takes in a comma-delimited (or in JSON format,
|
||||
array) set of values that correspond to the JSON fields returned from an API
|
||||
call (for listing, this will be applied to each resource under the `items`
|
||||
field). If specified for a given ID or resource type (and scoped to specific
|
||||
actions, if included), only the given values will be returned in the output.
|
||||
If no `output_fields` are specified, the defaults are used. For authenticated
|
||||
users this defaults to all fields; for `u_anon` this defaults to the fields
|
||||
useful for navigating to and authenticating to the system. In either case,
|
||||
this is overridable. See the [permissions
|
||||
documentation](https://www.boundaryproject.io/docs/concepts/security/permissions)
|
||||
for more information on why and when to use this. This currently only applies
|
||||
to top-level fields in the response.
|
||||
* cli/api/sdk: Add support to request additional OIDC claims scope values from
|
||||
the OIDC provider when making an authentication request.
|
||||
([PR](https://github.com/hashicorp/boundary/pull/1175)).
|
||||
@@ -50,7 +64,7 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
|
||||
|
||||
Boundary now provides a new OIDC auth method parameter `claims_scopes` which
|
||||
allows you to add multiple additional claims scope values to an OIDC auth
|
||||
method configuration.
|
||||
method configuration.
|
||||
|
||||
For information on claims scope values see: [Scope Claims in the OIDC
|
||||
specification](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims)
|
||||
@@ -65,7 +79,7 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
|
||||
used to allow principals to see values in list results without also giving
|
||||
`read` or other capabilities on the resources. The default scope permissions
|
||||
have been updated to convey `no-op,list` instead of `read,list`.
|
||||
[PR](https://github.com/hashicorp/boundary/pull/1138)
|
||||
([PR](https://github.com/hashicorp/boundary/pull/1138))
|
||||
* cli/api/sdk: User resources have new attributes for:
|
||||
* Primary Account ID
|
||||
* Login Name
|
||||
@@ -79,10 +93,10 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
|
||||
* cli: Support for reading and deleting the user's own token via the new
|
||||
`read:self` and `delete:self` actions on auth tokens. If no token ID is
|
||||
provided, the stored token's ID will be used (after prompting), or `"self"`
|
||||
can be set to the ID to trigger this behavior without prompting.
|
||||
([PR](https://github.com/hashicorp/boundary/pull/1162))
|
||||
can be set as the value of the `-id` parameter to trigger this behavior
|
||||
without prompting. ([PR](https://github.com/hashicorp/boundary/pull/1162))
|
||||
* cli: New `logout` command deletes the current token in Boundary and forgets it
|
||||
from the local system credential store
|
||||
from the local system credential store, respecting `-token-name`
|
||||
([PR](https://github.com/hashicorp/boundary/pull/1134))
|
||||
* config: The `name` field for workers and controllers now supports being set
|
||||
from environment variables or a file on disk
|
||||
@@ -91,7 +105,7 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
|
||||
### Bug Fixes
|
||||
|
||||
* cors: Fix allowing all origins by default
|
||||
[PR](https://github.com/hashicorp/boundary/pull/1134)
|
||||
([PR](https://github.com/hashicorp/boundary/pull/1134))
|
||||
* cli: It is now an error to run `boundary database migrate` on an uninitalized db.
|
||||
Use `boundary database init` instead.
|
||||
([PR](https://github.com/hashicorp/boundary/pull/1184))
|
||||
@@ -141,19 +155,19 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
|
||||
update, delete, and list (see new cli `oidc` subcommands available on CRUDL
|
||||
operations for examples), as well as the ability to authenticate against it
|
||||
via the SDK, CLI, admin UI, and desktop client.
|
||||
[PR](https://github.com/hashicorp/boundary/pull/1090)
|
||||
([PR](https://github.com/hashicorp/boundary/pull/1090))
|
||||
* server: When performing recursive listing, `list` action is no longer required
|
||||
to be granted to the calling user. Instead, the given scope acts as the root
|
||||
point (so only results under that scope will be shown), and `list` grant is
|
||||
evaluated per-scope. [PR](https://github.com/hashicorp/boundary/pull/1016)
|
||||
evaluated per-scope. ([PR](https://github.com/hashicorp/boundary/pull/1016))
|
||||
* database init: If the database is already initialized, return 0 as the exit
|
||||
code. This matches how the `database migrate` command works.
|
||||
[PR](https://github.com/hashicorp/boundary/pull/1033)
|
||||
([PR](https://github.com/hashicorp/boundary/pull/1033))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* server: Roles for auto generated scopes are now generated at database init.
|
||||
[PR](https://github.com/hashicorp/boundary/pull/996)
|
||||
([PR](https://github.com/hashicorp/boundary/pull/996))
|
||||
* cli: Don't panic on certain commands when outputting in `json` format
|
||||
([Issue](https://github.com/hashicorp/boundary/pull/992),
|
||||
[PR](https://github.com/hashicorp/boundary/pull/1095))
|
||||
@@ -190,25 +204,25 @@ to call out in this changelog. The full set of open issues is on GitHub.
|
||||
`ResponseMap()` and `ResponseBody()`, resources simply expose `Response()`.
|
||||
This higher-level response object contains the map and body, and also exposes
|
||||
`StatusCode()` in place of indivdidual resources.
|
||||
[PR](https://github.com/hashicorp/boundary/pull/962)
|
||||
([PR](https://github.com/hashicorp/boundary/pull/962))
|
||||
* cli: In `json` output format, a resource item is now an object under the
|
||||
top-level key `item`; a list of resource items is now an list of objects under
|
||||
the top-level key `items`. This preserves the top level for putting in other
|
||||
useful information later on (and the HTTP status code is included now).
|
||||
[PR](https://github.com/hashicorp/boundary/pull/962)
|
||||
([PR](https://github.com/hashicorp/boundary/pull/962))
|
||||
* cli: In `json` output format, errors are now serialized as a JSON object with
|
||||
an `error` key instead of outputting normal text
|
||||
[PR](https://github.com/hashicorp/boundary/pull/962)
|
||||
([PR](https://github.com/hashicorp/boundary/pull/962))
|
||||
* cli: All errors, including API errors, are now written to `stderr`. Previously
|
||||
in the default table format, API errors would be written to `stdout`.
|
||||
[PR](https://github.com/hashicorp/boundary/pull/962)
|
||||
([PR](https://github.com/hashicorp/boundary/pull/962))
|
||||
* cli: Error return codes have been standardized across CLI commands. An error
|
||||
code of `1` indicates an error generated from the actual controller API; an
|
||||
error code of `2` is an error encountered due to the CLI command's logic; and
|
||||
an error code of `3` indicates an error that was caused due to user input to
|
||||
the command. (There is some nuance sometimes whether an error is really due to
|
||||
user input or not, but we attempt to be consistent.)
|
||||
[PR](https://github.com/hashicorp/boundary/pull/976)
|
||||
([PR](https://github.com/hashicorp/boundary/pull/976))
|
||||
|
||||
### New and Improved
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package globals
|
||||
|
||||
// This set of consts is intended to be a place to collect commonly-used field
|
||||
// names. Since these might be used outside of the internal code (e.g. by SDK or
|
||||
// CLI), it's outside internal/ here in globals. Some of these are already const
|
||||
// elsewhere in the code but we should migrate to using a single source.
|
||||
const (
|
||||
IdField = "id"
|
||||
VersionField = "version"
|
||||
NameField = "name"
|
||||
DescriptionField = "description"
|
||||
CreatedTimeField = "created_time"
|
||||
UpdatedTimeField = "updated_time"
|
||||
TypeField = "type"
|
||||
AttributesField = "attributes"
|
||||
ScopeIdField = "scope_id"
|
||||
ScopeField = "scope"
|
||||
AuthMethodIdField = "auth_method_id"
|
||||
AccountIdField = "account_id"
|
||||
UserIdField = "user_id"
|
||||
IsPrimaryField = "is_primary"
|
||||
AuthorizedActionsField = "authorized_actions"
|
||||
AuthorizedCollectionActionsField = "authorized_collection_actions"
|
||||
ExpirationTimeField = "expiration_time"
|
||||
ApproximateLastUsedTimeField = "approximate_last_used_time"
|
||||
MembersField = "members"
|
||||
MemberIdsField = "member_ids"
|
||||
HostCatalogIdField = "host_catalog_id"
|
||||
HostSetIdsField = "host_set_ids"
|
||||
HostIdsField = "host_ids"
|
||||
PrincipalIdsField = "principal_ids"
|
||||
PrincipalsField = "principals"
|
||||
GrantScopeIdField = "grant_scope_id"
|
||||
GrantsField = "grants"
|
||||
GrantStringsField = "grant_strings"
|
||||
PrimaryAuthMethodId = "primary_auth_method_id"
|
||||
TargetIdField = "target_id"
|
||||
HostIdField = "host_id"
|
||||
HostSetIdField = "host_set_id"
|
||||
HostSetsField = "host_sets"
|
||||
AuthTokenIdField = "auth_token_id"
|
||||
EndpointField = "endpoint"
|
||||
CertificateField = "certificate"
|
||||
TerminationReasonField = "termination_reason"
|
||||
StatusField = "status"
|
||||
StatesField = "states"
|
||||
SessionConnectionLimitField = "session_connection_limit"
|
||||
SessionMaxSecondsField = "session_max_seconds"
|
||||
WorkerFilterField = "worker_filter"
|
||||
AccountIdsField = "account_ids"
|
||||
AccountsField = "accounts"
|
||||
LoginNameField = "login_name"
|
||||
FullNameField = "full_name"
|
||||
PrimaryAccountIdField = "primary_account_id"
|
||||
EmailField = "email"
|
||||
)
|
||||
+2
-2
@@ -15,8 +15,8 @@ const (
|
||||
)
|
||||
|
||||
type (
|
||||
ContextMaxRequestSizeType int
|
||||
ContextOriginalRequestPathType int
|
||||
ContextMaxRequestSizeType struct{}
|
||||
ContextOriginalRequestPathType struct{}
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -2,16 +2,23 @@ package auth_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"github.com/hashicorp/boundary/api/authtokens"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
"github.com/hashicorp/boundary/internal/authtoken"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller"
|
||||
authmethodsservice "github.com/hashicorp/boundary/internal/servers/controller/handlers/authmethods"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -106,3 +113,182 @@ func TestFetchActionSetForId(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecursiveListingDifferentOutputFields(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
tc := controller.NewTestController(t, nil)
|
||||
defer tc.Shutdown()
|
||||
|
||||
conn := tc.DbConn()
|
||||
client := tc.Client()
|
||||
token := tc.Token()
|
||||
client.SetToken(token.Token)
|
||||
|
||||
// Set some global permissions so we can read the auth method there. Here we
|
||||
// will expect the defaults.
|
||||
globalRole := iam.TestRole(t, conn, scope.Global.String())
|
||||
iam.TestUserRole(t, conn, globalRole.PublicId, token.UserId)
|
||||
iam.TestUserRole(t, conn, globalRole.PublicId, auth.AnonymousUserId)
|
||||
iam.TestRoleGrant(t, conn, globalRole.PublicId, "id=*;type=auth-method;actions=list,no-op")
|
||||
|
||||
// Create some users at the org level, and some role grants for them
|
||||
org, _ := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId), iam.WithSkipAdminRoleCreation(true), iam.WithSkipDefaultRoleCreation(true))
|
||||
orgAm1 := password.TestAuthMethod(t, conn, org.GetPublicId(), password.WithName("orgam1"), password.WithDescription("orgam1"))
|
||||
orgAm2 := password.TestAuthMethod(t, conn, org.GetPublicId(), password.WithName("orgam2"), password.WithDescription("orgam2"))
|
||||
orgRole := iam.TestRole(t, conn, org.GetPublicId())
|
||||
iam.TestUserRole(t, conn, orgRole.PublicId, token.UserId)
|
||||
// The first and second will actually not take effect for output
|
||||
// grantsbecause it's a list and output fields are scoped by action. So we
|
||||
// expect only name and scope_id for the auth methods in the scope, using
|
||||
// two patterns below. However, since you need an action on the resource for
|
||||
// list to return anything, those grants allow us to list the items, while
|
||||
// also verifying that those output fields don't take effect for the wrong
|
||||
// action.
|
||||
iam.TestRoleGrant(t, conn, orgRole.PublicId, fmt.Sprintf("id=%s;actions=read;output_fields=id,version", orgAm1.GetPublicId()))
|
||||
iam.TestRoleGrant(t, conn, orgRole.PublicId, fmt.Sprintf("id=%s;actions=read;output_fields=description", orgAm2.GetPublicId()))
|
||||
iam.TestRoleGrant(t, conn, orgRole.PublicId, "id=*;type=auth-method;output_fields=name")
|
||||
iam.TestRoleGrant(t, conn, orgRole.PublicId, "id=*;type=auth-method;actions=list;output_fields=scope_id")
|
||||
|
||||
amClient := authmethods.NewClient(tc.Client())
|
||||
resp, err := amClient.List(tc.Context(), scope.Global.String(), authmethods.WithRecursive(true))
|
||||
// Basic sanity checks
|
||||
require.NoError(err)
|
||||
require.NotNil(resp)
|
||||
require.NotNil(resp.GetItems())
|
||||
assert.Len(resp.GetItems().([]*authmethods.AuthMethod), 3)
|
||||
items := resp.GetResponse().Map["items"].([]interface{})
|
||||
require.NotNil(items)
|
||||
|
||||
// The default generated roles don't have output field definitions for them,
|
||||
// so we look for "scope" and if it's there we skip. Otherwise, we look for
|
||||
// "version" and if found expect that we will see a global auth method also
|
||||
// containing name and description. Otherwise it's an org role and we expect
|
||||
// to see name and scope_id. At the end we verify we've seen the exact
|
||||
// numbers of each we expect. Note that we expect 3 at global because of the
|
||||
// auto generated one.
|
||||
var globalAms, orgAms int
|
||||
for _, item := range items {
|
||||
m := item.(map[string]interface{})
|
||||
require.NotNil(m)
|
||||
switch m["scope_id"].(string) {
|
||||
case scope.Global.String():
|
||||
// Validate that it contains fields anon shouldn't; we'll check anon
|
||||
// later
|
||||
assert.Contains(m, "created_time")
|
||||
assert.Contains(m, "attributes")
|
||||
globalAms++
|
||||
|
||||
default:
|
||||
assert.Len(m, 2)
|
||||
assert.Contains(m, "name")
|
||||
assert.Contains(m, "scope_id")
|
||||
orgAms++
|
||||
}
|
||||
}
|
||||
assert.Equal(1, globalAms)
|
||||
assert.Equal(2, orgAms)
|
||||
|
||||
// Now act as the anonymous user and ensure that the fields we checked for
|
||||
// before are not available
|
||||
tc.Client().SetToken("")
|
||||
amClient = authmethods.NewClient(tc.Client())
|
||||
resp, err = amClient.List(tc.Context(), scope.Global.String(), authmethods.WithRecursive(true))
|
||||
require.NoError(err)
|
||||
require.NotNil(resp)
|
||||
require.NotNil(resp.GetItems())
|
||||
assert.Len(resp.GetItems().([]*authmethods.AuthMethod), 1)
|
||||
item := resp.GetResponse().Map["items"].([]interface{})[0].(map[string]interface{})
|
||||
assert.NotContains(item, "created_time")
|
||||
assert.NotContains(item, "attributes")
|
||||
}
|
||||
|
||||
func TestSelfReadingDifferentOutputFields(t *testing.T) {
|
||||
tc := controller.NewTestController(t, nil)
|
||||
defer tc.Shutdown()
|
||||
|
||||
conn := tc.DbConn()
|
||||
|
||||
s, err := authmethodsservice.NewService(tc.Kms(),
|
||||
tc.Controller().PasswordAuthRepoFn,
|
||||
tc.Controller().OidcRepoFn,
|
||||
tc.Controller().IamRepoFn,
|
||||
tc.Controller().AuthTokenRepoFn)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create two auth tokens belonging to different users in the org. Each will
|
||||
// have grants below and should be able to read the other token but see more
|
||||
// fields for its own.
|
||||
org, _ := iam.TestScopes(t, tc.IamRepo(), iam.WithSkipAdminRoleCreation(true), iam.WithSkipDefaultRoleCreation(true))
|
||||
am := password.TestAuthMethod(t, conn, org.GetPublicId())
|
||||
acct1 := password.TestAccount(t, conn, am.GetPublicId(), "acct1")
|
||||
acct2 := password.TestAccount(t, conn, am.GetPublicId(), "acct2")
|
||||
user1 := iam.TestUser(t, tc.IamRepo(), org.GetPublicId(), iam.WithAccountIds(acct1.GetPublicId()))
|
||||
user2 := iam.TestUser(t, tc.IamRepo(), org.GetPublicId(), iam.WithAccountIds(acct2.GetPublicId()))
|
||||
at1, err := tc.AuthTokenRepo().CreateAuthToken(tc.Context(), user1, acct1.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
token1, err := s.ConvertInternalAuthTokenToApiAuthToken(tc.Context(), at1)
|
||||
require.NoError(t, err)
|
||||
at2, err := tc.AuthTokenRepo().CreateAuthToken(tc.Context(), user2, acct2.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
token2, err := s.ConvertInternalAuthTokenToApiAuthToken(tc.Context(), at2)
|
||||
require.NoError(t, err)
|
||||
|
||||
orgRole := iam.TestRole(t, conn, org.GetPublicId())
|
||||
iam.TestUserRole(t, conn, orgRole.PublicId, user1.GetPublicId())
|
||||
iam.TestUserRole(t, conn, orgRole.PublicId, user2.GetPublicId())
|
||||
iam.TestRoleGrant(t, conn, orgRole.PublicId, "id=*;type=auth-token;actions=read:self;output_fields=account_id")
|
||||
iam.TestRoleGrant(t, conn, orgRole.PublicId, "id=*;type=auth-token;actions=read;output_fields=id,scope_id")
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
token string
|
||||
lookupId string
|
||||
keys []string
|
||||
}{
|
||||
{
|
||||
name: "at1 self",
|
||||
token: token1.GetToken(),
|
||||
lookupId: token1.GetId(),
|
||||
keys: []string{"id", "scope_id", "account_id"},
|
||||
},
|
||||
{
|
||||
name: "at2 self",
|
||||
token: token2.GetToken(),
|
||||
lookupId: token2.GetId(),
|
||||
keys: []string{"id", "scope_id", "account_id"},
|
||||
},
|
||||
{
|
||||
name: "at1 other",
|
||||
token: token1.GetToken(),
|
||||
lookupId: token2.GetId(),
|
||||
keys: []string{"id", "scope_id"},
|
||||
},
|
||||
{
|
||||
name: "at2 other",
|
||||
token: token2.GetToken(),
|
||||
lookupId: token1.GetId(),
|
||||
keys: []string{"id", "scope_id"},
|
||||
},
|
||||
}
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
client := tc.Client().Clone()
|
||||
client.SetToken(test.token)
|
||||
atClient := authtokens.NewClient(client)
|
||||
resp, err := atClient.Read(tc.Context(), test.lookupId)
|
||||
require.NoError(err)
|
||||
require.NotNil(resp)
|
||||
require.NotNil(resp.GetItem())
|
||||
item := resp.GetResponse().Map
|
||||
require.NotNil(item)
|
||||
keys := make([]string, 0, len(item))
|
||||
for k := range item {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(test.keys)
|
||||
sort.Strings(keys)
|
||||
assert.Equal(test.keys, keys)
|
||||
})
|
||||
}
|
||||
}
|
||||
+36
-3
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/gen/controller/tokens"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
@@ -158,6 +159,19 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
|
||||
|
||||
ret.Scope = new(scopes.ScopeInfo)
|
||||
|
||||
// Note: we don't call RequestContextFromCtx here because that performs a
|
||||
// SelfOrDefault. That's useful in the handlers, but here we don't want to
|
||||
// do anything if it's nil. That's mainly useful in tests where
|
||||
// DisableAuthEntirely is set. Later, if we don't have the value set at all,
|
||||
// we have some safeguards to ensure we fail safe (e.g. no output fields at
|
||||
// all). So it provides a good indication as well whether we have set this
|
||||
// where and when needed.
|
||||
var reqInfo *requests.RequestContext
|
||||
reqInfoRaw := ctx.Value(requests.ContextRequestInformationKey)
|
||||
if reqInfoRaw != nil {
|
||||
reqInfo = reqInfoRaw.(*requests.RequestContext)
|
||||
}
|
||||
|
||||
// In tests we often simply disable auth so we can test the service handlers
|
||||
// without fuss
|
||||
if v.requestInfo.DisableAuthEntirely {
|
||||
@@ -203,6 +217,9 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
|
||||
}
|
||||
}
|
||||
ret.UserId = v.requestInfo.userIdOverride
|
||||
if reqInfo != nil {
|
||||
reqInfo.UserId = ret.UserId
|
||||
}
|
||||
ret.Error = nil
|
||||
return
|
||||
}
|
||||
@@ -249,6 +266,11 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
|
||||
}
|
||||
}
|
||||
|
||||
if reqInfo != nil {
|
||||
reqInfo.UserId = ret.UserId
|
||||
reqInfo.OutputFields = authResults.OutputFields
|
||||
}
|
||||
|
||||
ret.Error = nil
|
||||
return
|
||||
}
|
||||
@@ -513,16 +535,16 @@ func (v verifier) performAuthCheck() (aclResults perms.ACLResults, userId string
|
||||
// FetchActionSetForId returns the allowed actions for a given ID using the
|
||||
// current set of ACLs and all other parameters the same (user, etc.)
|
||||
func (r *VerifyResults) FetchActionSetForId(ctx context.Context, id string, availableActions action.ActionSet, opt ...Option) action.ActionSet {
|
||||
return r.fetchActions(ctx, id, resource.Unknown, availableActions, opt...)
|
||||
return r.fetchActions(id, resource.Unknown, availableActions, opt...)
|
||||
}
|
||||
|
||||
// FetchActionSetForType returns the allowed actions for a given collection type
|
||||
// using the current set of ACLs and all other parameters the same (user, etc.)
|
||||
func (r *VerifyResults) FetchActionSetForType(ctx context.Context, typ resource.Type, availableActions action.ActionSet, opt ...Option) action.ActionSet {
|
||||
return r.fetchActions(ctx, "", typ, availableActions, opt...)
|
||||
return r.fetchActions("", typ, availableActions, opt...)
|
||||
}
|
||||
|
||||
func (r *VerifyResults) fetchActions(ctx context.Context, id string, typ resource.Type, availableActions action.ActionSet, opt ...Option) action.ActionSet {
|
||||
func (r *VerifyResults) fetchActions(id string, typ resource.Type, availableActions action.ActionSet, opt ...Option) action.ActionSet {
|
||||
switch {
|
||||
case r.v.requestInfo.DisableAuthEntirely,
|
||||
r.v.requestInfo.TokenFormat == AuthTokenTypeRecoveryKms:
|
||||
@@ -560,6 +582,17 @@ func (r *VerifyResults) fetchActions(ctx context.Context, id string, typ resourc
|
||||
return ret
|
||||
}
|
||||
|
||||
func (r *VerifyResults) FetchOutputFields(res perms.Resource, act action.Type) perms.OutputFieldsMap {
|
||||
switch {
|
||||
case r.v.requestInfo.TokenFormat == AuthTokenTypeRecoveryKms:
|
||||
return perms.OutputFieldsMap{"*": true}
|
||||
case r.v.requestInfo.DisableAuthEntirely:
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.v.acl.Allowed(res, act).OutputFields
|
||||
}
|
||||
|
||||
// GetTokenFromRequest pulls the token from either the Authorization header or
|
||||
// split cookies and parses it. If it cannot be parsed successfully, the issue
|
||||
// is logged and we return blank, so logic will continue as the anonymous user.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/hashicorp/boundary/sdk/strutil"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
// CalculateAuthorizedCollectionActions returns authorized actions for the given
|
||||
// inputs.
|
||||
//
|
||||
// NOTE: Eventually we should unit test this, but for now every service handler
|
||||
// is validating the results of this (as it was pulled out of the service
|
||||
// handlers).
|
||||
func CalculateAuthorizedCollectionActions(ctx context.Context,
|
||||
authResults VerifyResults,
|
||||
mapToRange map[resource.Type]action.ActionSet,
|
||||
scopeId, pin string) (map[string]*structpb.ListValue, error) {
|
||||
res := &perms.Resource{
|
||||
ScopeId: scopeId,
|
||||
Pin: pin,
|
||||
}
|
||||
// Range over the defined collections and check permissions against those
|
||||
// collections.
|
||||
var ret map[string]*structpb.ListValue
|
||||
for k, v := range mapToRange {
|
||||
res.Type = k
|
||||
acts := authResults.FetchActionSetForType(ctx, k, v, WithResource(res)).Strings()
|
||||
if len(acts) > 0 {
|
||||
if ret == nil {
|
||||
ret = make(map[string]*structpb.ListValue)
|
||||
}
|
||||
lv, err := structpb.NewList(strutil.StringListToInterfaceList(acts))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[k.String()+"s"] = lv
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
@@ -33,9 +33,7 @@ type options struct {
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
return options{
|
||||
withUserId: AnonymousUserId,
|
||||
}
|
||||
return options{}
|
||||
}
|
||||
|
||||
func WithScopeId(id string) Option {
|
||||
|
||||
@@ -13,15 +13,11 @@ import (
|
||||
// Test_GetOpts provides unit tests for GetOpts and all the options
|
||||
func Test_GetOpts(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := getOpts()
|
||||
assert.Equal(t, options{
|
||||
withUserId: AnonymousUserId,
|
||||
}, opts)
|
||||
|
||||
withKms := new(kms.Kms)
|
||||
res := new(perms.Resource)
|
||||
|
||||
opts = getOpts(
|
||||
opts := getOpts(
|
||||
WithScopeId("foo"),
|
||||
WithPin("bar"),
|
||||
WithId("zip"),
|
||||
|
||||
@@ -11,40 +11,48 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestAuthMethod creates a password auth methods to the provided DB with the
|
||||
// provided scope id. If any errors are encountered during the creation of the
|
||||
// auth methods, the test will fail.
|
||||
func TestAuthMethod(t *testing.T, conn *gorm.DB, scopeId string, opt ...Option) *AuthMethod {
|
||||
t.Helper()
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
w := db.New(conn)
|
||||
cat, err := NewAuthMethod(scopeId, opt...)
|
||||
assert.NoError(err)
|
||||
require.NotNil(cat)
|
||||
id, err := newAuthMethodId()
|
||||
assert.NoError(err)
|
||||
require.NotEmpty(id)
|
||||
cat.PublicId = id
|
||||
|
||||
conf := NewArgon2Configuration()
|
||||
require.NotNil(conf)
|
||||
conf.PrivateId, err = newArgon2ConfigurationId()
|
||||
require.NoError(err)
|
||||
conf.PasswordMethodId = cat.PublicId
|
||||
cat.PasswordConfId = conf.PrivateId
|
||||
|
||||
ctx := context.Background()
|
||||
_, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, iw db.Writer) error {
|
||||
require.NoError(iw.Create(ctx, conf))
|
||||
return iw.Create(ctx, cat)
|
||||
},
|
||||
)
|
||||
|
||||
require.NoError(err2)
|
||||
return cat
|
||||
}
|
||||
|
||||
// TestAuthMethods creates count number of password auth methods to the provided DB
|
||||
// with the provided scope id. If any errors are encountered during the creation of
|
||||
// the auth methods, the test will fail.
|
||||
func TestAuthMethods(t *testing.T, conn *gorm.DB, scopeId string, count int) []*AuthMethod {
|
||||
t.Helper()
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
w := db.New(conn)
|
||||
var auts []*AuthMethod
|
||||
for i := 0; i < count; i++ {
|
||||
cat, err := NewAuthMethod(scopeId)
|
||||
assert.NoError(err)
|
||||
require.NotNil(cat)
|
||||
id, err := newAuthMethodId()
|
||||
assert.NoError(err)
|
||||
require.NotEmpty(id)
|
||||
cat.PublicId = id
|
||||
|
||||
conf := NewArgon2Configuration()
|
||||
require.NotNil(conf)
|
||||
conf.PrivateId, err = newArgon2ConfigurationId()
|
||||
require.NoError(err)
|
||||
conf.PasswordMethodId = cat.PublicId
|
||||
cat.PasswordConfId = conf.PrivateId
|
||||
|
||||
ctx := context.Background()
|
||||
_, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, iw db.Writer) error {
|
||||
require.NoError(iw.Create(ctx, conf))
|
||||
return iw.Create(ctx, cat)
|
||||
},
|
||||
)
|
||||
|
||||
require.NoError(err2)
|
||||
auts = append(auts, cat)
|
||||
auts = append(auts, TestAuthMethod(t, conn, scopeId))
|
||||
}
|
||||
return auts
|
||||
}
|
||||
|
||||
@@ -3,10 +3,13 @@ package auth
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
)
|
||||
|
||||
// DisabledAuthTestContext is meant for testing, and uses a context that has auth checking entirely disabled
|
||||
// DisabledAuthTestContext is meant for testing, and uses a context that has
|
||||
// auth checking entirely disabled. Supported options: WithScopeId an WithUserId
|
||||
// are used directly; WithKms is passed through into the verifier context.
|
||||
func DisabledAuthTestContext(iamRepoFn common.IamRepoFactory, scopeId string, opt ...Option) context.Context {
|
||||
reqInfo := RequestInfo{DisableAuthEntirely: true}
|
||||
opts := getOpts(opt...)
|
||||
@@ -15,5 +18,9 @@ func DisabledAuthTestContext(iamRepoFn common.IamRepoFactory, scopeId string, op
|
||||
reqInfo.scopeIdOverride = scopeId
|
||||
}
|
||||
reqInfo.userIdOverride = opts.withUserId
|
||||
return NewVerifierContext(context.Background(), nil, iamRepoFn, nil, nil, opts.withKms, reqInfo)
|
||||
if reqInfo.userIdOverride == "" {
|
||||
reqInfo.userIdOverride = "u_auth"
|
||||
}
|
||||
requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
||||
return NewVerifierContext(requestContext, nil, iamRepoFn, nil, nil, opts.withKms, reqInfo)
|
||||
}
|
||||
@@ -20,6 +20,9 @@ import (
|
||||
var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}
|
||||
|
||||
func ScopeInfoForOutput(scp *scopes.ScopeInfo, maxLength int) string {
|
||||
if scp == nil {
|
||||
return " <not included in response>"
|
||||
}
|
||||
vals := map[string]interface{}{
|
||||
"ID": scp.Id,
|
||||
"Type": scp.Type,
|
||||
|
||||
@@ -275,8 +275,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*accounts.Account)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -193,31 +193,43 @@ func (c *Command) printListTable(items []*accounts.Account) string {
|
||||
"",
|
||||
"Account information:",
|
||||
}
|
||||
for i, m := range items {
|
||||
for i, item := range items {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
if item.Id != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", m.Id),
|
||||
fmt.Sprintf(" Version: %d", m.Version),
|
||||
fmt.Sprintf(" Type: %s", m.Type),
|
||||
fmt.Sprintf(" ID: %s", item.Id),
|
||||
)
|
||||
} else {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", "(not available)"),
|
||||
)
|
||||
}
|
||||
if m.Name != "" {
|
||||
if item.Version > 0 {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Name: %s", m.Name),
|
||||
fmt.Sprintf(" Version: %d", item.Version),
|
||||
)
|
||||
}
|
||||
if m.Description != "" {
|
||||
if item.Type != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Description: %s", m.Description),
|
||||
fmt.Sprintf(" Type: %s", item.Type),
|
||||
)
|
||||
}
|
||||
if len(m.AuthorizedActions) > 0 {
|
||||
if item.Name != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Name: %s", item.Name),
|
||||
)
|
||||
}
|
||||
if item.Description != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Description: %s", item.Description),
|
||||
)
|
||||
}
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
output = append(output,
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(6, m.AuthorizedActions),
|
||||
base.WrapSlice(6, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -225,16 +237,27 @@ func (c *Command) printListTable(items []*accounts.Account) string {
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func printItemTable(item *accounts.Account) string {
|
||||
nonAttributeMap := map[string]interface{}{
|
||||
"ID": item.Id,
|
||||
"Version": item.Version,
|
||||
"Type": item.Type,
|
||||
"Created Time": item.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": item.UpdatedTime.Local().Format(time.RFC1123),
|
||||
"Auth Method ID": item.AuthMethodId,
|
||||
func printItemTable(result api.GenericResult) string {
|
||||
item := result.GetItem().(*accounts.Account)
|
||||
nonAttributeMap := map[string]interface{}{}
|
||||
if item.Id != "" {
|
||||
nonAttributeMap["ID"] = item.Id
|
||||
}
|
||||
if item.Version != 0 {
|
||||
nonAttributeMap["Version"] = item.Version
|
||||
}
|
||||
if item.Type != "" {
|
||||
nonAttributeMap["Type"] = item.Type
|
||||
}
|
||||
if !item.CreatedTime.IsZero() {
|
||||
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if !item.UpdatedTime.IsZero() {
|
||||
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if item.AuthMethodId != "" {
|
||||
nonAttributeMap["Auth Method ID"] = item.AuthMethodId
|
||||
}
|
||||
|
||||
if item.Name != "" {
|
||||
nonAttributeMap["Name"] = item.Name
|
||||
}
|
||||
@@ -248,9 +271,14 @@ func printItemTable(item *accounts.Account) string {
|
||||
"",
|
||||
"Account information:",
|
||||
base.WrapMap(2, maxLength+2, nonAttributeMap),
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
}
|
||||
|
||||
if item.Scope != nil {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
)
|
||||
}
|
||||
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
|
||||
@@ -216,8 +216,7 @@ func (c *OidcCommand) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*accounts.Account)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -216,8 +216,7 @@ func (c *PasswordCommand) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*accounts.Account)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -242,8 +242,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*authmethods.AuthMethod)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
)
|
||||
|
||||
@@ -76,46 +78,54 @@ func (c *Command) printListTable(items []*authmethods.AuthMethod) string {
|
||||
"",
|
||||
"Auth Method information:",
|
||||
}
|
||||
for i, m := range items {
|
||||
for i, item := range items {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
if item.Id != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", m.Id),
|
||||
fmt.Sprintf(" ID: %s", item.Id),
|
||||
)
|
||||
} else {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", "(not available)"),
|
||||
)
|
||||
}
|
||||
if c.FlagRecursive {
|
||||
if c.FlagRecursive && item.ScopeId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Scope ID: %s", m.Scope.Id),
|
||||
fmt.Sprintf(" Scope ID: %s", item.ScopeId),
|
||||
)
|
||||
}
|
||||
if true {
|
||||
if item.Version > 0 {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Version: %d", m.Version),
|
||||
fmt.Sprintf(" Type: %s", m.Type),
|
||||
fmt.Sprintf(" Version: %d", item.Version),
|
||||
)
|
||||
}
|
||||
if m.Name != "" {
|
||||
if item.Type != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Name: %s", m.Name),
|
||||
fmt.Sprintf(" Type: %s", item.Type),
|
||||
)
|
||||
}
|
||||
if m.Description != "" {
|
||||
if item.Name != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Description: %s", m.Description),
|
||||
fmt.Sprintf(" Name: %s", item.Name),
|
||||
)
|
||||
}
|
||||
if true {
|
||||
if item.Description != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Is Primary For Scope: %t", m.IsPrimary),
|
||||
fmt.Sprintf(" Description: %s", item.Description),
|
||||
)
|
||||
}
|
||||
if item.IsPrimary {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Is Primary For Scope: %t", item.IsPrimary),
|
||||
)
|
||||
}
|
||||
|
||||
if len(m.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
output = append(output,
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(6, m.AuthorizedActions),
|
||||
base.WrapSlice(6, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -123,45 +133,63 @@ func (c *Command) printListTable(items []*authmethods.AuthMethod) string {
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func printItemTable(in *authmethods.AuthMethod) string {
|
||||
nonAttributeMap := map[string]interface{}{
|
||||
"ID": in.Id,
|
||||
"Version": in.Version,
|
||||
"Type": in.Type,
|
||||
"Created Time": in.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": in.UpdatedTime.Local().Format(time.RFC1123),
|
||||
"Is Primary For Scope": in.IsPrimary,
|
||||
func printItemTable(result api.GenericResult) string {
|
||||
item := result.GetItem().(*authmethods.AuthMethod)
|
||||
nonAttributeMap := map[string]interface{}{}
|
||||
if item.Id != "" {
|
||||
nonAttributeMap["ID"] = item.Id
|
||||
}
|
||||
if item.Version != 0 {
|
||||
nonAttributeMap["Version"] = item.Version
|
||||
}
|
||||
if item.Type != "" {
|
||||
nonAttributeMap["Type"] = item.Type
|
||||
}
|
||||
if !item.CreatedTime.IsZero() {
|
||||
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if !item.UpdatedTime.IsZero() {
|
||||
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if item.Name != "" {
|
||||
nonAttributeMap["Name"] = item.Name
|
||||
}
|
||||
if item.Description != "" {
|
||||
nonAttributeMap["Description"] = item.Description
|
||||
}
|
||||
if result.GetResponse() != nil && result.GetResponse().Map != nil {
|
||||
if result.GetResponse().Map[globals.IsPrimaryField] != nil {
|
||||
nonAttributeMap["Is Primary For Scope"] = item.IsPrimary
|
||||
}
|
||||
}
|
||||
|
||||
if in.Name != "" {
|
||||
nonAttributeMap["Name"] = in.Name
|
||||
}
|
||||
if in.Description != "" {
|
||||
nonAttributeMap["Description"] = in.Description
|
||||
}
|
||||
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap)
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap)
|
||||
|
||||
ret := []string{
|
||||
"",
|
||||
"Auth Method information:",
|
||||
base.WrapMap(2, maxLength+2, nonAttributeMap),
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(in.Scope, maxLength),
|
||||
}
|
||||
|
||||
if len(in.AuthorizedActions) > 0 {
|
||||
if item.Scope != nil {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, in.AuthorizedActions),
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
)
|
||||
}
|
||||
|
||||
if len(in.AuthorizedCollectionActions) > 0 {
|
||||
keys := make([]string, 0, len(in.AuthorizedCollectionActions))
|
||||
for k := range in.AuthorizedCollectionActions {
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
|
||||
if len(item.AuthorizedCollectionActions) > 0 {
|
||||
keys := make([]string, 0, len(item.AuthorizedCollectionActions))
|
||||
for k := range item.AuthorizedCollectionActions {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
@@ -172,16 +200,16 @@ func printItemTable(in *authmethods.AuthMethod) string {
|
||||
for _, key := range keys {
|
||||
ret = append(ret,
|
||||
fmt.Sprintf(" %s:", key),
|
||||
base.WrapSlice(6, in.AuthorizedCollectionActions[key]),
|
||||
base.WrapSlice(6, item.AuthorizedCollectionActions[key]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if len(in.Attributes) > 0 {
|
||||
if len(item.Attributes) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Attributes:",
|
||||
base.WrapMap(4, maxLength, in.Attributes),
|
||||
base.WrapMap(4, maxLength, item.Attributes),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -231,8 +231,7 @@ func (c *OidcCommand) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*authmethods.AuthMethod)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -221,8 +221,7 @@ func (c *PasswordCommand) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*authmethods.AuthMethod)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -233,8 +233,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*authtokens.AuthToken)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/authtokens"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/hashicorp/boundary/sdk/strutil"
|
||||
@@ -69,23 +70,43 @@ func (c *Command) printListTable(items []*authtokens.AuthToken) string {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
if t.Id != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", t.Id),
|
||||
)
|
||||
}
|
||||
if c.FlagRecursive {
|
||||
if c.FlagRecursive && t.ScopeId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Scope ID: %s", t.Scope.Id),
|
||||
fmt.Sprintf(" Scope ID: %s", t.ScopeId),
|
||||
)
|
||||
}
|
||||
if true {
|
||||
if !t.ApproximateLastUsedTime.IsZero() {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Approximate Last Used Time: %s", t.ApproximateLastUsedTime.Local().Format(time.RFC1123)),
|
||||
)
|
||||
}
|
||||
if t.AuthMethodId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Auth Method ID: %s", t.AuthMethodId),
|
||||
)
|
||||
}
|
||||
if !t.CreatedTime.IsZero() {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Created Time: %s", t.CreatedTime.Local().Format(time.RFC1123)),
|
||||
)
|
||||
}
|
||||
if !t.ExpirationTime.IsZero() {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Expiration Time: %s", t.ExpirationTime.Local().Format(time.RFC1123)),
|
||||
)
|
||||
}
|
||||
if !t.UpdatedTime.IsZero() {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Updated Time: %s", t.UpdatedTime.Local().Format(time.RFC1123)),
|
||||
)
|
||||
}
|
||||
if t.UserId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" User ID: %s", t.UserId),
|
||||
)
|
||||
}
|
||||
@@ -100,15 +121,16 @@ func (c *Command) printListTable(items []*authtokens.AuthToken) string {
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func printItemTable(in *authtokens.AuthToken) string {
|
||||
func printItemTable(result api.GenericResult) string {
|
||||
item := result.GetItem().(*authtokens.AuthToken)
|
||||
nonAttributeMap := map[string]interface{}{
|
||||
"ID": in.Id,
|
||||
"Auth Method ID": in.AuthMethodId,
|
||||
"User ID": in.UserId,
|
||||
"Created Time": in.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": in.UpdatedTime.Local().Format(time.RFC1123),
|
||||
"Expiration Time": in.ExpirationTime.Local().Format(time.RFC1123),
|
||||
"Approximate Last Used Time": in.ApproximateLastUsedTime.Local().Format(time.RFC1123),
|
||||
"ID": item.Id,
|
||||
"Auth Method ID": item.AuthMethodId,
|
||||
"User ID": item.UserId,
|
||||
"Created Time": item.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": item.UpdatedTime.Local().Format(time.RFC1123),
|
||||
"Expiration Time": item.ExpirationTime.Local().Format(time.RFC1123),
|
||||
"Approximate Last Used Time": item.ApproximateLastUsedTime.Local().Format(time.RFC1123),
|
||||
}
|
||||
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil)
|
||||
@@ -119,14 +141,14 @@ func printItemTable(in *authtokens.AuthToken) string {
|
||||
base.WrapMap(2, maxLength+2, nonAttributeMap),
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(in.Scope, maxLength),
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
}
|
||||
|
||||
if len(in.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, in.AuthorizedActions),
|
||||
base.WrapSlice(4, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -149,39 +149,43 @@ func (c *Command) printListTable(items []*groups.Group) string {
|
||||
"",
|
||||
"Group information:",
|
||||
}
|
||||
for i, g := range items {
|
||||
for i, item := range items {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
if item.Id != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", g.Id),
|
||||
fmt.Sprintf(" ID: %s", item.Id),
|
||||
)
|
||||
} else {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", "(not available)"),
|
||||
)
|
||||
}
|
||||
if c.FlagRecursive {
|
||||
if c.FlagRecursive && item.ScopeId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Scope ID: %s", g.Scope.Id),
|
||||
fmt.Sprintf(" Scope ID: %s", item.ScopeId),
|
||||
)
|
||||
}
|
||||
if true {
|
||||
if item.Version > 0 {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Version: %d", g.Version),
|
||||
fmt.Sprintf(" Version: %d", item.Version),
|
||||
)
|
||||
}
|
||||
if g.Name != "" {
|
||||
if item.Name != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Name: %s", g.Name),
|
||||
fmt.Sprintf(" Name: %s", item.Name),
|
||||
)
|
||||
}
|
||||
if g.Description != "" {
|
||||
if item.Description != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Description: %s", g.Description),
|
||||
fmt.Sprintf(" Description: %s", item.Description),
|
||||
)
|
||||
}
|
||||
if len(g.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
output = append(output,
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(6, g.AuthorizedActions),
|
||||
base.WrapSlice(6, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -189,14 +193,21 @@ func (c *Command) printListTable(items []*groups.Group) string {
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func printItemTable(item *groups.Group) string {
|
||||
nonAttributeMap := map[string]interface{}{
|
||||
"ID": item.Id,
|
||||
"Version": item.Version,
|
||||
"Created Time": item.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": item.UpdatedTime.Local().Format(time.RFC1123),
|
||||
func printItemTable(result api.GenericResult) string {
|
||||
item := result.GetItem().(*groups.Group)
|
||||
nonAttributeMap := map[string]interface{}{}
|
||||
if item.Id != "" {
|
||||
nonAttributeMap["ID"] = item.Id
|
||||
}
|
||||
if item.Version != 0 {
|
||||
nonAttributeMap["Version"] = item.Version
|
||||
}
|
||||
if !item.CreatedTime.IsZero() {
|
||||
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if !item.UpdatedTime.IsZero() {
|
||||
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
|
||||
if item.Name != "" {
|
||||
nonAttributeMap["Name"] = item.Name
|
||||
}
|
||||
@@ -224,9 +235,14 @@ func printItemTable(item *groups.Group) string {
|
||||
"",
|
||||
"Group information:",
|
||||
base.WrapMap(2, maxLength+2, nonAttributeMap),
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
}
|
||||
|
||||
if item.Scope != nil {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
)
|
||||
}
|
||||
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
|
||||
@@ -316,8 +316,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*groups.Group)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/hostcatalogs"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
)
|
||||
@@ -68,19 +69,27 @@ func (c *Command) printListTable(items []*hostcatalogs.HostCatalog) string {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
if m.Id != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", m.Id),
|
||||
)
|
||||
}
|
||||
if c.FlagRecursive {
|
||||
} else {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Scope ID: %s", m.Scope.Id),
|
||||
fmt.Sprintf(" ID: %s", "(not available)"),
|
||||
)
|
||||
}
|
||||
if true {
|
||||
if c.FlagRecursive && m.ScopeId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Scope ID: %s", m.ScopeId),
|
||||
)
|
||||
}
|
||||
if m.Version > 0 {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Version: %d", m.Version),
|
||||
)
|
||||
}
|
||||
if m.Type != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Type: %s", m.Type),
|
||||
)
|
||||
}
|
||||
@@ -105,44 +114,58 @@ func (c *Command) printListTable(items []*hostcatalogs.HostCatalog) string {
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func printItemTable(in *hostcatalogs.HostCatalog) string {
|
||||
nonAttributeMap := map[string]interface{}{
|
||||
"ID": in.Id,
|
||||
"Version": in.Version,
|
||||
"Type": in.Type,
|
||||
"Created Time": in.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": in.UpdatedTime.Local().Format(time.RFC1123),
|
||||
func printItemTable(result api.GenericResult) string {
|
||||
item := result.GetItem().(*hostcatalogs.HostCatalog)
|
||||
nonAttributeMap := map[string]interface{}{}
|
||||
if item.Id != "" {
|
||||
nonAttributeMap["ID"] = item.Id
|
||||
}
|
||||
if item.Version != 0 {
|
||||
nonAttributeMap["Version"] = item.Version
|
||||
}
|
||||
if !item.CreatedTime.IsZero() {
|
||||
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if !item.UpdatedTime.IsZero() {
|
||||
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if item.Name != "" {
|
||||
nonAttributeMap["Name"] = item.Name
|
||||
}
|
||||
if item.Description != "" {
|
||||
nonAttributeMap["Description"] = item.Description
|
||||
}
|
||||
if item.Type != "" {
|
||||
nonAttributeMap["Type"] = item.Type
|
||||
}
|
||||
|
||||
if in.Name != "" {
|
||||
nonAttributeMap["Name"] = in.Name
|
||||
}
|
||||
if in.Description != "" {
|
||||
nonAttributeMap["Description"] = in.Description
|
||||
}
|
||||
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap)
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap)
|
||||
|
||||
ret := []string{
|
||||
"",
|
||||
"Host Catalog information:",
|
||||
base.WrapMap(2, maxLength+2, nonAttributeMap),
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(in.Scope, maxLength),
|
||||
}
|
||||
|
||||
if len(in.AuthorizedActions) > 0 {
|
||||
if item.Scope != nil {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, in.AuthorizedActions),
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
)
|
||||
}
|
||||
|
||||
if len(in.AuthorizedCollectionActions) > 0 {
|
||||
keys := make([]string, 0, len(in.AuthorizedCollectionActions))
|
||||
for k := range in.AuthorizedCollectionActions {
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
|
||||
if len(item.AuthorizedCollectionActions) > 0 {
|
||||
keys := make([]string, 0, len(item.AuthorizedCollectionActions))
|
||||
for k := range item.AuthorizedCollectionActions {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
@@ -152,16 +175,16 @@ func printItemTable(in *hostcatalogs.HostCatalog) string {
|
||||
for _, key := range keys {
|
||||
ret = append(ret,
|
||||
fmt.Sprintf(" %s:", key),
|
||||
base.WrapSlice(6, in.AuthorizedCollectionActions[key]),
|
||||
base.WrapSlice(6, item.AuthorizedCollectionActions[key]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if len(in.Attributes) > 0 {
|
||||
if len(item.Attributes) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Attributes:",
|
||||
base.WrapMap(4, maxLength, in.Attributes),
|
||||
base.WrapMap(4, maxLength, item.Attributes),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -242,8 +242,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*hostcatalogs.HostCatalog)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -219,8 +219,7 @@ func (c *StaticCommand) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*hostcatalogs.HostCatalog)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/hosts"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
)
|
||||
@@ -67,10 +68,22 @@ func (c *Command) printListTable(items []*hosts.Host) string {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
if item.Id != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", item.Id),
|
||||
)
|
||||
} else {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", "(not available)"),
|
||||
)
|
||||
}
|
||||
if item.Version > 0 {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Version: %d", item.Version),
|
||||
)
|
||||
}
|
||||
if item.Type != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Type: %s", item.Type),
|
||||
)
|
||||
}
|
||||
@@ -95,55 +108,71 @@ func (c *Command) printListTable(items []*hosts.Host) string {
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func printItemTable(in *hosts.Host) string {
|
||||
nonAttributeMap := map[string]interface{}{
|
||||
"ID": in.Id,
|
||||
"Version": in.Version,
|
||||
"Type": in.Type,
|
||||
"Created Time": in.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": in.UpdatedTime.Local().Format(time.RFC1123),
|
||||
"Host Catalog ID": in.HostCatalogId,
|
||||
func printItemTable(result api.GenericResult) string {
|
||||
item := result.GetItem().(*hosts.Host)
|
||||
nonAttributeMap := map[string]interface{}{}
|
||||
if item.Id != "" {
|
||||
nonAttributeMap["ID"] = item.Id
|
||||
}
|
||||
if item.Version != 0 {
|
||||
nonAttributeMap["Version"] = item.Version
|
||||
}
|
||||
if item.Type != "" {
|
||||
nonAttributeMap["Type"] = item.Type
|
||||
}
|
||||
if !item.CreatedTime.IsZero() {
|
||||
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if !item.UpdatedTime.IsZero() {
|
||||
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if item.Name != "" {
|
||||
nonAttributeMap["Name"] = item.Name
|
||||
}
|
||||
if item.Description != "" {
|
||||
nonAttributeMap["Description"] = item.Description
|
||||
}
|
||||
if item.HostCatalogId != "" {
|
||||
nonAttributeMap["Host Catalog ID"] = item.HostCatalogId
|
||||
}
|
||||
|
||||
if in.Name != "" {
|
||||
nonAttributeMap["Name"] = in.Name
|
||||
}
|
||||
if in.Description != "" {
|
||||
nonAttributeMap["Description"] = in.Description
|
||||
}
|
||||
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap)
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap)
|
||||
|
||||
ret := []string{
|
||||
"",
|
||||
"Host information:",
|
||||
base.WrapMap(2, maxLength+2, nonAttributeMap),
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(in.Scope, maxLength),
|
||||
}
|
||||
|
||||
if len(in.AuthorizedActions) > 0 {
|
||||
if item.Scope != nil {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
)
|
||||
}
|
||||
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, in.AuthorizedActions),
|
||||
base.WrapSlice(4, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
|
||||
if len(in.HostSetIds) > 0 {
|
||||
if len(item.HostSetIds) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Host Set IDs:",
|
||||
base.WrapSlice(4, in.HostSetIds),
|
||||
base.WrapSlice(4, item.HostSetIds),
|
||||
)
|
||||
}
|
||||
|
||||
if len(in.Attributes) > 0 {
|
||||
if len(item.Attributes) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Attributes:",
|
||||
base.WrapMap(4, maxLength, in.Attributes),
|
||||
base.WrapMap(4, maxLength, item.Attributes),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -253,8 +253,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*hosts.Host)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -216,8 +216,7 @@ func (c *StaticCommand) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*hosts.Host)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -182,31 +182,43 @@ func (c *Command) printListTable(items []*hostsets.HostSet) string {
|
||||
"",
|
||||
"Host Set information:",
|
||||
}
|
||||
for i, m := range items {
|
||||
for i, item := range items {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
if item.Id != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", m.Id),
|
||||
fmt.Sprintf(" Version: %d", m.Version),
|
||||
fmt.Sprintf(" Type: %s", m.Type),
|
||||
fmt.Sprintf(" ID: %s", item.Id),
|
||||
)
|
||||
} else {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", "(not available)"),
|
||||
)
|
||||
}
|
||||
if m.Name != "" {
|
||||
if item.Version > 0 {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Name: %s", m.Name),
|
||||
fmt.Sprintf(" Version: %d", item.Version),
|
||||
)
|
||||
}
|
||||
if m.Description != "" {
|
||||
if item.Type != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Description: %s", m.Description),
|
||||
fmt.Sprintf(" Type: %s", item.Type),
|
||||
)
|
||||
}
|
||||
if len(m.AuthorizedActions) > 0 {
|
||||
if item.Name != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Name: %s", item.Name),
|
||||
)
|
||||
}
|
||||
if item.Description != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Description: %s", item.Description),
|
||||
)
|
||||
}
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
output = append(output,
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(6, m.AuthorizedActions),
|
||||
base.WrapSlice(6, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -214,55 +226,71 @@ func (c *Command) printListTable(items []*hostsets.HostSet) string {
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func printItemTable(in *hostsets.HostSet) string {
|
||||
nonAttributeMap := map[string]interface{}{
|
||||
"ID": in.Id,
|
||||
"Version": in.Version,
|
||||
"Type": in.Type,
|
||||
"Created Time": in.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": in.UpdatedTime.Local().Format(time.RFC1123),
|
||||
"Host Catalog ID": in.HostCatalogId,
|
||||
func printItemTable(result api.GenericResult) string {
|
||||
item := result.GetItem().(*hostsets.HostSet)
|
||||
nonAttributeMap := map[string]interface{}{}
|
||||
if item.Id != "" {
|
||||
nonAttributeMap["ID"] = item.Id
|
||||
}
|
||||
if item.Version != 0 {
|
||||
nonAttributeMap["Version"] = item.Version
|
||||
}
|
||||
if item.Type != "" {
|
||||
nonAttributeMap["Type"] = item.Type
|
||||
}
|
||||
if !item.CreatedTime.IsZero() {
|
||||
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if !item.UpdatedTime.IsZero() {
|
||||
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if item.Name != "" {
|
||||
nonAttributeMap["Name"] = item.Name
|
||||
}
|
||||
if item.Description != "" {
|
||||
nonAttributeMap["Description"] = item.Description
|
||||
}
|
||||
if item.HostCatalogId != "" {
|
||||
nonAttributeMap["Host Catalog ID"] = item.HostCatalogId
|
||||
}
|
||||
|
||||
if in.Name != "" {
|
||||
nonAttributeMap["Name"] = in.Name
|
||||
}
|
||||
if in.Description != "" {
|
||||
nonAttributeMap["Description"] = in.Description
|
||||
}
|
||||
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap)
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap)
|
||||
|
||||
ret := []string{
|
||||
"",
|
||||
"Host Set information:",
|
||||
base.WrapMap(2, maxLength+2, nonAttributeMap),
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(in.Scope, maxLength),
|
||||
}
|
||||
|
||||
if len(in.AuthorizedActions) > 0 {
|
||||
if item.Scope != nil {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
)
|
||||
}
|
||||
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, in.AuthorizedActions),
|
||||
base.WrapSlice(4, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
|
||||
if len(in.HostIds) > 0 {
|
||||
if len(item.HostIds) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Host IDs:",
|
||||
base.WrapSlice(4, in.HostIds),
|
||||
base.WrapSlice(4, item.HostIds),
|
||||
)
|
||||
}
|
||||
|
||||
if len(in.Attributes) > 0 {
|
||||
if len(item.Attributes) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Attributes:",
|
||||
base.WrapMap(4, maxLength, in.Attributes),
|
||||
base.WrapMap(4, maxLength, item.Attributes),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -283,8 +283,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*hostsets.HostSet)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -214,8 +214,7 @@ func (c *StaticCommand) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*hostsets.HostSet)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -257,17 +257,21 @@ func (c *Command) printListTable(items []*roles.Role) string {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
if item.Id != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", item.Id),
|
||||
)
|
||||
}
|
||||
if c.FlagRecursive {
|
||||
} else {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Scope ID: %s", item.Scope.Id),
|
||||
fmt.Sprintf(" ID: %s", "(not available)"),
|
||||
)
|
||||
}
|
||||
if true {
|
||||
if c.FlagRecursive && item.ScopeId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Scope ID: %s", item.ScopeId),
|
||||
)
|
||||
}
|
||||
if item.Version > 0 {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Version: %d", item.Version),
|
||||
)
|
||||
@@ -293,22 +297,29 @@ func (c *Command) printListTable(items []*roles.Role) string {
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func printItemTable(in *roles.Role) string {
|
||||
nonAttributeMap := map[string]interface{}{
|
||||
"ID": in.Id,
|
||||
"Version": in.Version,
|
||||
"Created Time": in.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": in.UpdatedTime.Local().Format(time.RFC1123),
|
||||
func printItemTable(result api.GenericResult) string {
|
||||
item := result.GetItem().(*roles.Role)
|
||||
nonAttributeMap := map[string]interface{}{}
|
||||
if item.Id != "" {
|
||||
nonAttributeMap["ID"] = item.Id
|
||||
}
|
||||
|
||||
if in.Name != "" {
|
||||
nonAttributeMap["Name"] = in.Name
|
||||
if item.Version != 0 {
|
||||
nonAttributeMap["Version"] = item.Version
|
||||
}
|
||||
if in.Description != "" {
|
||||
nonAttributeMap["Description"] = in.Description
|
||||
if !item.CreatedTime.IsZero() {
|
||||
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if in.GrantScopeId != "" {
|
||||
nonAttributeMap["Grant Scope ID"] = in.GrantScopeId
|
||||
if !item.UpdatedTime.IsZero() {
|
||||
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if item.Name != "" {
|
||||
nonAttributeMap["Name"] = item.Name
|
||||
}
|
||||
if item.Description != "" {
|
||||
nonAttributeMap["Description"] = item.Description
|
||||
}
|
||||
if item.GrantScopeId != "" {
|
||||
nonAttributeMap["Grant Scope ID"] = item.GrantScopeId
|
||||
}
|
||||
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil)
|
||||
@@ -317,39 +328,44 @@ func printItemTable(in *roles.Role) string {
|
||||
"",
|
||||
"Role information:",
|
||||
base.WrapMap(2, maxLength+2, nonAttributeMap),
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(in.Scope, maxLength),
|
||||
}
|
||||
|
||||
if len(in.AuthorizedActions) > 0 {
|
||||
if item.Scope != nil {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, in.AuthorizedActions),
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
)
|
||||
}
|
||||
|
||||
if len(in.Principals) > 0 {
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
|
||||
if len(item.Principals) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
fmt.Sprintf(" Principals: %s", ""),
|
||||
)
|
||||
}
|
||||
for _, principal := range in.Principals {
|
||||
for _, principal := range item.Principals {
|
||||
ret = append(ret,
|
||||
fmt.Sprintf(" ID: %s", principal.Id),
|
||||
fmt.Sprintf(" Type: %s", principal.Type),
|
||||
fmt.Sprintf(" Scope ID: %s", principal.ScopeId),
|
||||
)
|
||||
}
|
||||
if len(in.Grants) > 0 {
|
||||
if len(item.Grants) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
fmt.Sprintf(" Canonical Grants: %s", ""),
|
||||
)
|
||||
}
|
||||
for _, grant := range in.Grants {
|
||||
for _, grant := range item.Grants {
|
||||
ret = append(ret,
|
||||
fmt.Sprintf(" %s", grant.Canonical),
|
||||
)
|
||||
|
||||
@@ -340,8 +340,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*roles.Role)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/scopes"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
)
|
||||
@@ -96,7 +97,7 @@ func (c *Command) printListTable(items []*scopes.Scope) string {
|
||||
fmt.Sprintf(" Scope ID: %s", item.Scope.Id),
|
||||
)
|
||||
}
|
||||
if true {
|
||||
if item.Version > 0 {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Version: %d", item.Version),
|
||||
)
|
||||
@@ -127,22 +128,29 @@ func (c *Command) printListTable(items []*scopes.Scope) string {
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func printItemTable(in *scopes.Scope) string {
|
||||
nonAttributeMap := map[string]interface{}{
|
||||
"ID": in.Id,
|
||||
"Version": in.Version,
|
||||
"Created Time": in.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": in.UpdatedTime.Local().Format(time.RFC1123),
|
||||
func printItemTable(result api.GenericResult) string {
|
||||
item := result.GetItem().(*scopes.Scope)
|
||||
nonAttributeMap := map[string]interface{}{}
|
||||
if item.Id != "" {
|
||||
nonAttributeMap["ID"] = item.Id
|
||||
}
|
||||
|
||||
if in.Name != "" {
|
||||
nonAttributeMap["Name"] = in.Name
|
||||
if item.Version != 0 {
|
||||
nonAttributeMap["Version"] = item.Version
|
||||
}
|
||||
if in.Description != "" {
|
||||
nonAttributeMap["Description"] = in.Description
|
||||
if !item.CreatedTime.IsZero() {
|
||||
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if in.PrimaryAuthMethodId != "" {
|
||||
nonAttributeMap[flagPrimaryAuthMethodIdName] = in.PrimaryAuthMethodId
|
||||
if !item.UpdatedTime.IsZero() {
|
||||
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if item.Name != "" {
|
||||
nonAttributeMap["Name"] = item.Name
|
||||
}
|
||||
if item.Description != "" {
|
||||
nonAttributeMap["Description"] = item.Description
|
||||
}
|
||||
if item.PrimaryAuthMethodId != "" {
|
||||
nonAttributeMap[flagPrimaryAuthMethodIdName] = item.PrimaryAuthMethodId
|
||||
}
|
||||
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil)
|
||||
@@ -151,23 +159,28 @@ func printItemTable(in *scopes.Scope) string {
|
||||
"",
|
||||
"Scope information:",
|
||||
base.WrapMap(2, maxLength+2, nonAttributeMap),
|
||||
"",
|
||||
" Scope (parent):",
|
||||
base.ScopeInfoForOutput(in.Scope, maxLength),
|
||||
}
|
||||
|
||||
if len(in.AuthorizedActions) > 0 {
|
||||
if item.Scope != nil {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Scope (parent):",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
)
|
||||
}
|
||||
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, in.AuthorizedActions),
|
||||
base.WrapSlice(4, item.AuthorizedActions),
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
if len(in.AuthorizedCollectionActions) > 0 {
|
||||
keys := make([]string, 0, len(in.AuthorizedCollectionActions))
|
||||
for k := range in.AuthorizedCollectionActions {
|
||||
if len(item.AuthorizedCollectionActions) > 0 {
|
||||
keys := make([]string, 0, len(item.AuthorizedCollectionActions))
|
||||
for k := range item.AuthorizedCollectionActions {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
@@ -175,7 +188,7 @@ func printItemTable(in *scopes.Scope) string {
|
||||
for _, key := range keys {
|
||||
ret = append(ret,
|
||||
fmt.Sprintf(" %s:", key),
|
||||
base.WrapSlice(6, in.AuthorizedCollectionActions[key]),
|
||||
base.WrapSlice(6, item.AuthorizedCollectionActions[key]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,8 +290,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*scopes.Scope)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -76,23 +76,47 @@ func (c *Command) printListTable(items []*sessions.Session) string {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
if item.Id != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", item.Id),
|
||||
)
|
||||
}
|
||||
if c.FlagRecursive {
|
||||
} else {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Scope ID: %s", item.Scope.Id),
|
||||
fmt.Sprintf(" ID: %s", "(not available)"),
|
||||
)
|
||||
}
|
||||
if true {
|
||||
if c.FlagRecursive && item.ScopeId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Scope ID: %s", item.ScopeId),
|
||||
)
|
||||
}
|
||||
if item.Status != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Status: %s", item.Status),
|
||||
)
|
||||
}
|
||||
if !item.CreatedTime.IsZero() {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Created Time: %s", item.CreatedTime.Local().Format(time.RFC1123)),
|
||||
)
|
||||
}
|
||||
if !item.ExpirationTime.IsZero() {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Expiration Time: %s", item.ExpirationTime.Local().Format(time.RFC1123)),
|
||||
)
|
||||
}
|
||||
if !item.UpdatedTime.IsZero() {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Updated Time: %s", item.UpdatedTime.Local().Format(time.RFC1123)),
|
||||
)
|
||||
}
|
||||
if item.UserId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" User ID: %s", item.UserId),
|
||||
)
|
||||
}
|
||||
if item.TargetId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Target ID: %s", item.TargetId),
|
||||
)
|
||||
}
|
||||
@@ -107,31 +131,57 @@ func (c *Command) printListTable(items []*sessions.Session) string {
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func printItemTable(in *sessions.Session) string {
|
||||
nonAttributeMap := map[string]interface{}{
|
||||
"ID": in.Id,
|
||||
"Target ID": in.TargetId,
|
||||
"Created Time": in.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": in.UpdatedTime.Local().Format(time.RFC1123),
|
||||
"Expiration Time": in.ExpirationTime.Local().Format(time.RFC1123),
|
||||
"Version": in.Version,
|
||||
"Type": in.Type,
|
||||
"Auth Token ID": in.AuthTokenId,
|
||||
"User ID": in.UserId,
|
||||
"Host Set ID": in.HostSetId,
|
||||
"Host ID": in.HostId,
|
||||
"Endpoint": in.Endpoint,
|
||||
"Status": in.Status,
|
||||
func printItemTable(result api.GenericResult) string {
|
||||
item := result.GetItem().(*sessions.Session)
|
||||
nonAttributeMap := map[string]interface{}{}
|
||||
if item.Id != "" {
|
||||
nonAttributeMap["ID"] = item.Id
|
||||
}
|
||||
if len(strings.TrimSpace(in.TerminationReason)) > 0 {
|
||||
nonAttributeMap["Termination Reason"] = in.TerminationReason
|
||||
if item.Version != 0 {
|
||||
nonAttributeMap["Version"] = item.Version
|
||||
}
|
||||
if item.Type != "" {
|
||||
nonAttributeMap["Type"] = item.Type
|
||||
}
|
||||
if !item.CreatedTime.IsZero() {
|
||||
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if !item.UpdatedTime.IsZero() {
|
||||
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if !item.ExpirationTime.IsZero() {
|
||||
nonAttributeMap["Expiration Time"] = item.ExpirationTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if item.TargetId != "" {
|
||||
nonAttributeMap["Target ID"] = item.TargetId
|
||||
}
|
||||
if item.AuthTokenId != "" {
|
||||
nonAttributeMap["Auth Token ID"] = item.AuthTokenId
|
||||
}
|
||||
if item.UserId != "" {
|
||||
nonAttributeMap["User ID"] = item.UserId
|
||||
}
|
||||
if item.HostSetId != "" {
|
||||
nonAttributeMap["Host Set ID"] = item.HostSetId
|
||||
}
|
||||
if item.HostId != "" {
|
||||
nonAttributeMap["Host ID"] = item.HostId
|
||||
}
|
||||
if item.Endpoint != "" {
|
||||
nonAttributeMap["Endpoint"] = item.Endpoint
|
||||
}
|
||||
if item.Status != "" {
|
||||
nonAttributeMap["Status"] = item.Status
|
||||
}
|
||||
if len(strings.TrimSpace(item.TerminationReason)) > 0 {
|
||||
nonAttributeMap["Termination Reason"] = item.TerminationReason
|
||||
}
|
||||
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil)
|
||||
|
||||
var statesMaps []map[string]interface{}
|
||||
if len(in.States) > 0 {
|
||||
for _, state := range in.States {
|
||||
if len(item.States) > 0 {
|
||||
for _, state := range item.States {
|
||||
m := map[string]interface{}{
|
||||
"Status": state.Status,
|
||||
"Start Time": state.StartTime.Local().Format(time.RFC1123),
|
||||
@@ -147,8 +197,8 @@ func printItemTable(in *sessions.Session) string {
|
||||
}
|
||||
|
||||
var workerInfoMaps []map[string]interface{}
|
||||
if len(in.WorkerInfo) > 0 {
|
||||
for _, wi := range in.WorkerInfo {
|
||||
if len(item.WorkerInfo) > 0 {
|
||||
for _, wi := range item.WorkerInfo {
|
||||
m := map[string]interface{}{
|
||||
"Address": wi.Address,
|
||||
}
|
||||
@@ -163,20 +213,25 @@ func printItemTable(in *sessions.Session) string {
|
||||
"",
|
||||
"Session information:",
|
||||
base.WrapMap(2, maxLength+2, nonAttributeMap),
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(in.Scope, maxLength),
|
||||
}
|
||||
|
||||
if len(in.AuthorizedActions) > 0 {
|
||||
if item.Scope != nil {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, in.AuthorizedActions),
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
)
|
||||
}
|
||||
|
||||
if len(in.States) > 0 {
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
|
||||
if len(item.States) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" States:",
|
||||
@@ -189,7 +244,7 @@ func printItemTable(in *sessions.Session) string {
|
||||
}
|
||||
}
|
||||
|
||||
if len(in.WorkerInfo) > 0 {
|
||||
if len(item.WorkerInfo) > 0 {
|
||||
ret = append(ret,
|
||||
fmt.Sprintf(" Worker Info: %s", ""),
|
||||
)
|
||||
|
||||
@@ -225,8 +225,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*sessions.Session)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/scopes"
|
||||
"github.com/hashicorp/boundary/api/targets"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/hashicorp/boundary/sdk/strutil"
|
||||
"github.com/mitchellh/go-wordwrap"
|
||||
@@ -286,40 +287,48 @@ func (c *Command) printListTable(items []*targets.Target) string {
|
||||
"",
|
||||
"Target information:",
|
||||
}
|
||||
for i, m := range items {
|
||||
for i, item := range items {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
if item.Id != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", m.Id),
|
||||
fmt.Sprintf(" ID: %s", item.Id),
|
||||
)
|
||||
} else {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", "(not available)"),
|
||||
)
|
||||
}
|
||||
if c.FlagRecursive {
|
||||
if c.FlagRecursive && item.ScopeId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Scope ID: %s", m.Scope.Id),
|
||||
fmt.Sprintf(" Scope ID: %s", item.ScopeId),
|
||||
)
|
||||
}
|
||||
if true {
|
||||
if item.Version > 0 {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Version: %d", m.Version),
|
||||
fmt.Sprintf(" Type: %s", m.Type),
|
||||
fmt.Sprintf(" Version: %d", item.Version),
|
||||
)
|
||||
}
|
||||
if m.Name != "" {
|
||||
if item.Type != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Name: %s", m.Name),
|
||||
fmt.Sprintf(" Type: %s", item.Type),
|
||||
)
|
||||
}
|
||||
if m.Description != "" {
|
||||
if item.Name != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Description: %s", m.Description),
|
||||
fmt.Sprintf(" Name: %s", item.Name),
|
||||
)
|
||||
}
|
||||
if len(m.AuthorizedActions) > 0 {
|
||||
if item.Description != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Description: %s", item.Description),
|
||||
)
|
||||
}
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
output = append(output,
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(6, m.AuthorizedActions),
|
||||
base.WrapSlice(6, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -327,17 +336,24 @@ func (c *Command) printListTable(items []*targets.Target) string {
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func printItemTable(item *targets.Target) string {
|
||||
nonAttributeMap := map[string]interface{}{
|
||||
"ID": item.Id,
|
||||
"Version": item.Version,
|
||||
"Type": item.Type,
|
||||
"Created Time": item.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": item.UpdatedTime.Local().Format(time.RFC1123),
|
||||
"Session Connection Limit": item.SessionConnectionLimit,
|
||||
"Session Max Seconds": item.SessionMaxSeconds,
|
||||
func printItemTable(result api.GenericResult) string {
|
||||
item := result.GetItem().(*targets.Target)
|
||||
nonAttributeMap := map[string]interface{}{}
|
||||
if item.Id != "" {
|
||||
nonAttributeMap["ID"] = item.Id
|
||||
}
|
||||
if item.Version != 0 {
|
||||
nonAttributeMap["Version"] = item.Version
|
||||
}
|
||||
if item.Type != "" {
|
||||
nonAttributeMap["Type"] = item.Type
|
||||
}
|
||||
if !item.CreatedTime.IsZero() {
|
||||
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if !item.UpdatedTime.IsZero() {
|
||||
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
|
||||
if item.Name != "" {
|
||||
nonAttributeMap["Name"] = item.Name
|
||||
}
|
||||
@@ -347,6 +363,14 @@ func printItemTable(item *targets.Target) string {
|
||||
if item.WorkerFilter != "" {
|
||||
nonAttributeMap["Worker Filter"] = item.WorkerFilter
|
||||
}
|
||||
if result.GetResponse() != nil && result.GetResponse().Map != nil {
|
||||
if result.GetResponse().Map[globals.SessionConnectionLimitField] != nil {
|
||||
nonAttributeMap["Session Connection Limit"] = item.SessionConnectionLimit
|
||||
}
|
||||
if result.GetResponse().Map[globals.SessionMaxSecondsField] != nil {
|
||||
nonAttributeMap["Session Max Seconds"] = item.SessionMaxSeconds
|
||||
}
|
||||
}
|
||||
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap)
|
||||
|
||||
@@ -368,9 +392,14 @@ func printItemTable(item *targets.Target) string {
|
||||
"",
|
||||
"Target information:",
|
||||
base.WrapMap(2, maxLength+2, nonAttributeMap),
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
}
|
||||
|
||||
if item.Scope != nil {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
)
|
||||
}
|
||||
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
@@ -459,7 +488,7 @@ var keySubstMap = map[string]string{
|
||||
}
|
||||
|
||||
func exampleOutput() string {
|
||||
in := &targets.Target{
|
||||
item := &targets.Target{
|
||||
Id: "ttcp_1234567890",
|
||||
ScopeId: "global",
|
||||
Scope: &scopes.ScopeInfo{
|
||||
@@ -486,5 +515,7 @@ func exampleOutput() string {
|
||||
"default_port": 22,
|
||||
},
|
||||
}
|
||||
return printItemTable(in)
|
||||
target := new(targets.TargetReadResult)
|
||||
target.Item = item
|
||||
return printItemTable(target)
|
||||
}
|
||||
@@ -289,8 +289,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*targets.Target)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -221,8 +221,7 @@ func (c *TcpCommand) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*targets.Target)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -150,60 +150,64 @@ func (c *Command) printListTable(items []*users.User) string {
|
||||
"User information:",
|
||||
}
|
||||
|
||||
for i, u := range items {
|
||||
for i, item := range items {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
if item.Id != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", u.Id),
|
||||
fmt.Sprintf(" ID: %s", item.Id),
|
||||
)
|
||||
} else {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", "(not available)"),
|
||||
)
|
||||
}
|
||||
if c.FlagRecursive {
|
||||
if c.FlagRecursive && item.ScopeId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Scope ID: %s", u.Scope.Id),
|
||||
fmt.Sprintf(" Scope ID: %s", item.ScopeId),
|
||||
)
|
||||
}
|
||||
if true {
|
||||
if item.Version > 0 {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Version: %d", u.Version),
|
||||
fmt.Sprintf(" Version: %d", item.Version),
|
||||
)
|
||||
}
|
||||
if u.Name != "" {
|
||||
if item.Name != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Name: %s", u.Name),
|
||||
fmt.Sprintf(" Name: %s", item.Name),
|
||||
)
|
||||
}
|
||||
if u.Description != "" {
|
||||
if item.Description != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Description: %s", u.Description),
|
||||
fmt.Sprintf(" Description: %s", item.Description),
|
||||
)
|
||||
}
|
||||
if u.PrimaryAccountId != "" {
|
||||
if item.PrimaryAccountId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Primary Account ID: %s", u.PrimaryAccountId),
|
||||
fmt.Sprintf(" Primary Account ID: %s", item.PrimaryAccountId),
|
||||
)
|
||||
}
|
||||
if u.LoginName != "" {
|
||||
if item.LoginName != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Login Name: %s", u.LoginName),
|
||||
fmt.Sprintf(" Login Name: %s", item.LoginName),
|
||||
)
|
||||
}
|
||||
if u.FullName != "" {
|
||||
if item.FullName != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Full Name: %s", u.FullName),
|
||||
fmt.Sprintf(" Full Name: %s", item.FullName),
|
||||
)
|
||||
}
|
||||
if u.Email != "" {
|
||||
if item.Email != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Email: %s", u.Email),
|
||||
fmt.Sprintf(" Email: %s", item.Email),
|
||||
)
|
||||
}
|
||||
|
||||
if len(u.AuthorizedActions) > 0 {
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
output = append(output,
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(6, u.AuthorizedActions),
|
||||
base.WrapSlice(6, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -211,38 +215,45 @@ func (c *Command) printListTable(items []*users.User) string {
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func printItemTable(in *users.User) string {
|
||||
nonAttributeMap := map[string]interface{}{
|
||||
"ID": in.Id,
|
||||
"Version": in.Version,
|
||||
"Created Time": in.CreatedTime.Local().Format(time.RFC1123),
|
||||
"Updated Time": in.UpdatedTime.Local().Format(time.RFC1123),
|
||||
func printItemTable(result api.GenericResult) string {
|
||||
item := result.GetItem().(*users.User)
|
||||
nonAttributeMap := map[string]interface{}{}
|
||||
if item.Id != "" {
|
||||
nonAttributeMap["ID"] = item.Id
|
||||
}
|
||||
|
||||
if in.Name != "" {
|
||||
nonAttributeMap["Name"] = in.Name
|
||||
if item.Version != 0 {
|
||||
nonAttributeMap["Version"] = item.Version
|
||||
}
|
||||
if in.Description != "" {
|
||||
nonAttributeMap["Description"] = in.Description
|
||||
if !item.CreatedTime.IsZero() {
|
||||
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if in.PrimaryAccountId != "" {
|
||||
nonAttributeMap["PrimaryAccountId"] = in.PrimaryAccountId
|
||||
if !item.UpdatedTime.IsZero() {
|
||||
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
|
||||
}
|
||||
if in.LoginName != "" {
|
||||
nonAttributeMap["LoginName"] = in.LoginName
|
||||
if item.Name != "" {
|
||||
nonAttributeMap["Name"] = item.Name
|
||||
}
|
||||
if in.FullName != "" {
|
||||
nonAttributeMap["FullName"] = in.FullName
|
||||
if item.Description != "" {
|
||||
nonAttributeMap["Description"] = item.Description
|
||||
}
|
||||
if in.Email != "" {
|
||||
nonAttributeMap["Email"] = in.Email
|
||||
if item.PrimaryAccountId != "" {
|
||||
nonAttributeMap["Primary Account Id"] = item.PrimaryAccountId
|
||||
}
|
||||
if item.LoginName != "" {
|
||||
nonAttributeMap["Login Name"] = item.LoginName
|
||||
}
|
||||
if item.FullName != "" {
|
||||
nonAttributeMap["Full Name"] = item.FullName
|
||||
}
|
||||
if item.Email != "" {
|
||||
nonAttributeMap["Email"] = item.Email
|
||||
}
|
||||
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil)
|
||||
|
||||
var userMaps []map[string]interface{}
|
||||
if len(in.Accounts) > 0 {
|
||||
for _, account := range in.Accounts {
|
||||
if len(item.Accounts) > 0 {
|
||||
for _, account := range item.Accounts {
|
||||
a := map[string]interface{}{
|
||||
"ID": account.Id,
|
||||
"Scope ID": account.ScopeId,
|
||||
@@ -258,20 +269,25 @@ func printItemTable(in *users.User) string {
|
||||
"",
|
||||
"User information:",
|
||||
base.WrapMap(2, maxLength+2, nonAttributeMap),
|
||||
"",
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(in.Scope, maxLength),
|
||||
}
|
||||
|
||||
if len(in.AuthorizedActions) > 0 {
|
||||
if item.Scope != nil {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, in.AuthorizedActions),
|
||||
" Scope:",
|
||||
base.ScopeInfoForOutput(item.Scope, maxLength),
|
||||
)
|
||||
}
|
||||
|
||||
if len(in.Accounts) > 0 {
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Authorized Actions:",
|
||||
base.WrapSlice(4, item.AuthorizedActions),
|
||||
)
|
||||
}
|
||||
|
||||
if len(item.Accounts) > 0 {
|
||||
ret = append(ret,
|
||||
"",
|
||||
" Accounts:",
|
||||
|
||||
@@ -316,8 +316,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*users.User)
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
@@ -413,8 +413,7 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "table":
|
||||
item := result.GetItem().(*{{ .Pkg }}.{{ camelCase .ResourceType }})
|
||||
c.UI.Output(printItemTable(item))
|
||||
c.UI.Output(printItemTable(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
|
||||
+45
-14
@@ -20,6 +20,8 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
)
|
||||
|
||||
const AnonymousUserId = "u_anon"
|
||||
|
||||
// ACL provides an entry point into the permissions engine for determining if an
|
||||
// action is allowed on a resource based on a principal's (user or group) grants.
|
||||
type ACL struct {
|
||||
@@ -32,6 +34,7 @@ type ACL struct {
|
||||
type ACLResults struct {
|
||||
AuthenticationFinished bool
|
||||
Authorized bool
|
||||
OutputFields OutputFieldsMap
|
||||
|
||||
// This is included but unexported for testing/debugging
|
||||
scopeMap map[string][]Grant
|
||||
@@ -80,7 +83,17 @@ func (a ACL) Allowed(r Resource, aType action.Type) (results ACLResults) {
|
||||
}
|
||||
// Now, go through and check the cases indicated above
|
||||
for _, grant := range grants {
|
||||
var outputFieldsOnly bool
|
||||
switch {
|
||||
case len(grant.actions) == 0:
|
||||
// Continue with the next grant, unless we have output fields
|
||||
// specified in which case we continue to be able to apply the
|
||||
// output fields depending on ID and type.
|
||||
if len(grant.OutputFields) > 0 {
|
||||
outputFieldsOnly = true
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
case grant.actions[aType]:
|
||||
// We have this action
|
||||
case grant.actions[parentAction]:
|
||||
@@ -94,8 +107,18 @@ func (a ACL) Allowed(r Resource, aType action.Type) (results ACLResults) {
|
||||
// with the next grant
|
||||
continue
|
||||
}
|
||||
|
||||
// We step through all grants, to fetch the full list of output fields.
|
||||
// However, we shortcut if we find *.
|
||||
//
|
||||
// If the action was not found above but we did find output fields in
|
||||
// patterns that match, we do not authorize the request, but we do build
|
||||
// up the output fields patterns.
|
||||
var found bool
|
||||
switch {
|
||||
// id=<resource.id>;actions=<action> where ID cannot be a wildcard
|
||||
// id=<resource.id>;actions=<action> where ID cannot be a wildcard; or
|
||||
// id=<resource.id>;output_fields=<fields> where fields cannot be a
|
||||
// wildcard.
|
||||
case grant.id == r.Id &&
|
||||
grant.id != "" &&
|
||||
grant.id != "*" &&
|
||||
@@ -103,12 +126,12 @@ func (a ACL) Allowed(r Resource, aType action.Type) (results ACLResults) {
|
||||
aType != action.List &&
|
||||
aType != action.Create:
|
||||
|
||||
results.Authorized = true
|
||||
return
|
||||
found = true
|
||||
|
||||
// type=<resource.type>;actions=<action> when action is list(:self) or
|
||||
// create. Must be a top level collection, otherwise must be one of the
|
||||
// two formats specified below.
|
||||
// type=<resource.type>;actions=<action> when action is list or create.
|
||||
// Must be a top level collection, otherwise must be one of the two
|
||||
// formats specified below. Or,
|
||||
// type=resource.type;output_fields=<fields> and no action.
|
||||
case grant.id == "" &&
|
||||
r.Id == "" &&
|
||||
grant.typ == r.Type &&
|
||||
@@ -117,29 +140,37 @@ func (a ACL) Allowed(r Resource, aType action.Type) (results ACLResults) {
|
||||
(aType == action.List ||
|
||||
aType == action.Create):
|
||||
|
||||
results.Authorized = true
|
||||
return
|
||||
found = true
|
||||
|
||||
// id=*;type=<resource.type>;actions=<action> where type cannot be
|
||||
// unknown but can be a wildcard to allow any resource at all
|
||||
// unknown but can be a wildcard to allow any resource at all; or
|
||||
// id=*;type=<resource.type>;output_fields=<fields> with no action.
|
||||
case grant.id == "*" &&
|
||||
grant.typ != resource.Unknown &&
|
||||
(grant.typ == r.Type ||
|
||||
grant.typ == resource.All):
|
||||
|
||||
results.Authorized = true
|
||||
return
|
||||
found = true
|
||||
|
||||
// id=<pin>;type=<resource.type>;actions=<action> where type can be a
|
||||
// wildcard and this this is operating on a non-top-level type
|
||||
// wildcard and this this is operating on a non-top-level type. Same for
|
||||
// output fields only.
|
||||
case grant.id != "" &&
|
||||
grant.id == r.Pin &&
|
||||
grant.typ != resource.Unknown &&
|
||||
(grant.typ == r.Type || grant.typ == resource.All) &&
|
||||
!topLevelType(r.Type):
|
||||
|
||||
results.Authorized = true
|
||||
return
|
||||
found = true
|
||||
}
|
||||
|
||||
if found {
|
||||
if !outputFieldsOnly {
|
||||
results.Authorized = true
|
||||
}
|
||||
if results.OutputFields = results.OutputFields.AddFields(grant.OutputFields.Fields()); results.OutputFields.HasAll() && results.Authorized {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
+26
-21
@@ -17,8 +17,9 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
grants []string
|
||||
}
|
||||
type actionAuthorized struct {
|
||||
action action.Type
|
||||
authorized bool
|
||||
action action.Type
|
||||
authorized bool
|
||||
outputFields []string
|
||||
}
|
||||
type input struct {
|
||||
name string
|
||||
@@ -45,8 +46,9 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
scope: "o_b",
|
||||
grants: []string{
|
||||
"id=*;type=host-set;actions=list,create",
|
||||
"id=mypin;type=host;actions=*",
|
||||
"id=mypin;type=host;actions=*;output_fields=name,description",
|
||||
"id=*;type=*;actions=authenticate",
|
||||
"id=*;type=*;output_fields=id",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -61,6 +63,7 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
grants: []string{
|
||||
"id=*;type=*;actions=create,update",
|
||||
"id=*;type=session;actions=*",
|
||||
"id=*;type=account;actions=update;output_fields=id,version",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -118,9 +121,9 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
resource: Resource{ScopeId: "o_b", Pin: "mypin", Type: resource.Host},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAuthorized: []actionAuthorized{
|
||||
{action: action.Read, authorized: true},
|
||||
{action: action.Update, authorized: true},
|
||||
{action: action.Delete, authorized: true},
|
||||
{action: action.Read, authorized: true, outputFields: []string{"description", "id", "name"}},
|
||||
{action: action.Update, authorized: true, outputFields: []string{"description", "id", "name"}},
|
||||
{action: action.Delete, authorized: true, outputFields: []string{"description", "id", "name"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -128,9 +131,9 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
resource: Resource{ScopeId: "o_b", Pin: "notmypin", Type: resource.Host},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAuthorized: []actionAuthorized{
|
||||
{action: action.Read},
|
||||
{action: action.Update},
|
||||
{action: action.Delete},
|
||||
{action: action.Read, outputFields: []string{"id"}},
|
||||
{action: action.Update, outputFields: []string{"id"}},
|
||||
{action: action.Delete, outputFields: []string{"id"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -138,9 +141,9 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
resource: Resource{ScopeId: "o_b", Id: "myhost", Type: resource.HostSet},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAuthorized: []actionAuthorized{
|
||||
{action: action.List, authorized: true},
|
||||
{action: action.Create, authorized: true},
|
||||
{action: action.AddHosts},
|
||||
{action: action.List, authorized: true, outputFields: []string{"id"}},
|
||||
{action: action.Create, authorized: true, outputFields: []string{"id"}},
|
||||
{action: action.AddHosts, outputFields: []string{"id"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -148,9 +151,9 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
resource: Resource{ScopeId: "o_b", Id: "id_g"},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAuthorized: []actionAuthorized{
|
||||
{action: action.Read},
|
||||
{action: action.Update},
|
||||
{action: action.Delete},
|
||||
{action: action.Read, outputFields: []string{"id"}},
|
||||
{action: action.Update, outputFields: []string{"id"}},
|
||||
{action: action.Delete, outputFields: []string{"id"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -197,9 +200,9 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
resource: Resource{ScopeId: "o_b", Type: resource.AuthMethod},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAuthorized: []actionAuthorized{
|
||||
{action: action.List},
|
||||
{action: action.Authenticate, authorized: true},
|
||||
{action: action.Delete},
|
||||
{action: action.List, outputFields: []string{"id"}},
|
||||
{action: action.Authenticate, authorized: true, outputFields: []string{"id"}},
|
||||
{action: action.Delete, outputFields: []string{"id"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -235,7 +238,7 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
accountId: "apw_1234567890",
|
||||
},
|
||||
{
|
||||
name: "good templated user id",
|
||||
name: "good templated account id",
|
||||
resource: Resource{ScopeId: "o_c", Id: "apw_1234567890"},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAuthorized: []actionAuthorized{
|
||||
@@ -250,7 +253,7 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
scopeGrants: commonGrants,
|
||||
actionsAuthorized: []actionAuthorized{
|
||||
{action: action.Create, authorized: true},
|
||||
{action: action.Update, authorized: true},
|
||||
{action: action.Update, authorized: true, outputFields: []string{"id", "version"}},
|
||||
},
|
||||
userId: "u_abcd1234",
|
||||
},
|
||||
@@ -302,7 +305,9 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
}
|
||||
acl := NewACL(grants...)
|
||||
for _, aa := range test.actionsAuthorized {
|
||||
assert.True(t, acl.Allowed(test.resource, aa.action).Authorized == aa.authorized, "action: %s, acl authorized: %t, test action authorized: %t", aa.action, acl.Allowed(test.resource, aa.action).Authorized, aa.authorized)
|
||||
result := acl.Allowed(test.resource, aa.action)
|
||||
assert.True(t, result.Authorized == aa.authorized, "action: %s, acl authorized: %t, test action authorized: %t", aa.action, acl.Allowed(test.resource, aa.action).Authorized, aa.authorized)
|
||||
assert.ElementsMatch(t, result.OutputFields.Fields(), aa.outputFields)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+70
-18
@@ -43,6 +43,9 @@ type Grant struct {
|
||||
// The set of actions being granted
|
||||
actions map[action.Type]bool
|
||||
|
||||
// The set of output fields granted
|
||||
OutputFields OutputFieldsMap
|
||||
|
||||
// This is used as a temporary staging area before validating permissions to
|
||||
// allow the same validation code across grant string formats
|
||||
actionsBeingParsed []string
|
||||
@@ -85,6 +88,12 @@ func (g Grant) clone() *Grant {
|
||||
ret.actions[action] = true
|
||||
}
|
||||
}
|
||||
if g.OutputFields != nil {
|
||||
ret.OutputFields = make(OutputFieldsMap, len(g.OutputFields))
|
||||
for k, v := range g.OutputFields {
|
||||
ret.OutputFields[k] = v
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -109,6 +118,10 @@ func (g Grant) CanonicalString() string {
|
||||
builder = append(builder, fmt.Sprintf("actions=%s", strings.Join(actions, ",")))
|
||||
}
|
||||
|
||||
if len(g.OutputFields) > 0 {
|
||||
builder = append(builder, fmt.Sprintf("output_fields=%s", strings.Join(g.OutputFields.Fields(), ",")))
|
||||
}
|
||||
|
||||
return strings.Join(builder, ";")
|
||||
}
|
||||
|
||||
@@ -130,6 +143,9 @@ func (g Grant) MarshalJSON() ([]byte, error) {
|
||||
sort.Strings(actions)
|
||||
res["actions"] = actions
|
||||
}
|
||||
if len(g.OutputFields) > 0 {
|
||||
res["output_fields"] = g.OutputFields.Fields()
|
||||
}
|
||||
b, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithCode(errors.Encode))
|
||||
@@ -183,6 +199,26 @@ func (g *Grant) unmarshalJSON(data []byte) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if rawOutputFields, ok := raw["output_fields"]; ok {
|
||||
interfaceOutputFields, ok := rawOutputFields.([]interface{})
|
||||
if !ok {
|
||||
return errors.New(errors.InvalidParameter, op, fmt.Sprintf("unable to interpret %q as array", "output_fields"))
|
||||
}
|
||||
// We do the make here because we detect later if the field was set but
|
||||
// no values given
|
||||
g.OutputFields = make(OutputFieldsMap, len(interfaceOutputFields))
|
||||
if len(interfaceOutputFields) > 0 {
|
||||
for _, v := range interfaceOutputFields {
|
||||
field, ok := v.(string)
|
||||
switch {
|
||||
case !ok:
|
||||
return errors.New(errors.InvalidParameter, op, fmt.Sprintf("unable to interpret %v in output_fields array as string", v))
|
||||
default:
|
||||
g.OutputFields[field] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -224,6 +260,9 @@ func (g *Grant) unmarshalText(grantString string) error {
|
||||
g.actionsBeingParsed = append(g.actionsBeingParsed, strings.ToLower(action))
|
||||
}
|
||||
}
|
||||
|
||||
case "output_fields":
|
||||
g.OutputFields = g.OutputFields.AddFields(strings.Split(kv[1], ","))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,26 +387,33 @@ func Parse(scopeId, grantString string, opt ...Option) (Grant, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create a dummy resource and pass it through Allowed and ensure that
|
||||
// we get allowed.
|
||||
acl := NewACL(grant)
|
||||
r := Resource{
|
||||
ScopeId: scopeId,
|
||||
Id: grant.id,
|
||||
Type: grant.typ,
|
||||
// Set but empty output fields...
|
||||
if grant.OutputFields != nil && len(grant.OutputFields) == 0 {
|
||||
return Grant{}, errors.New(errors.InvalidParameter, op, "parsed grant string has output_fields set but empty")
|
||||
}
|
||||
if !topLevelType(grant.typ) {
|
||||
r.Pin = grant.id
|
||||
}
|
||||
var allowed bool
|
||||
for k := range grant.actions {
|
||||
results := acl.Allowed(r, k)
|
||||
if results.Authorized {
|
||||
allowed = true
|
||||
// This might be zero if output fields is populated
|
||||
if len(grant.actions) > 0 {
|
||||
// Create a dummy resource and pass it through Allowed and ensure that
|
||||
// we get allowed.
|
||||
acl := NewACL(grant)
|
||||
r := Resource{
|
||||
ScopeId: scopeId,
|
||||
Id: grant.id,
|
||||
Type: grant.typ,
|
||||
}
|
||||
if !topLevelType(grant.typ) {
|
||||
r.Pin = grant.id
|
||||
}
|
||||
var allowed bool
|
||||
for k := range grant.actions {
|
||||
results := acl.Allowed(r, k)
|
||||
if results.Authorized {
|
||||
allowed = true
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
return Grant{}, errors.New(errors.InvalidParameter, op, "parsed grant string would not result in any action being authorized")
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
return Grant{}, errors.New(errors.InvalidParameter, op, "parsed grant string would not result in any action being authorized")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,6 +445,12 @@ func (g Grant) validateType() error {
|
||||
func (g *Grant) parseAndValidateActions() error {
|
||||
const op = "perms.(Grant).parseAndValidateActions"
|
||||
if len(g.actionsBeingParsed) == 0 {
|
||||
g.actionsBeingParsed = nil
|
||||
// If there are no actions it's fine if the grant is just used to
|
||||
// specify output fields
|
||||
if len(g.OutputFields) > 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(errors.InvalidParameter, op, "missing actions")
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,19 @@ func Test_ActionParsingValidation(t *testing.T) {
|
||||
},
|
||||
errResult: "perms.(Grant).parseAndValidateActions: empty action found: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "empty action with output fields",
|
||||
input: Grant{
|
||||
OutputFields: OutputFieldsMap{
|
||||
"id": true,
|
||||
},
|
||||
},
|
||||
result: Grant{
|
||||
OutputFields: OutputFieldsMap{
|
||||
"id": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown action",
|
||||
input: Grant{
|
||||
@@ -157,6 +170,23 @@ func Test_MarshalingAndCloning(t *testing.T) {
|
||||
jsonOutput: `{"id":"baz","type":"group"}`,
|
||||
canonicalString: `id=baz;type=group`,
|
||||
},
|
||||
{
|
||||
name: "output fields",
|
||||
input: Grant{
|
||||
id: "baz",
|
||||
scope: Scope{
|
||||
Type: scope.Project,
|
||||
},
|
||||
typ: resource.Group,
|
||||
OutputFields: OutputFieldsMap{
|
||||
"name": true,
|
||||
"version": true,
|
||||
"id": true,
|
||||
},
|
||||
},
|
||||
jsonOutput: `{"id":"baz","output_fields":["id","name","version"],"type":"group"}`,
|
||||
canonicalString: `id=baz;type=group;output_fields=id,name,version`,
|
||||
},
|
||||
{
|
||||
name: "everything",
|
||||
input: Grant{
|
||||
@@ -170,9 +200,14 @@ func Test_MarshalingAndCloning(t *testing.T) {
|
||||
action.Read: true,
|
||||
},
|
||||
actionsBeingParsed: []string{"create", "read"},
|
||||
OutputFields: OutputFieldsMap{
|
||||
"name": true,
|
||||
"version": true,
|
||||
"id": true,
|
||||
},
|
||||
},
|
||||
jsonOutput: `{"actions":["create","read"],"id":"baz","type":"group"}`,
|
||||
canonicalString: `id=baz;type=group;actions=create,read`,
|
||||
jsonOutput: `{"actions":["create","read"],"id":"baz","output_fields":["id","name","version"],"type":"group"}`,
|
||||
canonicalString: `id=baz;type=group;actions=create,read;output_fields=id,name,version`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -249,6 +284,25 @@ func Test_Unmarshaling(t *testing.T) {
|
||||
textInput: `type=host-catalog=id`,
|
||||
textErr: `perms.(Grant).unmarshalText: segment "type=host-catalog=id" not formatted correctly, wrong number of equal signs: parameter violation: error #100`,
|
||||
},
|
||||
{
|
||||
name: "good output fields",
|
||||
expected: Grant{
|
||||
OutputFields: OutputFieldsMap{
|
||||
"name": true,
|
||||
"version": true,
|
||||
"id": true,
|
||||
},
|
||||
},
|
||||
jsonInput: `{"output_fields":["id","name","version"]}`,
|
||||
textInput: `output_fields=id,version,name`,
|
||||
},
|
||||
{
|
||||
name: "bad output fields",
|
||||
jsonInput: `{"output_fields":true}`,
|
||||
jsonErr: `perms.(Grant).unmarshalJSON: unable to interpret "output_fields" as array: parameter violation: error #100`,
|
||||
textInput: `output_fields=id=version,name`,
|
||||
textErr: `perms.(Grant).unmarshalText: segment "output_fields=id=version,name" not formatted correctly, wrong number of equal signs: parameter violation: error #100`,
|
||||
},
|
||||
{
|
||||
name: "good actions",
|
||||
expected: Grant{
|
||||
@@ -386,6 +440,16 @@ func Test_Parse(t *testing.T) {
|
||||
input: "actions=create",
|
||||
err: `perms.Parse: parsed grant string contains no id or type: parameter violation: error #100`,
|
||||
},
|
||||
{
|
||||
name: "empty output fields",
|
||||
input: "id=*;type=*;actions=read,list;output_fields=",
|
||||
err: `perms.Parse: unable to parse grant string: perms.(Grant).unmarshalText: segment "output_fields=" not formatted correctly, missing value: parameter violation: error #100`,
|
||||
},
|
||||
{
|
||||
name: "empty output fields json",
|
||||
input: `{"id": "*", "type": "*", "actions": ["read", "list"], "output_fields": []}`,
|
||||
err: "perms.Parse: parsed grant string has output_fields set but empty: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "wildcard id and type and actions with list",
|
||||
input: "id=*;type=*;actions=read,list",
|
||||
@@ -431,6 +495,43 @@ func Test_Parse(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "good json output fields",
|
||||
input: `{"id":"foobar","actions":["read"],"output_fields":["version","id","name"]}`,
|
||||
expected: Grant{
|
||||
scope: Scope{
|
||||
Id: "o_scope",
|
||||
Type: scope.Org,
|
||||
},
|
||||
id: "foobar",
|
||||
typ: resource.Unknown,
|
||||
actions: map[action.Type]bool{
|
||||
action.Read: true,
|
||||
},
|
||||
OutputFields: OutputFieldsMap{
|
||||
"version": true,
|
||||
"id": true,
|
||||
"name": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "good json output fields no action",
|
||||
input: `{"id":"foobar","output_fields":["version","id","name"]}`,
|
||||
expected: Grant{
|
||||
scope: Scope{
|
||||
Id: "o_scope",
|
||||
Type: scope.Org,
|
||||
},
|
||||
id: "foobar",
|
||||
typ: resource.Unknown,
|
||||
OutputFields: OutputFieldsMap{
|
||||
"version": true,
|
||||
"id": true,
|
||||
"name": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "good text type",
|
||||
input: `type=host-catalog;actions=create`,
|
||||
@@ -460,6 +561,26 @@ func Test_Parse(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "good output fields",
|
||||
input: `id=foobar;actions=read;output_fields=version,id,name`,
|
||||
expected: Grant{
|
||||
scope: Scope{
|
||||
Id: "o_scope",
|
||||
Type: scope.Org,
|
||||
},
|
||||
id: "foobar",
|
||||
typ: resource.Unknown,
|
||||
actions: map[action.Type]bool{
|
||||
action.Read: true,
|
||||
},
|
||||
OutputFields: OutputFieldsMap{
|
||||
"version": true,
|
||||
"id": true,
|
||||
"name": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default project scope",
|
||||
input: `id=foobar;actions=read`,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package perms
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
)
|
||||
|
||||
// OutputFieldsMap is used to store information about allowed output fields in
|
||||
// grants
|
||||
type OutputFieldsMap map[string]bool
|
||||
|
||||
// AddFields adds the given fields and returns the map.
|
||||
func (o OutputFieldsMap) AddFields(input []string) (ret OutputFieldsMap) {
|
||||
switch {
|
||||
case len(input) == 0:
|
||||
if o == nil {
|
||||
return o
|
||||
}
|
||||
return o
|
||||
case o == nil:
|
||||
ret = make(OutputFieldsMap, len(input))
|
||||
case len(o) == 1 && o["*"]:
|
||||
return o
|
||||
default:
|
||||
ret = o
|
||||
}
|
||||
for _, k := range input {
|
||||
if k == "*" {
|
||||
ret = OutputFieldsMap{k: true}
|
||||
return
|
||||
}
|
||||
ret[k] = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o OutputFieldsMap) HasAll() bool {
|
||||
return o["*"]
|
||||
}
|
||||
|
||||
// Fields returns an alphabetical string slice of the fields in the map
|
||||
func (o OutputFieldsMap) Fields() (ret []string) {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
if len(o) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
ret = make([]string, 0, len(o))
|
||||
for f := range o {
|
||||
ret = append(ret, f)
|
||||
}
|
||||
sort.Strings(ret)
|
||||
return
|
||||
}
|
||||
|
||||
// SelfOrDefaults returns either the fields map itself or the defaults for the
|
||||
// given user
|
||||
func (o OutputFieldsMap) SelfOrDefaults(userId string) OutputFieldsMap {
|
||||
switch {
|
||||
case o != nil:
|
||||
// We have values set (which may be empty) so use those
|
||||
return o
|
||||
case userId == "":
|
||||
// This shouldn't happen, and if it does, don't allow anything to be
|
||||
// output
|
||||
return OutputFieldsMap{}
|
||||
case userId == AnonymousUserId:
|
||||
return OutputFieldsMap{
|
||||
globals.IdField: true,
|
||||
globals.ScopeField: true,
|
||||
globals.ScopeIdField: true,
|
||||
globals.NameField: true,
|
||||
globals.DescriptionField: true,
|
||||
globals.TypeField: true,
|
||||
globals.IsPrimaryField: true,
|
||||
globals.AuthorizedActionsField: true,
|
||||
globals.AuthorizedCollectionActionsField: true,
|
||||
}
|
||||
default:
|
||||
return OutputFieldsMap{
|
||||
"*": true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Has returns true if the value exists; that is, it is directly in the map, or
|
||||
// the map contains *
|
||||
func (o OutputFieldsMap) Has(in string) bool {
|
||||
// Handle nil or empty case
|
||||
if len(o) == 0 {
|
||||
return false
|
||||
}
|
||||
return o.HasAll() || o[in]
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package perms
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_OutputFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type input struct {
|
||||
name string
|
||||
fields []string
|
||||
startMap OutputFieldsMap
|
||||
resMap OutputFieldsMap
|
||||
resStar bool
|
||||
}
|
||||
tests := []input{
|
||||
{
|
||||
name: "nil map, add nil",
|
||||
},
|
||||
{
|
||||
name: "nil map, add fields",
|
||||
fields: []string{"id", "version"},
|
||||
resMap: OutputFieldsMap{"id": true, "version": true},
|
||||
},
|
||||
{
|
||||
name: "existing map, add nil",
|
||||
startMap: OutputFieldsMap{"id": true, "version": true},
|
||||
resMap: OutputFieldsMap{"id": true, "version": true},
|
||||
},
|
||||
{
|
||||
name: "existing with star, add nil",
|
||||
startMap: OutputFieldsMap{"*": true},
|
||||
resMap: OutputFieldsMap{"*": true},
|
||||
resStar: true,
|
||||
},
|
||||
{
|
||||
name: "existing with star, add new",
|
||||
fields: []string{"id", "version"},
|
||||
startMap: OutputFieldsMap{"*": true},
|
||||
resMap: OutputFieldsMap{"*": true},
|
||||
resStar: true,
|
||||
},
|
||||
{
|
||||
name: "existing without star, add new",
|
||||
fields: []string{"id", "version"},
|
||||
startMap: OutputFieldsMap{"name": true},
|
||||
resMap: OutputFieldsMap{"id": true, "version": true, "name": true},
|
||||
},
|
||||
{
|
||||
name: "existing without star, add star",
|
||||
fields: []string{"id", "*"},
|
||||
startMap: OutputFieldsMap{"name": true},
|
||||
resMap: OutputFieldsMap{"*": true},
|
||||
resStar: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
out := test.startMap.AddFields(test.fields)
|
||||
assert.True(out.HasAll() == test.resStar)
|
||||
assert.Equal(test.resMap, out)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ACLOutputFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type input struct {
|
||||
name string
|
||||
grants []string
|
||||
resource Resource
|
||||
action action.Type
|
||||
fields []string
|
||||
authorized bool
|
||||
}
|
||||
tests := []input{
|
||||
{
|
||||
name: "default",
|
||||
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
|
||||
action: action.Read,
|
||||
grants: []string{"id=bar;actions=read,update"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
name: "single value",
|
||||
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
|
||||
grants: []string{"id=bar;actions=read,update;output_fields=id"},
|
||||
action: action.Read,
|
||||
fields: []string{"id"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
name: "compound no overlap",
|
||||
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
|
||||
grants: []string{
|
||||
"id=bar;actions=read,update;output_fields=id",
|
||||
"id=*;type=host-catalog;actions=read,update;output_fields=version",
|
||||
},
|
||||
action: action.Read,
|
||||
fields: []string{"id"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
name: "compound",
|
||||
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
|
||||
grants: []string{
|
||||
"id=bar;actions=read,update;output_fields=id",
|
||||
"id=*;type=role;output_fields=version",
|
||||
},
|
||||
action: action.Read,
|
||||
fields: []string{"id", "version"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
name: "wildcard with type",
|
||||
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
|
||||
grants: []string{
|
||||
"id=bar;actions=read,update;output_fields=read",
|
||||
"id=*;type=role;output_fields=*",
|
||||
},
|
||||
action: action.Read,
|
||||
fields: []string{"*"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
name: "wildcard with wildcard type",
|
||||
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
|
||||
grants: []string{
|
||||
"id=bar;actions=read,update;output_fields=read",
|
||||
"id=*;type=*;output_fields=*",
|
||||
},
|
||||
action: action.Read,
|
||||
fields: []string{"*"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
name: "subaction exact",
|
||||
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
|
||||
grants: []string{
|
||||
"id=bar;actions=read:self,update;output_fields=version",
|
||||
},
|
||||
action: action.ReadSelf,
|
||||
fields: []string{"version"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
// If the action is a subaction, parent output fields will apply, in
|
||||
// addition to subaction. This matches authorization.
|
||||
name: "subaction parent action",
|
||||
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
|
||||
grants: []string{
|
||||
"id=bar;actions=read,update;output_fields=version",
|
||||
"id=bar;actions=read:self;output_fields=id",
|
||||
},
|
||||
action: action.ReadSelf,
|
||||
fields: []string{"id", "version"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
// The inverse isn't true. Similarly to authorization, if you have
|
||||
// specific output fields on a self action, they don't apply to
|
||||
// non-self actions. This is useful to allow more visibility to self
|
||||
// actions and less in the general case.
|
||||
name: "subaction child action",
|
||||
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
|
||||
grants: []string{
|
||||
"id=bar;actions=read:self,update;output_fields=version",
|
||||
"id=bar;actions=read;output_fields=id",
|
||||
},
|
||||
action: action.Read,
|
||||
fields: []string{"id"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
name: "initial grant unauthorized with star",
|
||||
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
|
||||
grants: []string{
|
||||
"id=bar;output_fields=*",
|
||||
"id=bar;actions=delete;output_fields=id",
|
||||
},
|
||||
action: action.Delete,
|
||||
fields: []string{"*"},
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
name: "unauthorized",
|
||||
resource: Resource{ScopeId: "o_myorg", Id: "bar", Type: resource.Role},
|
||||
grants: []string{
|
||||
"id=bar;output_fields=name",
|
||||
},
|
||||
action: action.Delete,
|
||||
fields: []string{"name"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var grants []Grant
|
||||
for _, g := range test.grants {
|
||||
grant, err := Parse("o_myorg", g)
|
||||
require.NoError(t, err)
|
||||
grants = append(grants, grant)
|
||||
}
|
||||
acl := NewACL(grants...)
|
||||
results := acl.Allowed(test.resource, test.action)
|
||||
assert.ElementsMatch(t, results.OutputFields.Fields(), test.fields)
|
||||
assert.True(t, test.authorized == results.Authorized)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ACLSelfOrDefault(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type input struct {
|
||||
name string
|
||||
input OutputFieldsMap
|
||||
output OutputFieldsMap
|
||||
userId string
|
||||
}
|
||||
tests := []input{
|
||||
{
|
||||
name: "nil, no user ID",
|
||||
output: OutputFieldsMap{},
|
||||
},
|
||||
{
|
||||
name: "nil, non anon id",
|
||||
output: OutputFieldsMap{"*": true},
|
||||
userId: "u_abc123",
|
||||
},
|
||||
{
|
||||
name: "nil, anon id",
|
||||
output: OutputFieldsMap{
|
||||
globals.IdField: true,
|
||||
globals.ScopeField: true,
|
||||
globals.ScopeIdField: true,
|
||||
globals.NameField: true,
|
||||
globals.DescriptionField: true,
|
||||
globals.TypeField: true,
|
||||
globals.IsPrimaryField: true,
|
||||
globals.AuthorizedActionsField: true,
|
||||
globals.AuthorizedCollectionActionsField: true,
|
||||
},
|
||||
userId: AnonymousUserId,
|
||||
},
|
||||
{
|
||||
name: "not nil",
|
||||
input: OutputFieldsMap{"foo": true},
|
||||
output: OutputFieldsMap{"foo": true},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.output, test.input.SelfOrDefaults(test.userId))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package requests
|
||||
|
||||
func getOpts(opt ...Option) options {
|
||||
opts := getDefaultOptions()
|
||||
for _, o := range opt {
|
||||
o(&opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// Option - how Options are passed as arguments. Some of these are used for
|
||||
// types within the handlers package, and some are for handlers to re-use across
|
||||
// the various handler types.
|
||||
type Option func(*options)
|
||||
|
||||
// options = how options are represented
|
||||
type options struct {
|
||||
withUserId string
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
return options{}
|
||||
}
|
||||
|
||||
// WithUserId specifies a user ID with which to populate the returned
|
||||
// RequestContext
|
||||
func WithUserId(userId string) Option {
|
||||
return func(o *options) {
|
||||
o.withUserId = userId
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
)
|
||||
|
||||
// ContextRequestInforation is a type used solely for context keys -- see the
|
||||
// variable below
|
||||
type ContextRequestInformation struct{}
|
||||
|
||||
// ContextRequestInformationKey is a value to keep linters from complaining
|
||||
// about clashing identifiers
|
||||
var ContextRequestInformationKey ContextRequestInformation
|
||||
|
||||
// RequestContext is used to propagate request information. It can be updated at
|
||||
// various points, e.g. UserId would be updated via the result of auth.Verify.
|
||||
//
|
||||
// It serves as a struct to gather information we learn about the request as it
|
||||
// goes along. This is distinct from options handling; none of the fields here
|
||||
// are "optional" and should all eventually be populated as the request moves
|
||||
// through the system.
|
||||
type RequestContext struct {
|
||||
// Method is the method of the request
|
||||
Method string
|
||||
|
||||
// Path is the path of the request
|
||||
Path string
|
||||
|
||||
// UserId contains the final discovered user ID
|
||||
UserId string
|
||||
|
||||
// OutputFields is the set of fields authorized for output for the
|
||||
// authorized action, if not the default
|
||||
OutputFields perms.OutputFieldsMap
|
||||
}
|
||||
|
||||
// NewRequestContext returns a derived context with a new RequestContext value
|
||||
// added in.
|
||||
func NewRequestContext(parent context.Context, opt ...Option) context.Context {
|
||||
opts := getOpts(opt...)
|
||||
ret := &RequestContext{
|
||||
UserId: opts.withUserId,
|
||||
}
|
||||
return context.WithValue(parent, ContextRequestInformationKey, ret)
|
||||
}
|
||||
|
||||
// RequestContextFromCtx pulls out RequestContext and returns it and an
|
||||
// indication it was found. If it's not found, nil will be returned and the bool
|
||||
// will be false.
|
||||
func RequestContextFromCtx(ctx context.Context) (*RequestContext, bool) {
|
||||
reqCtxRaw := ctx.Value(ContextRequestInformationKey)
|
||||
if reqCtxRaw == nil {
|
||||
return nil, false
|
||||
}
|
||||
reqCtx, ok := reqCtxRaw.(*RequestContext)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return reqCtx, true
|
||||
}
|
||||
|
||||
// OutputFields returns output fields from the given context and calls
|
||||
// SelfOrDefaults on it. If the context does not contain a RequestContext,
|
||||
// this will return nil, false.
|
||||
func OutputFields(ctx context.Context) (perms.OutputFieldsMap, bool) {
|
||||
reqCtx, ok := RequestContextFromCtx(ctx)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return reqCtx.OutputFields.SelfOrDefaults(reqCtx.UserId), true
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_RequestContextFromCtx(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type input struct {
|
||||
name string
|
||||
input context.Context
|
||||
output *RequestContext
|
||||
}
|
||||
tests := []input{
|
||||
{
|
||||
name: "not added",
|
||||
input: context.Background(),
|
||||
},
|
||||
{
|
||||
name: "added but empty",
|
||||
input: NewRequestContext(context.Background()),
|
||||
output: &RequestContext{},
|
||||
},
|
||||
{
|
||||
name: "non-empty",
|
||||
input: func() context.Context {
|
||||
ctx := context.WithValue(context.Background(), ContextRequestInformationKey, &RequestContext{
|
||||
UserId: "u_foo",
|
||||
})
|
||||
return ctx
|
||||
}(),
|
||||
output: &RequestContext{UserId: "u_foo"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
reqCtx, ok := RequestContextFromCtx(test.input)
|
||||
require.Equal(t, test.output == nil, !ok)
|
||||
assert.Equal(t, test.output, reqCtx)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc"
|
||||
"github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers/accounts"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers/authmethods"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers/host_sets"
|
||||
@@ -228,6 +229,18 @@ func wrapHandlerWithCommonFuncs(h http.Handler, c *Controller, props HandlerProp
|
||||
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = auth.GetTokenFromRequest(c.logger, c.kms, r)
|
||||
ctx = auth.NewVerifierContext(ctx, c.logger, c.IamRepoFn, c.AuthTokenRepoFn, c.ServersRepoFn, c.kms, requestInfo)
|
||||
|
||||
// Add general request information to the context. The information from
|
||||
// the auth verifier context is pretty specifically curated to
|
||||
// authentication/authorization verification so this is more
|
||||
// general-purpose.
|
||||
//
|
||||
// We could use requests.NewRequestContext but this saves an immediate
|
||||
// lookup.
|
||||
ctx = context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{
|
||||
Path: r.URL.Path,
|
||||
Method: r.Method,
|
||||
})
|
||||
|
||||
// Set the context back on the request
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc"
|
||||
oidcstore "github.com/hashicorp/boundary/internal/auth/oidc/store"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/accounts"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
@@ -120,22 +122,45 @@ func (s Service) ListAccounts(ctx context.Context, req *pbs.ListAccountsRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ul) == 0 {
|
||||
return &pbs.ListAccountsResponse{}, nil
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Account, 0, len(ul))
|
||||
res := &perms.Resource{
|
||||
|
||||
res := perms.Resource{
|
||||
ScopeId: authResults.Scope.Id,
|
||||
Type: resource.Account,
|
||||
Pin: req.GetAuthMethodId(),
|
||||
}
|
||||
for _, item := range ul {
|
||||
item.Scope = authResults.Scope
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions[auth.SubtypeFromId(item.Id)], auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
for _, acct := range ul {
|
||||
res.Id = acct.GetPublicId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, acct.GetPublicId(), IdActions[auth.SubtypeFromId(acct.GetPublicId())], auth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, acct, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This comes last so that we can use item fields in the filter after
|
||||
// the allowed fields are populated above
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
@@ -145,56 +170,119 @@ func (s Service) ListAccounts(ctx context.Context, req *pbs.ListAccountsRequest)
|
||||
|
||||
// GetAccount implements the interface pbs.AccountServiceServer.
|
||||
func (s Service) GetAccount(ctx context.Context, req *pbs.GetAccountRequest) (*pbs.GetAccountResponse, error) {
|
||||
const op = "accounts.(Service).GetAccount"
|
||||
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.Read)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.getFromRepo(ctx, req.GetId())
|
||||
acct, err := s.getFromRepo(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
|
||||
return &pbs.GetAccountResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, acct.GetPublicId(), IdActions[auth.SubtypeFromId(acct.GetPublicId())]).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, acct, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.GetAccountResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// CreateAccount implements the interface pbs.AccountServiceServer.
|
||||
func (s Service) CreateAccount(ctx context.Context, req *pbs.CreateAccountRequest) (*pbs.CreateAccountResponse, error) {
|
||||
const op = "accounts.(Service).CreateAccount"
|
||||
|
||||
if err := validateCreateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authMeth, authResults := s.parentAndAuthResult(ctx, req.GetItem().GetAuthMethodId(), action.Create)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.createInRepo(ctx, authMeth, req.GetItem())
|
||||
acct, err := s.createInRepo(ctx, authMeth, req.GetItem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(req.GetItem().GetAuthMethodId())]).Strings()
|
||||
return &pbs.CreateAccountResponse{Item: u, Uri: fmt.Sprintf("accounts/%s", u.GetId())}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, acct.GetPublicId(), IdActions[auth.SubtypeFromId(acct.GetPublicId())]).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, acct, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.CreateAccountResponse{Item: item, Uri: fmt.Sprintf("accounts/%s", item.GetId())}, nil
|
||||
}
|
||||
|
||||
// UpdateAccount implements the interface pbs.AccountServiceServer.
|
||||
func (s Service) UpdateAccount(ctx context.Context, req *pbs.UpdateAccountRequest) (*pbs.UpdateAccountResponse, error) {
|
||||
const op = "accounts.(Service).UpdateAccount"
|
||||
|
||||
if err := validateUpdateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authMeth, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.Update)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.updateInRepo(ctx, authResults.Scope.GetId(), authMeth.GetPublicId(), req)
|
||||
acct, err := s.updateInRepo(ctx, authResults.Scope.GetId(), authMeth.GetPublicId(), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
|
||||
return &pbs.UpdateAccountResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, acct.GetPublicId(), IdActions[auth.SubtypeFromId(acct.GetPublicId())]).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, acct, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.UpdateAccountResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// DeleteAccount implements the interface pbs.AccountServiceServer.
|
||||
@@ -215,41 +303,83 @@ func (s Service) DeleteAccount(ctx context.Context, req *pbs.DeleteAccountReques
|
||||
|
||||
// ChangePassword implements the interface pbs.AccountServiceServer.
|
||||
func (s Service) ChangePassword(ctx context.Context, req *pbs.ChangePasswordRequest) (*pbs.ChangePasswordResponse, error) {
|
||||
const op = "accounts.(Service).ChangePassword"
|
||||
|
||||
if err := validateChangePasswordRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.ChangePassword)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.changePasswordInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetVersion(), req.GetCurrentPassword(), req.GetNewPassword())
|
||||
acct, err := s.changePasswordInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetVersion(), req.GetCurrentPassword(), req.GetNewPassword())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
|
||||
return &pbs.ChangePasswordResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, acct.GetPublicId(), IdActions[auth.SubtypeFromId(acct.GetPublicId())]).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, acct, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.ChangePasswordResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// SetPassword implements the interface pbs.AccountServiceServer.
|
||||
func (s Service) SetPassword(ctx context.Context, req *pbs.SetPasswordRequest) (*pbs.SetPasswordResponse, error) {
|
||||
const op = "accounts.(Service).SetPassword"
|
||||
|
||||
if err := validateSetPasswordRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.SetPassword)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.setPasswordInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetVersion(), req.GetPassword())
|
||||
acct, err := s.setPasswordInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetVersion(), req.GetPassword())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
|
||||
return &pbs.SetPasswordResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, acct.GetPublicId(), IdActions[auth.SubtypeFromId(acct.GetPublicId())]).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, acct, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.SetPasswordResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Account, error) {
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (auth.Account, error) {
|
||||
var acct auth.Account
|
||||
switch auth.SubtypeFromId(id) {
|
||||
case auth.PasswordSubtype:
|
||||
@@ -281,7 +411,7 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Account, error
|
||||
default:
|
||||
return nil, handlers.NotFoundErrorf("Unrecognized id.")
|
||||
}
|
||||
return toProto(acct)
|
||||
return acct, nil
|
||||
}
|
||||
|
||||
func (s Service) createPwInRepo(ctx context.Context, am auth.AuthMethod, item *pb.Account) (*password.Account, error) {
|
||||
@@ -367,7 +497,7 @@ func (s Service) createOidcInRepo(ctx context.Context, am auth.AuthMethod, item
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) createInRepo(ctx context.Context, am auth.AuthMethod, item *pb.Account) (*pb.Account, error) {
|
||||
func (s Service) createInRepo(ctx context.Context, am auth.AuthMethod, item *pb.Account) (auth.Account, error) {
|
||||
const op = "accounts.(Service).createInRepo"
|
||||
if item == nil {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing item")
|
||||
@@ -393,7 +523,7 @@ func (s Service) createInRepo(ctx context.Context, am auth.AuthMethod, item *pb.
|
||||
}
|
||||
out = am
|
||||
}
|
||||
return toProto(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) updatePwInRepo(ctx context.Context, scopeId, authMethId, id string, mask []string, item *pb.Account) (*password.Account, error) {
|
||||
@@ -463,7 +593,7 @@ func (s Service) updateOidcInRepo(ctx context.Context, scopeId, amId, id string,
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId, authMethodId string, req *pbs.UpdateAccountRequest) (*pb.Account, error) {
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId, authMethodId string, req *pbs.UpdateAccountRequest) (auth.Account, error) {
|
||||
const op = "accounts.(Service).updateInRepo"
|
||||
var out auth.Account
|
||||
switch auth.SubtypeFromId(req.GetId()) {
|
||||
@@ -486,7 +616,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, authMethodId string,
|
||||
}
|
||||
out = a
|
||||
}
|
||||
return toProto(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) {
|
||||
@@ -516,46 +646,40 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool,
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, authMethodId string) ([]*pb.Account, error) {
|
||||
var outUl []*pb.Account
|
||||
func (s Service) listFromRepo(ctx context.Context, authMethodId string) ([]auth.Account, error) {
|
||||
const op = "accounts.(Service).listFromRepo"
|
||||
|
||||
var outUl []auth.Account
|
||||
switch auth.SubtypeFromId(authMethodId) {
|
||||
case auth.PasswordSubtype:
|
||||
pwRepo, err := s.pwRepoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
pwl, err := pwRepo.ListAccounts(ctx, authMethodId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
for _, u := range pwl {
|
||||
ou, err := toProto(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outUl = append(outUl, ou)
|
||||
for _, a := range pwl {
|
||||
outUl = append(outUl, a)
|
||||
}
|
||||
case auth.OidcSubtype:
|
||||
oidcRepo, err := s.oidcRepoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
oidcl, err := oidcRepo.ListAccounts(ctx, authMethodId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
for _, u := range oidcl {
|
||||
ou, err := toProto(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outUl = append(outUl, ou)
|
||||
for _, a := range oidcl {
|
||||
outUl = append(outUl, a)
|
||||
}
|
||||
}
|
||||
return outUl, nil
|
||||
}
|
||||
|
||||
func (s Service) changePasswordInRepo(ctx context.Context, scopeId, id string, version uint32, currentPassword, newPassword string) (*pb.Account, error) {
|
||||
func (s Service) changePasswordInRepo(ctx context.Context, scopeId, id string, version uint32, currentPassword, newPassword string) (auth.Account, error) {
|
||||
const op = "account.(Service).changePasswordInRepo"
|
||||
repo, err := s.pwRepoFn()
|
||||
if err != nil {
|
||||
@@ -578,10 +702,10 @@ func (s Service) changePasswordInRepo(ctx context.Context, scopeId, id string, v
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.PermissionDenied, "Failed to change password.")
|
||||
}
|
||||
return toProto(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) setPasswordInRepo(ctx context.Context, scopeId, id string, version uint32, pw string) (*pb.Account, error) {
|
||||
func (s Service) setPasswordInRepo(ctx context.Context, scopeId, id string, version uint32, pw string) (auth.Account, error) {
|
||||
const op = "accounts.(Service).setPasswordInRepo"
|
||||
|
||||
repo, err := s.pwRepoFn()
|
||||
@@ -599,7 +723,7 @@ func (s Service) setPasswordInRepo(ctx context.Context, scopeId, id string, vers
|
||||
}
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
return toProto(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Type) (auth.AuthMethod, auth.VerifyResults) {
|
||||
@@ -677,30 +801,61 @@ func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Ty
|
||||
return authMeth, auth.Verify(ctx, opts...)
|
||||
}
|
||||
|
||||
func toProto(in auth.Account) (*pb.Account, error) {
|
||||
out := pb.Account{
|
||||
Id: in.GetPublicId(),
|
||||
CreatedTime: in.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
|
||||
AuthMethodId: in.GetAuthMethodId(),
|
||||
Version: in.GetVersion(),
|
||||
func toProto(ctx context.Context, in auth.Account, opt ...handlers.Option) (*pb.Account, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building account proto")
|
||||
}
|
||||
if in.GetDescription() != "" {
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.Account{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.GetPublicId()
|
||||
}
|
||||
if outputFields.Has(globals.AuthMethodIdField) {
|
||||
out.AuthMethodId = in.GetAuthMethodId()
|
||||
}
|
||||
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
|
||||
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
|
||||
}
|
||||
if in.GetName() != "" {
|
||||
if outputFields.Has(globals.NameField) && in.GetName() != "" {
|
||||
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
|
||||
}
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.GetCreateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.UpdatedTimeField) {
|
||||
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.VersionField) {
|
||||
out.Version = in.GetVersion()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
out.AuthorizedActions = opts.WithAuthorizedActions
|
||||
}
|
||||
switch i := in.(type) {
|
||||
case *password.Account:
|
||||
out.Type = auth.PasswordSubtype.String()
|
||||
if outputFields.Has(globals.TypeField) {
|
||||
out.Type = auth.PasswordSubtype.String()
|
||||
}
|
||||
if !outputFields.Has(globals.AttributesField) {
|
||||
break
|
||||
}
|
||||
st, err := handlers.ProtoToStruct(&pb.PasswordAccountAttributes{LoginName: i.GetLoginName()})
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "failed building password attribute struct: %v", err)
|
||||
}
|
||||
out.Attributes = st
|
||||
case *oidc.Account:
|
||||
out.Type = auth.OidcSubtype.String()
|
||||
if outputFields.Has(globals.TypeField) {
|
||||
out.Type = auth.OidcSubtype.String()
|
||||
}
|
||||
if !outputFields.Has(globals.AttributesField) {
|
||||
break
|
||||
}
|
||||
attrs := &pb.OidcAccountAttributes{
|
||||
Issuer: i.GetIssuer(),
|
||||
Subject: i.GetSubject(),
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc"
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
@@ -268,10 +269,11 @@ func TestListPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
req *pbs.ListAccountsRequest
|
||||
res *pbs.ListAccountsResponse
|
||||
err error
|
||||
name string
|
||||
req *pbs.ListAccountsRequest
|
||||
res *pbs.ListAccountsResponse
|
||||
err error
|
||||
skipAnon bool
|
||||
}{
|
||||
{
|
||||
name: "List Some Accounts",
|
||||
@@ -299,7 +301,8 @@ func TestListPassword(t *testing.T) {
|
||||
AuthMethodId: amSomeAccounts.GetPublicId(),
|
||||
Filter: fmt.Sprintf(`"/item/attributes/login_name"==%q`, wantSomeAccounts[1].Attributes.AsMap()["login_name"]),
|
||||
},
|
||||
res: &pbs.ListAccountsResponse{Items: wantSomeAccounts[1:2]},
|
||||
res: &pbs.ListAccountsResponse{Items: wantSomeAccounts[1:2]},
|
||||
skipAnon: true,
|
||||
},
|
||||
{
|
||||
name: "Filter All Accounts",
|
||||
@@ -321,6 +324,7 @@ func TestListPassword(t *testing.T) {
|
||||
s, err := accounts.NewService(pwRepoFn, oidcRepoFn)
|
||||
require.NoError(err, "Couldn't create new user service.")
|
||||
|
||||
// Test non-anon first
|
||||
got, gErr := s.ListAccounts(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
@@ -330,6 +334,20 @@ func TestListPassword(t *testing.T) {
|
||||
require.NoError(gErr)
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "ListAccounts() with scope %q got response %q, wanted %q", tc.req, got, tc.res)
|
||||
|
||||
// Now test with anon
|
||||
if tc.skipAnon {
|
||||
return
|
||||
}
|
||||
got, gErr = s.ListAccounts(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, g := range got.GetItems() {
|
||||
assert.Nil(g.Attributes)
|
||||
assert.Nil(g.CreatedTime)
|
||||
assert.Nil(g.UpdatedTime)
|
||||
assert.Empty(g.Version)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -401,10 +419,11 @@ func TestListOidc(t *testing.T) {
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
req *pbs.ListAccountsRequest
|
||||
res *pbs.ListAccountsResponse
|
||||
err error
|
||||
name string
|
||||
req *pbs.ListAccountsRequest
|
||||
res *pbs.ListAccountsResponse
|
||||
err error
|
||||
skipAnon bool
|
||||
}{
|
||||
{
|
||||
name: "List Some Accounts",
|
||||
@@ -432,7 +451,8 @@ func TestListOidc(t *testing.T) {
|
||||
AuthMethodId: amSomeAccounts.GetPublicId(),
|
||||
Filter: fmt.Sprintf(`"/item/attributes/subject"==%q`, wantSomeAccounts[1].Attributes.AsMap()["subject"]),
|
||||
},
|
||||
res: &pbs.ListAccountsResponse{Items: wantSomeAccounts[1:2]},
|
||||
res: &pbs.ListAccountsResponse{Items: wantSomeAccounts[1:2]},
|
||||
skipAnon: true,
|
||||
},
|
||||
{
|
||||
name: "Filter All Accounts",
|
||||
@@ -467,6 +487,20 @@ func TestListOidc(t *testing.T) {
|
||||
got.Items[j].GetAttributes().GetFields()["subject"].GetStringValue()) < 0
|
||||
})
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListAccounts() with scope %q got response %q, wanted %q", tc.req, got, tc.res)
|
||||
|
||||
// Now test with anon
|
||||
if tc.skipAnon {
|
||||
return
|
||||
}
|
||||
got, gErr = s.ListAccounts(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, g := range got.GetItems() {
|
||||
assert.Nil(g.Attributes)
|
||||
assert.Nil(g.CreatedTime)
|
||||
assert.Nil(g.UpdatedTime)
|
||||
assert.Empty(g.Version)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1067,7 +1101,7 @@ func TestUpdatePassword(t *testing.T) {
|
||||
name: "Update an Existing AuthMethod",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"name", "description"},
|
||||
Paths: []string{globals.NameField, globals.DescriptionField},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "new"},
|
||||
@@ -1126,7 +1160,7 @@ func TestUpdatePassword(t *testing.T) {
|
||||
name: "Cant change type",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"name"},
|
||||
Paths: []string{globals.NameField},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: ""},
|
||||
@@ -1163,7 +1197,7 @@ func TestUpdatePassword(t *testing.T) {
|
||||
name: "Unset Name",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"name"},
|
||||
Paths: []string{globals.NameField},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Description: &wrapperspb.StringValue{Value: "ignored"},
|
||||
@@ -1185,7 +1219,7 @@ func TestUpdatePassword(t *testing.T) {
|
||||
name: "Update Only Name",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"name"},
|
||||
Paths: []string{globals.NameField},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "updated"},
|
||||
@@ -1209,7 +1243,7 @@ func TestUpdatePassword(t *testing.T) {
|
||||
name: "Update Only Description",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"description"},
|
||||
Paths: []string{globals.DescriptionField},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "ignored"},
|
||||
@@ -1258,7 +1292,7 @@ func TestUpdatePassword(t *testing.T) {
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
Id: password.AccountPrefix + "_DoesntExis",
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"description"},
|
||||
Paths: []string{globals.DescriptionField},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "new"},
|
||||
@@ -1434,7 +1468,7 @@ func TestUpdateOidc(t *testing.T) {
|
||||
name: "Update an Existing AuthMethod",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"name", "description"},
|
||||
Paths: []string{globals.NameField, globals.DescriptionField},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "new"},
|
||||
@@ -1493,7 +1527,7 @@ func TestUpdateOidc(t *testing.T) {
|
||||
name: "Cant change type",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"name"},
|
||||
Paths: []string{globals.NameField},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: ""},
|
||||
@@ -1530,7 +1564,7 @@ func TestUpdateOidc(t *testing.T) {
|
||||
name: "Unset Name",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"name"},
|
||||
Paths: []string{globals.NameField},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Description: &wrapperspb.StringValue{Value: "ignored"},
|
||||
@@ -1552,7 +1586,7 @@ func TestUpdateOidc(t *testing.T) {
|
||||
name: "Update Only Name",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"name"},
|
||||
Paths: []string{globals.NameField},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "updated"},
|
||||
@@ -1576,7 +1610,7 @@ func TestUpdateOidc(t *testing.T) {
|
||||
name: "Update Only Description",
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"description"},
|
||||
Paths: []string{globals.DescriptionField},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "ignored"},
|
||||
@@ -1615,7 +1649,7 @@ func TestUpdateOidc(t *testing.T) {
|
||||
req: &pbs.UpdateAccountRequest{
|
||||
Id: password.AccountPrefix + "_DoesntExis",
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"description"},
|
||||
Paths: []string{globals.DescriptionField},
|
||||
},
|
||||
Item: &pb.Account{
|
||||
Name: &wrapperspb.StringValue{Value: "new"},
|
||||
@@ -1864,7 +1898,7 @@ func TestSetPassword(t *testing.T) {
|
||||
password: "anewpassword",
|
||||
},
|
||||
{
|
||||
name: "password to short",
|
||||
name: "password too short",
|
||||
accountId: defaultAcct.GetId(),
|
||||
version: defaultAcct.GetVersion(),
|
||||
password: "123",
|
||||
@@ -2027,7 +2061,7 @@ func TestChangePassword(t *testing.T) {
|
||||
newPW: "anewpassword",
|
||||
},
|
||||
{
|
||||
name: "new password to short",
|
||||
name: "new password too short",
|
||||
authMethodId: defaultAcct.GetAuthMethodId(),
|
||||
accountId: defaultAcct.GetId(),
|
||||
version: defaultAcct.GetVersion(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc"
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
@@ -61,33 +63,6 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func populateCollectionAuthorizedActions(ctx context.Context,
|
||||
authResults auth.VerifyResults,
|
||||
item *pb.AuthMethod) error {
|
||||
res := &perms.Resource{
|
||||
ScopeId: authResults.Scope.Id,
|
||||
Pin: item.Id,
|
||||
}
|
||||
// Range over the defined collections and check permissions against those
|
||||
// collections. We use the ID of this scope being returned, not its parent,
|
||||
// hence passing in a resource here.
|
||||
for k, v := range collectionTypeMap {
|
||||
res.Type = k
|
||||
acts := authResults.FetchActionSetForType(ctx, k, v, auth.WithResource(res)).Strings()
|
||||
if len(acts) > 0 {
|
||||
if item.AuthorizedCollectionActions == nil {
|
||||
item.AuthorizedCollectionActions = make(map[string]*structpb.ListValue)
|
||||
}
|
||||
lv, err := structpb.NewList(strutil.StringListToInterfaceList(acts))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
item.AuthorizedCollectionActions[k.String()+"s"] = lv
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Service handles request as described by the pbs.AuthMethodServiceServer interface.
|
||||
type Service struct {
|
||||
pbs.UnimplementedAuthMethodServiceServer
|
||||
@@ -157,30 +132,54 @@ func (s Service) ListAuthMethods(ctx context.Context, req *pbs.ListAuthMethodsRe
|
||||
return &pbs.ListAuthMethodsResponse{}, nil
|
||||
}
|
||||
|
||||
ul, err := s.listFromRepo(ctx, scopeIds, authResults.UserId == auth.AnonymousUserId)
|
||||
ul, err := s.listFromRepo(ctx, scopeIds, authResults)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ul) == 0 {
|
||||
return &pbs.ListAuthMethodsResponse{}, nil
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.AuthMethod, 0, len(ul))
|
||||
res := &perms.Resource{
|
||||
res := perms.Resource{
|
||||
Type: resource.AuthMethod,
|
||||
}
|
||||
for _, item := range ul {
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions[auth.SubtypeFromId(item.Id)], auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
for _, am := range ul {
|
||||
res.Id = am.GetPublicId()
|
||||
res.ScopeId = am.GetScopeId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, am.GetPublicId(), IdActions[auth.SubtypeFromId(am.GetPublicId())], auth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, item); err != nil {
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[am.GetScopeId()]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, am.GetPublicId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := toAuthMethodProto(ctx, am, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
return &pbs.ListAuthMethodsResponse{Items: finalItems}, nil
|
||||
@@ -188,6 +187,8 @@ func (s Service) ListAuthMethods(ctx context.Context, req *pbs.ListAuthMethodsRe
|
||||
|
||||
// GetAuthMethod implements the interface pbs.AuthMethodServiceServer.
|
||||
func (s Service) GetAuthMethod(ctx context.Context, req *pbs.GetAuthMethodRequest) (*pbs.GetAuthMethodResponse, error) {
|
||||
const op = "authmethods.(Service).GetAuthMethod"
|
||||
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -195,20 +196,44 @@ func (s Service) GetAuthMethod(ctx context.Context, req *pbs.GetAuthMethodReques
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.getFromRepo(ctx, req.GetId())
|
||||
am, err := s.getFromRepo(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, u); err != nil {
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, am.GetPublicId(), IdActions[auth.SubtypeFromId(am.GetPublicId())]).Strings()))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, am.GetPublicId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := toAuthMethodProto(ctx, am, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbs.GetAuthMethodResponse{Item: u}, nil
|
||||
|
||||
return &pbs.GetAuthMethodResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// CreateAuthMethod implements the interface pbs.AuthMethodServiceServer.
|
||||
func (s Service) CreateAuthMethod(ctx context.Context, req *pbs.CreateAuthMethodRequest) (*pbs.CreateAuthMethodResponse, error) {
|
||||
const op = "authmethods.(Service).CreateAuthMethod"
|
||||
|
||||
if err := validateCreateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -216,20 +241,44 @@ func (s Service) CreateAuthMethod(ctx context.Context, req *pbs.CreateAuthMethod
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem())
|
||||
am, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, u); err != nil {
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, am.GetPublicId(), IdActions[auth.SubtypeFromId(am.GetPublicId())]).Strings()))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, am.GetPublicId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := toAuthMethodProto(ctx, am, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbs.CreateAuthMethodResponse{Item: u, Uri: fmt.Sprintf("auth-methods/%s", u.GetId())}, nil
|
||||
|
||||
return &pbs.CreateAuthMethodResponse{Item: item, Uri: fmt.Sprintf("auth-methods/%s", item.GetId())}, nil
|
||||
}
|
||||
|
||||
// UpdateAuthMethod implements the interface pbs.AuthMethodServiceServer.
|
||||
func (s Service) UpdateAuthMethod(ctx context.Context, req *pbs.UpdateAuthMethodRequest) (*pbs.UpdateAuthMethodResponse, error) {
|
||||
const op = "authmethods.(Service).UpdateAuthMethod"
|
||||
|
||||
if err := validateUpdateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -237,7 +286,7 @@ func (s Service) UpdateAuthMethod(ctx context.Context, req *pbs.UpdateAuthMethod
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req)
|
||||
am, dryRun, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Match(errors.T(errors.InvalidParameter), err):
|
||||
@@ -246,16 +295,44 @@ func (s Service) UpdateAuthMethod(ctx context.Context, req *pbs.UpdateAuthMethod
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions[auth.SubtypeFromId(u.Id)]).Strings()
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, u); err != nil {
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, am.GetPublicId(), IdActions[auth.SubtypeFromId(am.GetPublicId())]).Strings()))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, am.GetPublicId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := toAuthMethodProto(ctx, am, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbs.UpdateAuthMethodResponse{Item: u}, nil
|
||||
|
||||
if item.GetAttributes() != nil && dryRun {
|
||||
item.GetAttributes().Fields["dry_run"] = structpb.NewBoolValue(true)
|
||||
}
|
||||
|
||||
return &pbs.UpdateAuthMethodResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// ChangeState implements the interface pbs.AuthMethodServiceServer.
|
||||
func (s Service) ChangeState(ctx context.Context, req *pbs.ChangeStateRequest) (*pbs.ChangeStateResponse, error) {
|
||||
const op = "authmethods.(Service).ChangeState"
|
||||
|
||||
if err := validateChangeStateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -272,12 +349,34 @@ func (s Service) ChangeState(ctx context.Context, req *pbs.ChangeStateRequest) (
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
am.Scope = authResults.Scope
|
||||
am.AuthorizedActions = authResults.FetchActionSetForId(ctx, am.Id, IdActions[auth.OidcSubtype]).Strings()
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, am); err != nil {
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, am.GetPublicId(), IdActions[auth.SubtypeFromId(am.GetPublicId())]).Strings()))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, am.GetPublicId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := toAuthMethodProto(ctx, am, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbs.ChangeStateResponse{Item: am}, nil
|
||||
|
||||
return &pbs.ChangeStateResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// DeleteAuthMethod implements the interface pbs.AuthMethodServiceServer.
|
||||
@@ -351,7 +450,7 @@ func (s Service) AuthenticateLogin(ctx context.Context, req *pbs.AuthenticateLog
|
||||
return &pbs.AuthenticateLoginResponse{Item: tok, TokenType: req.GetTokenType()}, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.AuthMethod, error) {
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (auth.AuthMethod, error) {
|
||||
var lookupErr error
|
||||
var am auth.AuthMethod
|
||||
switch auth.SubtypeFromId(id) {
|
||||
@@ -383,26 +482,28 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*pb.AuthMethod, er
|
||||
return nil, handlers.NotFoundErrorf("AuthMethod %q doesn't exist.", id)
|
||||
}
|
||||
|
||||
return toAuthMethodProto(am)
|
||||
return am, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string, anonUser bool) ([]*pb.AuthMethod, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string, authResults auth.VerifyResults) ([]auth.AuthMethod, error) {
|
||||
const op = "authmethods.(Service).listFromRepo"
|
||||
reqCtx, ok := requests.RequestContextFromCtx(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
var outUl []auth.AuthMethod
|
||||
|
||||
oidcRepo, err := s.oidcRepoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
ol, err := oidcRepo.ListAuthMethods(ctx, scopeIds, oidc.WithUnauthenticatedUser(anonUser))
|
||||
ol, err := oidcRepo.ListAuthMethods(ctx, scopeIds, oidc.WithUnauthenticatedUser(reqCtx.UserId == auth.AnonymousUserId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
var outUl []*pb.AuthMethod
|
||||
for _, u := range ol {
|
||||
ou, err := toAuthMethodProto(u, handlers.WithAnonymousListing(anonUser))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outUl = append(outUl, ou)
|
||||
for _, item := range ol {
|
||||
outUl = append(outUl, item)
|
||||
}
|
||||
|
||||
repo, err := s.pwRepoFn()
|
||||
@@ -413,17 +514,13 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string, anonUser b
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
for _, u := range pl {
|
||||
ou, err := toAuthMethodProto(u, handlers.WithAnonymousListing(anonUser))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
outUl = append(outUl, ou)
|
||||
for _, item := range pl {
|
||||
outUl = append(outUl, item)
|
||||
}
|
||||
return outUl, nil
|
||||
}
|
||||
|
||||
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.AuthMethod) (*pb.AuthMethod, error) {
|
||||
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.AuthMethod) (auth.AuthMethod, error) {
|
||||
const op = "authmethods.(Service).createInRepo"
|
||||
var out auth.AuthMethod
|
||||
switch auth.SubtypeFromType(item.GetType()) {
|
||||
@@ -446,51 +543,39 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Auth
|
||||
}
|
||||
out = am
|
||||
}
|
||||
return toAuthMethodProto(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId string, req *pbs.UpdateAuthMethodRequest) (*pb.AuthMethod, error) {
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId string, req *pbs.UpdateAuthMethodRequest) (auth.AuthMethod, bool, error) {
|
||||
const op = "authmethods.(Service).updateInRepo"
|
||||
storageToWire := toAuthMethodProto
|
||||
var out auth.AuthMethod
|
||||
|
||||
var am auth.AuthMethod
|
||||
var dryRun bool
|
||||
|
||||
switch auth.SubtypeFromId(req.GetId()) {
|
||||
case auth.PasswordSubtype:
|
||||
am, err := s.updatePwInRepo(ctx, scopeId, req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
pam, err := s.updatePwInRepo(ctx, scopeId, req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
return nil, false, errors.Wrap(err, op)
|
||||
}
|
||||
if am == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to update auth method but no error returned from repository.")
|
||||
if pam == nil {
|
||||
return nil, false, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to update auth method but no error returned from repository.")
|
||||
}
|
||||
out = am
|
||||
am = pam
|
||||
|
||||
case auth.OidcSubtype:
|
||||
am, dryRun, err := s.updateOidcInRepo(ctx, scopeId, req)
|
||||
oam, dr, err := s.updateOidcInRepo(ctx, scopeId, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
return nil, false, errors.Wrap(err, op)
|
||||
}
|
||||
if am == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to update auth method but no error returned from repository.")
|
||||
if oam == nil {
|
||||
return nil, false, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to update auth method but no error returned from repository.")
|
||||
}
|
||||
if dryRun {
|
||||
storageToWire = func(in auth.AuthMethod, opt ...handlers.Option) (*pb.AuthMethod, error) {
|
||||
am, err := toAuthMethodProto(in)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
attrs := &pb.OidcAuthMethodAttributes{}
|
||||
if err := handlers.StructToProto(am.GetAttributes(), attrs); err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("can't convert from attribute struct to proto"))
|
||||
}
|
||||
attrs.DryRun = true
|
||||
if am.Attributes, err = handlers.ProtoToStruct(attrs); err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("can't convert from attribute proto to struct"))
|
||||
}
|
||||
return am, nil
|
||||
}
|
||||
}
|
||||
out = am
|
||||
am = oam
|
||||
dryRun = dr
|
||||
}
|
||||
return storageToWire(out)
|
||||
|
||||
return am, dryRun, nil
|
||||
}
|
||||
|
||||
func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) {
|
||||
@@ -523,7 +608,7 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool,
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) changeStateInRepo(ctx context.Context, req *pbs.ChangeStateRequest) (*pb.AuthMethod, error) {
|
||||
func (s Service) changeStateInRepo(ctx context.Context, req *pbs.ChangeStateRequest) (auth.AuthMethod, error) {
|
||||
const op = "authmethod_service.(Service).changeStateInRepo"
|
||||
|
||||
switch auth.SubtypeFromId(req.GetId()) {
|
||||
@@ -558,7 +643,7 @@ func (s Service) changeStateInRepo(ctx context.Context, req *pbs.ChangeStateRequ
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toAuthMethodProto(am)
|
||||
return am, nil
|
||||
}
|
||||
|
||||
return nil, errors.New(errors.InvalidParameter, op, "Given auth method type does not support changing state")
|
||||
@@ -633,28 +718,53 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
|
||||
return auth.Verify(ctx, opts...)
|
||||
}
|
||||
|
||||
func toAuthMethodProto(in auth.AuthMethod, opt ...handlers.Option) (*pb.AuthMethod, error) {
|
||||
anonListing := handlers.GetOpts(opt...).WithAnonymousListing
|
||||
out := &pb.AuthMethod{
|
||||
Id: in.GetPublicId(),
|
||||
ScopeId: in.GetScopeId(),
|
||||
IsPrimary: in.GetIsPrimaryAuthMethod(),
|
||||
func toAuthMethodProto(ctx context.Context, in auth.AuthMethod, opt ...handlers.Option) (*pb.AuthMethod, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building auth method proto")
|
||||
}
|
||||
if in.GetDescription() != "" {
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.AuthMethod{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.GetPublicId()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeIdField) {
|
||||
out.ScopeId = in.GetScopeId()
|
||||
}
|
||||
if outputFields.Has(globals.IsPrimaryField) {
|
||||
out.IsPrimary = in.GetIsPrimaryAuthMethod()
|
||||
}
|
||||
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
|
||||
out.Description = wrapperspb.String(in.GetDescription())
|
||||
}
|
||||
if in.GetName() != "" {
|
||||
if outputFields.Has(globals.NameField) && in.GetName() != "" {
|
||||
out.Name = wrapperspb.String(in.GetName())
|
||||
}
|
||||
if !anonListing {
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.GetCreateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.UpdatedTimeField) {
|
||||
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.VersionField) {
|
||||
out.Version = in.GetVersion()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
out.AuthorizedActions = opts.WithAuthorizedActions
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
out.AuthorizedCollectionActions = opts.WithAuthorizedCollectionActions
|
||||
}
|
||||
switch i := in.(type) {
|
||||
case *password.AuthMethod:
|
||||
out.Type = auth.PasswordSubtype.String()
|
||||
if anonListing {
|
||||
if outputFields.Has(globals.TypeField) {
|
||||
out.Type = auth.PasswordSubtype.String()
|
||||
}
|
||||
if !outputFields.Has(globals.AttributesField) {
|
||||
break
|
||||
}
|
||||
st, err := handlers.ProtoToStruct(&pb.PasswordAuthMethodAttributes{
|
||||
@@ -666,8 +776,10 @@ func toAuthMethodProto(in auth.AuthMethod, opt ...handlers.Option) (*pb.AuthMeth
|
||||
}
|
||||
out.Attributes = st
|
||||
case *oidc.AuthMethod:
|
||||
out.Type = auth.OidcSubtype.String()
|
||||
if anonListing {
|
||||
if outputFields.Has(globals.TypeField) {
|
||||
out.Type = auth.OidcSubtype.String()
|
||||
}
|
||||
if !outputFields.Has(globals.AttributesField) {
|
||||
break
|
||||
}
|
||||
attrs := &pb.OidcAuthMethodAttributes{
|
||||
@@ -704,7 +816,7 @@ func toAuthMethodProto(in auth.AuthMethod, opt ...handlers.Option) (*pb.AuthMeth
|
||||
}
|
||||
out.Attributes = st
|
||||
}
|
||||
return out, nil
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func toAuthTokenProto(t *authtoken.AuthToken) *pba.AuthToken {
|
||||
@@ -1080,8 +1192,8 @@ func validateAuthenticateLoginRequest(req *pbs.AuthenticateLoginRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Service) convertInternalAuthTokenToApiAuthToken(ctx context.Context, tok *authtoken.AuthToken) (*pba.AuthToken, error) {
|
||||
const op = "authmethod.convertInternalAuthTokenToApiAuthToken"
|
||||
func (s Service) ConvertInternalAuthTokenToApiAuthToken(ctx context.Context, tok *authtoken.AuthToken) (*pba.AuthToken, error) {
|
||||
const op = "authmethod.ConvertInternalAuthTokenToApiAuthToken"
|
||||
iamRepo, err := s.iamRepoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -353,7 +353,7 @@ func TestList(t *testing.T) {
|
||||
require.NoError(err, "Couldn't create new auth_method service.")
|
||||
|
||||
// First check with non-anonymous user
|
||||
got, gErr := s.ListAuthMethods(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId("u_auth")), tc.req)
|
||||
got, gErr := s.ListAuthMethods(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListAuthMethods() for scope %q got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
|
||||
@@ -370,8 +370,9 @@ func TestList(t *testing.T) {
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListAuthMethods() for scope %q got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
|
||||
|
||||
// Now check with anonymous user
|
||||
got, gErr = s.ListAuthMethods(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req)
|
||||
got, gErr = s.ListAuthMethods(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, g := range got.GetItems() {
|
||||
assert.Nil(g.Attributes)
|
||||
assert.Nil(g.CreatedTime)
|
||||
|
||||
@@ -324,7 +324,7 @@ func (s Service) authenticateOidcToken(ctx context.Context, req *pbs.Authenticat
|
||||
}, nil
|
||||
}
|
||||
|
||||
responseToken, err := s.convertInternalAuthTokenToApiAuthToken(
|
||||
responseToken, err := s.ConvertInternalAuthTokenToApiAuthToken(
|
||||
ctx,
|
||||
token,
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
@@ -153,6 +154,7 @@ func TestList_FilterNonPublic(t *testing.T) {
|
||||
serversRepoFn := func() (*servers.Repository, error) {
|
||||
return servers.NewRepository(rw, rw, kmsCache)
|
||||
}
|
||||
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
|
||||
o, _ := iam.TestScopes(t, iamRepo)
|
||||
@@ -194,14 +196,14 @@ func TestList_FilterNonPublic(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "unauthenticated",
|
||||
reqCtx: auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()),
|
||||
reqCtx: auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)),
|
||||
respCount: 1,
|
||||
},
|
||||
{
|
||||
name: "authenticated",
|
||||
reqCtx: func() context.Context {
|
||||
at := authtoken.TestAuthToken(t, conn, kmsCache, o.GetPublicId())
|
||||
return auth.NewVerifierContext(context.Background(),
|
||||
return auth.NewVerifierContext(requests.NewRequestContext(context.Background()),
|
||||
nil,
|
||||
iamRepoFn,
|
||||
authTokenRepoFn,
|
||||
|
||||
@@ -127,7 +127,7 @@ func (s Service) authenticateWithPwRepo(ctx context.Context, scopeId, authMethod
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.convertInternalAuthTokenToApiAuthToken(
|
||||
return s.ConvertInternalAuthTokenToApiAuthToken(
|
||||
ctx,
|
||||
tok,
|
||||
)
|
||||
|
||||
@@ -4,18 +4,21 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/authtoken"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/authtokens"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -90,37 +93,57 @@ func (s Service) ListAuthTokens(ctx context.Context, req *pbs.ListAuthTokensRequ
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ul) == 0 {
|
||||
return &pbs.ListAuthTokensResponse{}, nil
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.AuthToken, 0, len(ul))
|
||||
res := &perms.Resource{
|
||||
res := perms.Resource{
|
||||
Type: resource.AuthToken,
|
||||
}
|
||||
for _, item := range ul {
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res))
|
||||
for _, at := range ul {
|
||||
res.Id = at.GetPublicId()
|
||||
res.ScopeId = at.GetScopeId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, at.GetPublicId(), IdActions, auth.WithResource(&res))
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if authorizedActions.OnlySelf() && item.GetUserId() != authResults.UserId {
|
||||
if authorizedActions.OnlySelf() && at.GetIamUserId() != authResults.UserId {
|
||||
continue
|
||||
}
|
||||
|
||||
item.AuthorizedActions = authorizedActions.Strings()
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[at.GetScopeId()]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions.Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, at, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
|
||||
return &pbs.ListAuthTokensResponse{Items: finalItems}, nil
|
||||
}
|
||||
|
||||
// GetAuthToken implements the interface pbs.AuthTokenServiceServer.
|
||||
func (s Service) GetAuthToken(ctx context.Context, req *pbs.GetAuthTokenRequest) (*pbs.GetAuthTokenResponse, error) {
|
||||
const op = "authtokens.(Service).GetAuthToken"
|
||||
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -128,22 +151,47 @@ func (s Service) GetAuthToken(ctx context.Context, req *pbs.GetAuthTokenRequest)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.getFromRepo(ctx, req.GetId())
|
||||
at, err := s.getFromRepo(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authzdActions := authResults.FetchActionSetForId(ctx, u.Id, IdActions)
|
||||
var outputFields perms.OutputFieldsMap
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, at.GetPublicId(), IdActions)
|
||||
|
||||
// Check to see if we need to verify Read vs. just ReadSelf
|
||||
if u.GetUserId() != authResults.UserId {
|
||||
if !authzdActions.HasAction(action.Read) {
|
||||
if at.GetIamUserId() != authResults.UserId {
|
||||
if !authorizedActions.HasAction(action.Read) {
|
||||
return nil, handlers.ForbiddenError()
|
||||
}
|
||||
outputFields = authResults.FetchOutputFields(perms.Resource{
|
||||
Id: at.GetPublicId(),
|
||||
ScopeId: at.GetScopeId(),
|
||||
Type: resource.AuthToken,
|
||||
}, action.Read).SelfOrDefaults(authResults.UserId)
|
||||
} else {
|
||||
var ok bool
|
||||
outputFields, ok = requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
}
|
||||
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.GetAuthTokenResponse{Item: u}, nil
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions.Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, at, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.GetAuthTokenResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// DeleteAuthToken implements the interface pbs.AuthTokenServiceServer.
|
||||
@@ -160,10 +208,12 @@ func (s Service) DeleteAuthToken(ctx context.Context, req *pbs.DeleteAuthTokenRe
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authzdActions := authResults.FetchActionSetForId(ctx, at.Id, IdActions)
|
||||
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, at.GetPublicId(), IdActions)
|
||||
|
||||
// Check to see if we need to verify Delete vs. just DeleteSelf
|
||||
if at.GetUserId() != authResults.UserId {
|
||||
if !authzdActions.HasAction(action.Delete) {
|
||||
if at.GetIamUserId() != authResults.UserId {
|
||||
if !authorizedActions.HasAction(action.Delete) {
|
||||
return nil, handlers.ForbiddenError()
|
||||
}
|
||||
}
|
||||
@@ -175,20 +225,20 @@ func (s Service) DeleteAuthToken(ctx context.Context, req *pbs.DeleteAuthTokenRe
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.AuthToken, error) {
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*authtoken.AuthToken, error) {
|
||||
const op = "authtokens.(Service).getFromRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
u, err := repo.LookupAuthToken(ctx, id)
|
||||
at, err := repo.LookupAuthToken(ctx, id)
|
||||
if err != nil && !errors.IsNotFoundError(err) {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
if u == nil {
|
||||
if at == nil {
|
||||
return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("AuthToken %q not found", id))
|
||||
}
|
||||
return toProto(u), nil
|
||||
return at, nil
|
||||
}
|
||||
|
||||
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
@@ -207,7 +257,7 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.AuthToken, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*authtoken.AuthToken, error) {
|
||||
repo, err := s.repoFn()
|
||||
_ = repo
|
||||
if err != nil {
|
||||
@@ -217,11 +267,7 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Aut
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var outUl []*pb.AuthToken
|
||||
for _, u := range ul {
|
||||
outUl = append(outUl, toProto(u))
|
||||
}
|
||||
return outUl, nil
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
|
||||
@@ -268,19 +314,49 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
|
||||
return auth.Verify(ctx, opts...)
|
||||
}
|
||||
|
||||
func toProto(in *authtoken.AuthToken) *pb.AuthToken {
|
||||
out := pb.AuthToken{
|
||||
Id: in.GetPublicId(),
|
||||
ScopeId: in.GetScopeId(),
|
||||
CreatedTime: in.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
|
||||
ApproximateLastUsedTime: in.GetApproximateLastAccessTime().GetTimestamp(),
|
||||
ExpirationTime: in.GetExpirationTime().GetTimestamp(),
|
||||
UserId: in.GetIamUserId(),
|
||||
AuthMethodId: in.GetAuthMethodId(),
|
||||
AccountId: in.GetAuthAccountId(),
|
||||
func toProto(ctx context.Context, in *authtoken.AuthToken, opt ...handlers.Option) (*pb.AuthToken, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building auth token proto")
|
||||
}
|
||||
return &out
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.AuthToken{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.GetPublicId()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeIdField) {
|
||||
out.ScopeId = in.GetScopeId()
|
||||
}
|
||||
if outputFields.Has(globals.UserIdField) {
|
||||
out.UserId = in.GetIamUserId()
|
||||
}
|
||||
if outputFields.Has(globals.AuthMethodIdField) {
|
||||
out.AuthMethodId = in.GetAuthMethodId()
|
||||
}
|
||||
if outputFields.Has(globals.AccountIdField) {
|
||||
out.AccountId = in.GetAuthAccountId()
|
||||
}
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.GetCreateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.UpdatedTimeField) {
|
||||
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.ApproximateLastUsedTimeField) {
|
||||
out.ApproximateLastUsedTime = in.GetApproximateLastAccessTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.ExpirationTimeField) {
|
||||
out.ExpirationTime = in.GetExpirationTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
out.AuthorizedActions = opts.WithAuthorizedActions
|
||||
}
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers/authtokens"
|
||||
@@ -97,6 +98,7 @@ func TestGetSelf(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := auth.NewVerifierContext(context.Background(), logger, iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo)
|
||||
ctx = context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{})
|
||||
got, err := a.GetAuthToken(ctx, &pbs.GetAuthTokenRequest{Id: tc.readId})
|
||||
if tc.err != nil {
|
||||
require.EqualError(err, tc.err.Error())
|
||||
@@ -105,9 +107,9 @@ func TestGetSelf(t *testing.T) {
|
||||
}
|
||||
require.NoError(err)
|
||||
require.NotNil(got)
|
||||
assert.Equal(got.GetItem().GetId(), tc.token.GetPublicId())
|
||||
assert.Equal(tc.token.GetPublicId(), got.GetItem().GetId())
|
||||
// Ensure we didn't simply have e.g. read on all tokens
|
||||
assert.Equal(got.Item.GetAuthorizedActions(), []string{"read:self", "delete:self"})
|
||||
assert.Equal([]string{"read:self", "delete:self"}, got.Item.GetAuthorizedActions())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -384,16 +386,30 @@ func TestList(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s, err := authtokens.NewService(repoFn, iamRepoFn)
|
||||
require.NoError(t, err, "Couldn't create new user service.")
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
require.NoError(err, "Couldn't create new user service.")
|
||||
|
||||
got, gErr := s.ListAuthTokens(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req)
|
||||
// Check non-anon listing
|
||||
got, gErr := s.ListAuthTokens(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId("u_auth")), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(t, gErr)
|
||||
assert.True(t, errors.Is(gErr, tc.err), "ListAuthTokens() with scope %q got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListAuthTokens() with scope %q got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, gErr)
|
||||
require.NoError(gErr)
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "ListAuthTokens() with scope %q got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
|
||||
|
||||
// Now check anon listing
|
||||
got, gErr = s.ListAuthTokens(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, item := range got.GetItems() {
|
||||
require.Nil(item.CreatedTime)
|
||||
require.Nil(item.ExpirationTime)
|
||||
require.Nil(item.UpdatedTime)
|
||||
require.Nil(item.ApproximateLastUsedTime)
|
||||
}
|
||||
assert.Empty(t, cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "ListAuthTokens() with scope %q got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/groups"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/iam/store"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
@@ -102,21 +104,41 @@ func (s Service) ListGroups(ctx context.Context, req *pbs.ListGroupsRequest) (*p
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(gl) == 0 {
|
||||
return &pbs.ListGroupsResponse{}, nil
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Group, 0, len(gl))
|
||||
res := &perms.Resource{
|
||||
res := perms.Resource{
|
||||
Type: resource.Group,
|
||||
}
|
||||
for _, item := range gl {
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
res.Id = item.GetPublicId()
|
||||
res.ScopeId = item.GetScopeId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.GetScopeId()]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, item, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
@@ -126,6 +148,8 @@ func (s Service) ListGroups(ctx context.Context, req *pbs.ListGroupsRequest) (*p
|
||||
|
||||
// GetGroups implements the interface pbs.GroupServiceServer.
|
||||
func (s Service) GetGroup(ctx context.Context, req *pbs.GetGroupRequest) (*pbs.GetGroupResponse, error) {
|
||||
const op = "groups.(Service).GetGroup"
|
||||
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,17 +157,37 @@ func (s Service) GetGroup(ctx context.Context, req *pbs.GetGroupRequest) (*pbs.G
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.getFromRepo(ctx, req.GetId())
|
||||
u, members, err := s.getFromRepo(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.GetGroupResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, u.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, u, members, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.GetGroupResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// CreateGroup implements the interface pbs.GroupServiceServer.
|
||||
func (s Service) CreateGroup(ctx context.Context, req *pbs.CreateGroupRequest) (*pbs.CreateGroupResponse, error) {
|
||||
const op = "groups.(Service).CreateGroup"
|
||||
|
||||
if err := validateCreateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -155,13 +199,33 @@ func (s Service) CreateGroup(ctx context.Context, req *pbs.CreateGroupRequest) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.CreateGroupResponse{Item: u, Uri: fmt.Sprintf("groups/%s", u.GetId())}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, u.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, u, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.CreateGroupResponse{Item: item, Uri: fmt.Sprintf("groups/%s", item.GetId())}, nil
|
||||
}
|
||||
|
||||
// UpdateGroup implements the interface pbs.GroupServiceServer.
|
||||
func (s Service) UpdateGroup(ctx context.Context, req *pbs.UpdateGroupRequest) (*pbs.UpdateGroupResponse, error) {
|
||||
const op = "groups.(Service).UpdateGroup"
|
||||
|
||||
if err := validateUpdateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -169,13 +233,31 @@ func (s Service) UpdateGroup(ctx context.Context, req *pbs.UpdateGroupRequest) (
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
u, members, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.UpdateGroupResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, u.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, u, members, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.UpdateGroupResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// DeleteGroup implements the interface pbs.GroupServiceServer.
|
||||
@@ -196,6 +278,8 @@ func (s Service) DeleteGroup(ctx context.Context, req *pbs.DeleteGroupRequest) (
|
||||
|
||||
// AddGroupMembers implements the interface pbs.GroupServiceServer.
|
||||
func (s Service) AddGroupMembers(ctx context.Context, req *pbs.AddGroupMembersRequest) (*pbs.AddGroupMembersResponse, error) {
|
||||
const op = "groups.(Service).AddGroupMembers"
|
||||
|
||||
if err := validateAddGroupMembersRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -203,17 +287,37 @@ func (s Service) AddGroupMembers(ctx context.Context, req *pbs.AddGroupMembersRe
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
g, err := s.addMembersInRepo(ctx, req.GetId(), req.GetMemberIds(), req.GetVersion())
|
||||
g, members, err := s.addMembersInRepo(ctx, req.GetId(), req.GetMemberIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.Scope = authResults.Scope
|
||||
g.AuthorizedActions = authResults.FetchActionSetForId(ctx, g.Id, IdActions).Strings()
|
||||
return &pbs.AddGroupMembersResponse{Item: g}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, g.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, g, members, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.AddGroupMembersResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// SetGroupMembers implements the interface pbs.GroupServiceServer.
|
||||
func (s Service) SetGroupMembers(ctx context.Context, req *pbs.SetGroupMembersRequest) (*pbs.SetGroupMembersResponse, error) {
|
||||
const op = "groups.(Service).SetGroupMembers"
|
||||
|
||||
if err := validateSetGroupMembersRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -221,17 +325,37 @@ func (s Service) SetGroupMembers(ctx context.Context, req *pbs.SetGroupMembersRe
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
g, err := s.setMembersInRepo(ctx, req.GetId(), req.GetMemberIds(), req.GetVersion())
|
||||
g, members, err := s.setMembersInRepo(ctx, req.GetId(), req.GetMemberIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.Scope = authResults.Scope
|
||||
g.AuthorizedActions = authResults.FetchActionSetForId(ctx, g.Id, IdActions).Strings()
|
||||
return &pbs.SetGroupMembersResponse{Item: g}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, g.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, g, members, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.SetGroupMembersResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// RemoveGroupMembers implements the interface pbs.GroupServiceServer.
|
||||
func (s Service) RemoveGroupMembers(ctx context.Context, req *pbs.RemoveGroupMembersRequest) (*pbs.RemoveGroupMembersResponse, error) {
|
||||
const op = "groups.(Service).RemoveGroupMembers"
|
||||
|
||||
if err := validateRemoveGroupMembersRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -239,32 +363,50 @@ func (s Service) RemoveGroupMembers(ctx context.Context, req *pbs.RemoveGroupMem
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
g, err := s.removeMembersInRepo(ctx, req.GetId(), req.GetMemberIds(), req.GetVersion())
|
||||
g, members, err := s.removeMembersInRepo(ctx, req.GetId(), req.GetMemberIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.Scope = authResults.Scope
|
||||
g.AuthorizedActions = authResults.FetchActionSetForId(ctx, g.Id, IdActions).Strings()
|
||||
return &pbs.RemoveGroupMembersResponse{Item: g}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, g.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, g, members, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.RemoveGroupMembersResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Group, error) {
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*iam.Group, []*iam.GroupMember, error) {
|
||||
const op = "groups.(Service).getFromRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
g, m, err := repo.LookupGroup(ctx, id)
|
||||
if err != nil && !errors.IsNotFoundError(err) {
|
||||
return nil, errors.Wrap(err, op)
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
if g == nil {
|
||||
return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("group %q not found", id))
|
||||
return nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("group %q not found", id))
|
||||
}
|
||||
return toProto(g, m), nil
|
||||
return g, m, err
|
||||
}
|
||||
|
||||
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Group) (*pb.Group, error) {
|
||||
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Group) (*iam.Group, error) {
|
||||
const op = "groups.(Service).createInRepo"
|
||||
var opts []iam.Option
|
||||
if item.GetName() != nil {
|
||||
@@ -288,10 +430,10 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Grou
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create group but no error returned from repository.")
|
||||
}
|
||||
return toProto(out, nil), nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Group) (*pb.Group, error) {
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Group) (*iam.Group, []*iam.GroupMember, error) {
|
||||
const op = "groups.(Service).updateInRepo"
|
||||
var opts []iam.Option
|
||||
if desc := item.GetDescription(); desc != nil {
|
||||
@@ -303,25 +445,25 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st
|
||||
version := item.GetVersion()
|
||||
g, err := iam.NewGroup(scopeId, opts...)
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build group for update: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build group for update: %v.", err)
|
||||
}
|
||||
g.PublicId = id
|
||||
dbMask := maskManager.Translate(mask)
|
||||
if len(dbMask) == 0 {
|
||||
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
|
||||
return nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
|
||||
}
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
out, m, rowsUpdated, err := repo.UpdateGroup(ctx, g, version, dbMask)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
if rowsUpdated == 0 {
|
||||
return nil, handlers.NotFoundErrorf("Group %q doesn't exist or incorrect version provided.", id)
|
||||
return nil, nil, handlers.NotFoundErrorf("Group %q doesn't exist or incorrect version provided.", id)
|
||||
}
|
||||
return toProto(out, m), nil
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
@@ -340,7 +482,7 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Group, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*iam.Group, error) {
|
||||
const op = "groups.(Service).listFromRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
@@ -350,74 +492,70 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Gro
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
var outGl []*pb.Group
|
||||
for _, g := range gl {
|
||||
outGl = append(outGl, toProto(g, nil))
|
||||
}
|
||||
return outGl, nil
|
||||
return gl, nil
|
||||
}
|
||||
|
||||
func (s Service) addMembersInRepo(ctx context.Context, groupId string, userIds []string, version uint32) (*pb.Group, error) {
|
||||
func (s Service) addMembersInRepo(ctx context.Context, groupId string, userIds []string, version uint32) (*iam.Group, []*iam.GroupMember, error) {
|
||||
const op = "groups.(Service).addMembersInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
_, err = repo.AddGroupMembers(ctx, groupId, version, strutil.RemoveDuplicates(userIds, false))
|
||||
if err != nil {
|
||||
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add members to group: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add members to group: %v.", err)
|
||||
}
|
||||
out, m, err := repo.LookupGroup(ctx, groupId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up group after adding memebers"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up group after adding memebers"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup group after adding member to it.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup group after adding member to it.")
|
||||
}
|
||||
return toProto(out, m), nil
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) setMembersInRepo(ctx context.Context, groupId string, userIds []string, version uint32) (*pb.Group, error) {
|
||||
func (s Service) setMembersInRepo(ctx context.Context, groupId string, userIds []string, version uint32) (*iam.Group, []*iam.GroupMember, error) {
|
||||
const op = "groups.(Service).setMembersInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
_, _, err = repo.SetGroupMembers(ctx, groupId, version, strutil.RemoveDuplicates(userIds, false))
|
||||
if err != nil {
|
||||
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set members on group: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set members on group: %v.", err)
|
||||
}
|
||||
out, m, err := repo.LookupGroup(ctx, groupId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up group after setting members"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up group after setting members"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup group after setting members for it.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup group after setting members for it.")
|
||||
}
|
||||
return toProto(out, m), nil
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) removeMembersInRepo(ctx context.Context, groupId string, userIds []string, version uint32) (*pb.Group, error) {
|
||||
func (s Service) removeMembersInRepo(ctx context.Context, groupId string, userIds []string, version uint32) (*iam.Group, []*iam.GroupMember, error) {
|
||||
const op = "groups.(Service).removeMembersInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
_, err = repo.DeleteGroupMembers(ctx, groupId, version, strutil.RemoveDuplicates(userIds, false))
|
||||
if err != nil {
|
||||
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove members from group: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove members from group: %v.", err)
|
||||
}
|
||||
out, m, err := repo.LookupGroup(ctx, groupId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up group after removing members"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up group after removing members"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup group after removing members from it.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup group after removing members from it.")
|
||||
}
|
||||
return toProto(out, m), nil
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
|
||||
@@ -459,28 +597,55 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
|
||||
return auth.Verify(ctx, opts...)
|
||||
}
|
||||
|
||||
func toProto(in *iam.Group, members []*iam.GroupMember) *pb.Group {
|
||||
out := pb.Group{
|
||||
Id: in.GetPublicId(),
|
||||
ScopeId: in.GetScopeId(),
|
||||
CreatedTime: in.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
|
||||
Version: in.Version,
|
||||
func toProto(ctx context.Context, in *iam.Group, members []*iam.GroupMember, opt ...handlers.Option) (*pb.Group, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building group proto")
|
||||
}
|
||||
if in.GetDescription() != "" {
|
||||
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.Group{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.GetPublicId()
|
||||
}
|
||||
if in.GetName() != "" {
|
||||
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
|
||||
if outputFields.Has(globals.ScopeIdField) {
|
||||
out.ScopeId = in.GetScopeId()
|
||||
}
|
||||
for _, m := range members {
|
||||
out.MemberIds = append(out.MemberIds, m.GetMemberId())
|
||||
out.Members = append(out.Members, &pb.Member{
|
||||
Id: m.GetMemberId(),
|
||||
ScopeId: m.GetMemberScopeId(),
|
||||
})
|
||||
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
|
||||
out.Description = wrapperspb.String(in.GetDescription())
|
||||
}
|
||||
return &out
|
||||
if outputFields.Has(globals.NameField) && in.GetName() != "" {
|
||||
out.Name = wrapperspb.String(in.GetName())
|
||||
}
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.GetCreateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.UpdatedTimeField) {
|
||||
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.VersionField) {
|
||||
out.Version = in.GetVersion()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
out.AuthorizedActions = opts.WithAuthorizedActions
|
||||
}
|
||||
if outputFields.Has(globals.MemberIdsField) {
|
||||
for _, m := range members {
|
||||
out.MemberIds = append(out.MemberIds, m.GetMemberId())
|
||||
}
|
||||
}
|
||||
if outputFields.Has(globals.MembersField) {
|
||||
for _, m := range members {
|
||||
out.Members = append(out.Members, &pb.Member{
|
||||
Id: m.GetMemberId(),
|
||||
ScopeId: m.GetMemberScopeId(),
|
||||
})
|
||||
}
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
|
||||
|
||||
@@ -319,12 +319,26 @@ func TestList(t *testing.T) {
|
||||
s, err := groups.NewService(repoFn)
|
||||
require.NoError(err, "Couldn't create new group service.")
|
||||
|
||||
// Test with a non-anon user
|
||||
got, gErr := s.ListGroups(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId()), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListGroups(%q) got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
|
||||
return
|
||||
}
|
||||
require.NoError(gErr)
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListGroups(%q) got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
|
||||
|
||||
// Test the anon case
|
||||
got, gErr = s.ListGroups(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, item := range got.GetItems() {
|
||||
require.Nil(item.CreatedTime)
|
||||
require.Nil(item.Members)
|
||||
require.Nil(item.UpdatedTime)
|
||||
require.Zero(item.Version)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hostcatalogs"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/host/static"
|
||||
"github.com/hashicorp/boundary/internal/host/static/store"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
@@ -20,9 +22,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/boundary/sdk/strutil"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
@@ -67,33 +67,6 @@ type Service struct {
|
||||
|
||||
var _ pbs.HostCatalogServiceServer = Service{}
|
||||
|
||||
func populateCollectionAuthorizedActions(ctx context.Context,
|
||||
authResults auth.VerifyResults,
|
||||
item *pb.HostCatalog) error {
|
||||
res := &perms.Resource{
|
||||
ScopeId: authResults.Scope.Id,
|
||||
Pin: item.Id,
|
||||
}
|
||||
// Range over the defined collections and check permissions against those
|
||||
// collections. We use the ID of this scope being returned, not its parent,
|
||||
// hence passing in a resource here.
|
||||
for k, v := range collectionTypeMap {
|
||||
res.Type = k
|
||||
acts := authResults.FetchActionSetForType(ctx, k, v, auth.WithResource(res)).Strings()
|
||||
if len(acts) > 0 {
|
||||
if item.AuthorizedCollectionActions == nil {
|
||||
item.AuthorizedCollectionActions = make(map[string]*structpb.ListValue)
|
||||
}
|
||||
lv, err := structpb.NewList(strutil.StringListToInterfaceList(acts))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
item.AuthorizedCollectionActions[k.String()+"s"] = lv
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewService returns a host catalog Service which handles host catalog related requests to boundary and uses the provided
|
||||
// repositories for storage and retrieval.
|
||||
func NewService(repoFn common.StaticRepoFactory, iamRepoFn common.IamRepoFactory) (Service, error) {
|
||||
@@ -138,27 +111,50 @@ func (s Service) ListHostCatalogs(ctx context.Context, req *pbs.ListHostCatalogs
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ul) == 0 {
|
||||
return &pbs.ListHostCatalogsResponse{}, nil
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.HostCatalog, 0, len(ul))
|
||||
res := &perms.Resource{
|
||||
res := perms.Resource{
|
||||
Type: resource.HostCatalog,
|
||||
}
|
||||
for _, item := range ul {
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
res.Id = item.GetPublicId()
|
||||
res.ScopeId = item.GetScopeId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, item); err != nil {
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.GetScopeId()]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, item.GetPublicId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, item, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
return &pbs.ListHostCatalogsResponse{Items: finalItems}, nil
|
||||
@@ -166,6 +162,8 @@ func (s Service) ListHostCatalogs(ctx context.Context, req *pbs.ListHostCatalogs
|
||||
|
||||
// GetHostCatalog implements the interface pbs.HostCatalogServiceServer.
|
||||
func (s Service) GetHostCatalog(ctx context.Context, req *pbs.GetHostCatalogRequest) (*pbs.GetHostCatalogResponse, error) {
|
||||
const op = "host_catalogs.(Service).GetostCatalog"
|
||||
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -177,16 +175,40 @@ func (s Service) GetHostCatalog(ctx context.Context, req *pbs.GetHostCatalogRequ
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hc.Scope = authResults.Scope
|
||||
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, hc); err != nil {
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hc.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, hc.GetPublicId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, hc, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbs.GetHostCatalogResponse{Item: hc}, nil
|
||||
|
||||
return &pbs.GetHostCatalogResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// CreateHostCatalog implements the interface pbs.HostCatalogServiceServer.
|
||||
func (s Service) CreateHostCatalog(ctx context.Context, req *pbs.CreateHostCatalogRequest) (*pbs.CreateHostCatalogResponse, error) {
|
||||
const op = "host_catalogs.(Service).CreateHostCatalog"
|
||||
|
||||
if err := validateCreateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -198,19 +220,43 @@ func (s Service) CreateHostCatalog(ctx context.Context, req *pbs.CreateHostCatal
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hc.Scope = authResults.Scope
|
||||
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, hc); err != nil {
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hc.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, hc.GetPublicId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, hc, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.CreateHostCatalogResponse{
|
||||
Item: hc,
|
||||
Uri: fmt.Sprintf("host-catalogs/%s", hc.GetId()),
|
||||
Item: item,
|
||||
Uri: fmt.Sprintf("host-catalogs/%s", item.GetId()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateHostCatalog implements the interface pbs.HostCatalogServiceServer.
|
||||
func (s Service) UpdateHostCatalog(ctx context.Context, req *pbs.UpdateHostCatalogRequest) (*pbs.UpdateHostCatalogResponse, error) {
|
||||
const op = "host_catalogs.(Service).UpdateHostCatalog"
|
||||
|
||||
if err := validateUpdateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -222,12 +268,34 @@ func (s Service) UpdateHostCatalog(ctx context.Context, req *pbs.UpdateHostCatal
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hc.Scope = authResults.Scope
|
||||
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, hc); err != nil {
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hc.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, hc.GetPublicId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, hc, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbs.UpdateHostCatalogResponse{Item: hc}, nil
|
||||
|
||||
return &pbs.UpdateHostCatalogResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// DeleteHostCatalog implements the interface pbs.HostCatalogServiceServer.
|
||||
@@ -246,7 +314,7 @@ func (s Service) DeleteHostCatalog(ctx context.Context, req *pbs.DeleteHostCatal
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.HostCatalog, error) {
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*static.HostCatalog, error) {
|
||||
repo, err := s.staticRepoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -258,10 +326,10 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*pb.HostCatalog, e
|
||||
if hc == nil {
|
||||
return nil, handlers.NotFoundErrorf("Host Catalog %q doesn't exist.", id)
|
||||
}
|
||||
return toProto(hc), nil
|
||||
return hc, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.HostCatalog, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*static.HostCatalog, error) {
|
||||
repo, err := s.staticRepoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -270,14 +338,10 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Hos
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var outUl []*pb.HostCatalog
|
||||
for _, u := range ul {
|
||||
outUl = append(outUl, toProto(u))
|
||||
}
|
||||
return outUl, nil
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func (s Service) createInRepo(ctx context.Context, projId string, item *pb.HostCatalog) (*pb.HostCatalog, error) {
|
||||
func (s Service) createInRepo(ctx context.Context, projId string, item *pb.HostCatalog) (*static.HostCatalog, error) {
|
||||
const op = "host_catalogs.(Servivce).createInRepo"
|
||||
var opts []static.Option
|
||||
if item.GetName() != nil {
|
||||
@@ -301,11 +365,11 @@ func (s Service) createInRepo(ctx context.Context, projId string, item *pb.HostC
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create host catalog but no error returned from repository.")
|
||||
}
|
||||
return toProto(out), nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []string, item *pb.HostCatalog) (*pb.HostCatalog, error) {
|
||||
const op = "service.(Service).updateInRepo"
|
||||
func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []string, item *pb.HostCatalog) (*static.HostCatalog, error) {
|
||||
const op = "host_catalogs.(Service).updateInRepo"
|
||||
var opts []static.Option
|
||||
if desc := item.GetDescription(); desc != nil {
|
||||
opts = append(opts, static.WithDescription(desc.GetValue()))
|
||||
@@ -334,11 +398,11 @@ func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []str
|
||||
if rowsUpdated == 0 {
|
||||
return nil, handlers.NotFoundErrorf("Host Catalog %q doesn't exist or incorrect version provided.", id)
|
||||
}
|
||||
return toProto(out), nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
const op = "service.(Service).deleteFromRepo"
|
||||
const op = "host_catalogs.(Service).deleteFromRepo"
|
||||
repo, err := s.staticRepoFn()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, op)
|
||||
@@ -394,22 +458,48 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
|
||||
return auth.Verify(ctx, opts...)
|
||||
}
|
||||
|
||||
func toProto(in *static.HostCatalog) *pb.HostCatalog {
|
||||
out := pb.HostCatalog{
|
||||
Id: in.GetPublicId(),
|
||||
ScopeId: in.GetScopeId(),
|
||||
CreatedTime: in.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
|
||||
Version: in.GetVersion(),
|
||||
Type: host.StaticSubtype.String(),
|
||||
func toProto(ctx context.Context, in *static.HostCatalog, opt ...handlers.Option) (*pb.HostCatalog, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building auth method proto")
|
||||
}
|
||||
if in.GetDescription() != "" {
|
||||
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.HostCatalog{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.GetPublicId()
|
||||
}
|
||||
if in.GetName() != "" {
|
||||
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
|
||||
if outputFields.Has(globals.ScopeIdField) {
|
||||
out.ScopeId = in.GetScopeId()
|
||||
}
|
||||
return &out
|
||||
if outputFields.Has(globals.TypeField) {
|
||||
out.Type = host.StaticSubtype.String()
|
||||
}
|
||||
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
|
||||
out.Description = wrapperspb.String(in.GetDescription())
|
||||
}
|
||||
if outputFields.Has(globals.NameField) && in.GetName() != "" {
|
||||
out.Name = wrapperspb.String(in.GetName())
|
||||
}
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.GetCreateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.UpdatedTimeField) {
|
||||
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.VersionField) {
|
||||
out.Version = in.GetVersion()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
out.AuthorizedActions = opts.WithAuthorizedActions
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
out.AuthorizedCollectionActions = opts.WithAuthorizedCollectionActions
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
|
||||
|
||||
@@ -259,12 +259,24 @@ func TestList(t *testing.T) {
|
||||
s, err := host_catalogs.NewService(repoFn, iamRepoFn)
|
||||
require.NoError(err, "Couldn't create new auth_method service.")
|
||||
|
||||
// Test with non-anon user
|
||||
got, gErr := s.ListHostCatalogs(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListHostCatalogs() for scope %q got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
|
||||
return
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHostCatalogs() for scope %q got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
|
||||
|
||||
// Test with anon user
|
||||
got, gErr = s.ListHostCatalogs(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId("u_anon")), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, item := range got.GetItems() {
|
||||
require.Nil(item.CreatedTime)
|
||||
require.Nil(item.UpdatedTime)
|
||||
require.Zero(item.Version)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hostsets"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/host/static"
|
||||
"github.com/hashicorp/boundary/internal/host/static/store"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
@@ -81,22 +83,43 @@ func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(hl) == 0 {
|
||||
return &pbs.ListHostSetsResponse{}, nil
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.HostSet, 0, len(hl))
|
||||
res := &perms.Resource{
|
||||
|
||||
res := perms.Resource{
|
||||
ScopeId: authResults.Scope.Id,
|
||||
Type: resource.HostSet,
|
||||
Pin: req.GetHostCatalogId(),
|
||||
}
|
||||
for _, item := range hl {
|
||||
item.Scope = authResults.Scope
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
res.Id = item.GetPublicId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, item, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
@@ -106,6 +129,8 @@ func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest)
|
||||
|
||||
// GetHostSet implements the interface pbs.HostSetServiceServer.
|
||||
func (s Service) GetHostSet(ctx context.Context, req *pbs.GetHostSetRequest) (*pbs.GetHostSetResponse, error) {
|
||||
const op = "host_sets.(Service).GetHostSet"
|
||||
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -113,17 +138,37 @@ func (s Service) GetHostSet(ctx context.Context, req *pbs.GetHostSetRequest) (*p
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
hc, err := s.getFromRepo(ctx, req.GetId())
|
||||
hs, hosts, err := s.getFromRepo(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hc.Scope = authResults.Scope
|
||||
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
|
||||
return &pbs.GetHostSetResponse{Item: hc}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, hs, hosts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.GetHostSetResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// CreateHostSet implements the interface pbs.HostSetServiceServer.
|
||||
func (s Service) CreateHostSet(ctx context.Context, req *pbs.CreateHostSetRequest) (*pbs.CreateHostSetResponse, error) {
|
||||
const op = "host_sets.(Service).CreateHostSet"
|
||||
|
||||
if err := validateCreateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -131,20 +176,40 @@ func (s Service) CreateHostSet(ctx context.Context, req *pbs.CreateHostSetReques
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
h, err := s.createInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetItem())
|
||||
hs, err := s.createInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetItem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h.Scope = authResults.Scope
|
||||
h.AuthorizedActions = authResults.FetchActionSetForId(ctx, h.Id, IdActions).Strings()
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, hs, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.CreateHostSetResponse{
|
||||
Item: h,
|
||||
Uri: fmt.Sprintf("host-sets/%s", h.GetId()),
|
||||
Item: item,
|
||||
Uri: fmt.Sprintf("host-sets/%s", item.GetId()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateHostSet implements the interface pbs.HostSetServiceServer.
|
||||
func (s Service) UpdateHostSet(ctx context.Context, req *pbs.UpdateHostSetRequest) (*pbs.UpdateHostSetResponse, error) {
|
||||
const op = "host_sets.(Service).UpdateHostSet"
|
||||
|
||||
if err := validateUpdateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -152,13 +217,31 @@ func (s Service) UpdateHostSet(ctx context.Context, req *pbs.UpdateHostSetReques
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
hc, err := s.updateInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
hs, hosts, err := s.updateInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hc.Scope = authResults.Scope
|
||||
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
|
||||
return &pbs.UpdateHostSetResponse{Item: hc}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, hs, hosts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.UpdateHostSetResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// DeleteHostSet implements the interface pbs.HostSetServiceServer.
|
||||
@@ -179,6 +262,8 @@ func (s Service) DeleteHostSet(ctx context.Context, req *pbs.DeleteHostSetReques
|
||||
|
||||
// AddHostSetHosts implements the interface pbs.HostSetServiceServer.
|
||||
func (s Service) AddHostSetHosts(ctx context.Context, req *pbs.AddHostSetHostsRequest) (*pbs.AddHostSetHostsResponse, error) {
|
||||
const op = "host_sets.(Service).AddHostSetHosts"
|
||||
|
||||
if err := validateAddRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -186,17 +271,37 @@ func (s Service) AddHostSetHosts(ctx context.Context, req *pbs.AddHostSetHostsRe
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
g, err := s.addInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
|
||||
hs, hosts, err := s.addInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.Scope = authResults.Scope
|
||||
g.AuthorizedActions = authResults.FetchActionSetForId(ctx, g.Id, IdActions).Strings()
|
||||
return &pbs.AddHostSetHostsResponse{Item: g}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, hs, hosts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.AddHostSetHostsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// SetHostSetHosts implements the interface pbs.HostSetServiceServer.
|
||||
func (s Service) SetHostSetHosts(ctx context.Context, req *pbs.SetHostSetHostsRequest) (*pbs.SetHostSetHostsResponse, error) {
|
||||
const op = "host_sets.(Service).SetHostSetHosts"
|
||||
|
||||
if err := validateSetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -204,17 +309,36 @@ func (s Service) SetHostSetHosts(ctx context.Context, req *pbs.SetHostSetHostsRe
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
g, err := s.setInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
|
||||
hs, hosts, err := s.setInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.Scope = authResults.Scope
|
||||
g.AuthorizedActions = authResults.FetchActionSetForId(ctx, g.Id, IdActions).Strings()
|
||||
return &pbs.SetHostSetHostsResponse{Item: g}, nil
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, hs, hosts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.SetHostSetHostsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// RemoveHostSetHosts implements the interface pbs.HostSetServiceServer.
|
||||
func (s Service) RemoveHostSetHosts(ctx context.Context, req *pbs.RemoveHostSetHostsRequest) (*pbs.RemoveHostSetHostsResponse, error) {
|
||||
const op = "host_sets.(Service).RemoveHostSetHosts"
|
||||
|
||||
if err := validateRemoveRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -222,31 +346,49 @@ func (s Service) RemoveHostSetHosts(ctx context.Context, req *pbs.RemoveHostSetH
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
g, err := s.removeInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
|
||||
hs, hosts, err := s.removeInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.Scope = authResults.Scope
|
||||
g.AuthorizedActions = authResults.FetchActionSetForId(ctx, g.Id, IdActions).Strings()
|
||||
return &pbs.RemoveHostSetHostsResponse{Item: g}, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.HostSet, error) {
|
||||
repo, err := s.staticRepoFn()
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, hs, hosts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.RemoveHostSetHostsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*static.HostSet, []*static.Host, error) {
|
||||
repo, err := s.staticRepoFn()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
h, m, err := repo.LookupSet(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if h == nil {
|
||||
return nil, handlers.NotFoundErrorf("Host Set %q doesn't exist.", id)
|
||||
return nil, nil, handlers.NotFoundErrorf("Host Set %q doesn't exist.", id)
|
||||
}
|
||||
return toProto(h, m), nil
|
||||
return h, m, nil
|
||||
}
|
||||
|
||||
func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, item *pb.HostSet) (*pb.HostSet, error) {
|
||||
func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, item *pb.HostSet) (*static.HostSet, error) {
|
||||
const op = "host_sets.(Service).createInRepo"
|
||||
var opts []static.Option
|
||||
if item.GetName() != nil {
|
||||
@@ -270,10 +412,10 @@ func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, it
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create host set but no error returned from repository.")
|
||||
}
|
||||
return toProto(out, nil), nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string, mask []string, item *pb.HostSet) (*pb.HostSet, error) {
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string, mask []string, item *pb.HostSet) (*static.HostSet, []*static.Host, error) {
|
||||
const op = "host_sets.(Service).updateInRepo"
|
||||
var opts []static.Option
|
||||
if desc := item.GetDescription(); desc != nil {
|
||||
@@ -284,25 +426,25 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string
|
||||
}
|
||||
h, err := static.NewHostSet(catalogId, opts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("Unable to build host set for update"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("Unable to build host set for update"))
|
||||
}
|
||||
h.PublicId = id
|
||||
dbMask := maskManager.Translate(mask)
|
||||
if len(dbMask) == 0 {
|
||||
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
|
||||
return nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
|
||||
}
|
||||
repo, err := s.staticRepoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
out, m, rowsUpdated, err := repo.UpdateSet(ctx, scopeId, h, item.GetVersion(), dbMask)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to update host set"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to update host set"))
|
||||
}
|
||||
if rowsUpdated == 0 {
|
||||
return nil, handlers.NotFoundErrorf("Host Set %q doesn't exist or incorrect version provided.", id)
|
||||
return nil, nil, handlers.NotFoundErrorf("Host Set %q doesn't exist or incorrect version provided.", id)
|
||||
}
|
||||
return toProto(out, m), nil
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) {
|
||||
@@ -318,7 +460,7 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool,
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*pb.HostSet, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*static.HostSet, error) {
|
||||
repo, err := s.staticRepoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -327,72 +469,68 @@ func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*pb.Host
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var outH []*pb.HostSet
|
||||
for _, h := range hl {
|
||||
outH = append(outH, toProto(h, nil))
|
||||
}
|
||||
return outH, nil
|
||||
return hl, nil
|
||||
}
|
||||
|
||||
func (s Service) addInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*pb.HostSet, error) {
|
||||
func (s Service) addInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*static.HostSet, []*static.Host, error) {
|
||||
const op = "host_sets.(Service).addInRepo"
|
||||
repo, err := s.staticRepoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
_, err = repo.AddSetMembers(ctx, scopeId, setId, version, strutil.RemoveDuplicates(hostIds, false))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("Unable to add hosts to host set"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("Unable to add hosts to host set"))
|
||||
}
|
||||
out, m, err := repo.LookupSet(ctx, setId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up host set after adding hosts"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up host set after adding hosts"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after adding hosts to it.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after adding hosts to it.")
|
||||
}
|
||||
return toProto(out, m), nil
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) setInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*pb.HostSet, error) {
|
||||
func (s Service) setInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*static.HostSet, []*static.Host, error) {
|
||||
const op = "host_sets.(Service).setInRepo"
|
||||
repo, err := s.staticRepoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
_, _, err = repo.SetSetMembers(ctx, scopeId, setId, version, strutil.RemoveDuplicates(hostIds, false))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("Unable to set hosts in host set"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("Unable to set hosts in host set"))
|
||||
}
|
||||
|
||||
out, m, err := repo.LookupSet(ctx, setId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up host set after setting hosts"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up host set after setting hosts"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after setting hosts for it.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after setting hosts for it.")
|
||||
}
|
||||
return toProto(out, m), nil
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) removeInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*pb.HostSet, error) {
|
||||
func (s Service) removeInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*static.HostSet, []*static.Host, error) {
|
||||
const op = "host_sets.(Service).removeInRepo"
|
||||
repo, err := s.staticRepoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
_, err = repo.DeleteSetMembers(ctx, scopeId, setId, version, strutil.RemoveDuplicates(hostIds, false))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("Unable to remove hosts from host set"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("Unable to remove hosts from host set"))
|
||||
}
|
||||
out, m, err := repo.LookupSet(ctx, setId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up host set"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up host set"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after removing hosts from it.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after removing hosts from it.")
|
||||
}
|
||||
return toProto(out, m), nil
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Type) (*static.HostCatalog, auth.VerifyResults) {
|
||||
@@ -435,25 +573,50 @@ func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Ty
|
||||
return cat, auth.Verify(ctx, opts...)
|
||||
}
|
||||
|
||||
func toProto(in *static.HostSet, hs []*static.Host) *pb.HostSet {
|
||||
out := pb.HostSet{
|
||||
Id: in.GetPublicId(),
|
||||
HostCatalogId: in.GetCatalogId(),
|
||||
Type: host.StaticSubtype.String(),
|
||||
CreatedTime: in.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
|
||||
Version: in.GetVersion(),
|
||||
func toProto(ctx context.Context, in *static.HostSet, hosts []*static.Host, opt ...handlers.Option) (*pb.HostSet, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building hostset proto")
|
||||
}
|
||||
if in.GetDescription() != "" {
|
||||
out.Description = wrapperspb.String(in.GetDescription())
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.HostSet{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.GetPublicId()
|
||||
}
|
||||
if in.GetName() != "" {
|
||||
out.Name = wrapperspb.String(in.GetName())
|
||||
if outputFields.Has(globals.HostCatalogIdField) {
|
||||
out.HostCatalogId = in.GetCatalogId()
|
||||
}
|
||||
for _, h := range hs {
|
||||
out.HostIds = append(out.HostIds, h.GetPublicId())
|
||||
if outputFields.Has(globals.TypeField) {
|
||||
out.Type = host.StaticSubtype.String()
|
||||
}
|
||||
return &out
|
||||
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
|
||||
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
|
||||
}
|
||||
if outputFields.Has(globals.NameField) && in.GetName() != "" {
|
||||
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
|
||||
}
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.GetCreateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.UpdatedTimeField) {
|
||||
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.VersionField) {
|
||||
out.Version = in.GetVersion()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
out.AuthorizedActions = opts.WithAuthorizedActions
|
||||
}
|
||||
if outputFields.Has(globals.HostIdsField) {
|
||||
for _, h := range hosts {
|
||||
out.HostIds = append(out.HostIds, h.GetPublicId())
|
||||
}
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
|
||||
|
||||
@@ -194,14 +194,26 @@ func TestList(t *testing.T) {
|
||||
s, err := host_sets.NewService(repoFn)
|
||||
require.NoError(err, "Couldn't create new host set service.")
|
||||
|
||||
// Test with non-anon user
|
||||
got, gErr := s.ListHostSets(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListHostSets(%q) got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
} else {
|
||||
require.NoError(gErr)
|
||||
return
|
||||
}
|
||||
require.NoError(gErr)
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHostSets(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
|
||||
// Test with anon user
|
||||
got, gErr = s.ListHostSets(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, item := range got.GetItems() {
|
||||
require.Nil(item.CreatedTime)
|
||||
require.Nil(item.UpdatedTime)
|
||||
require.Zero(item.Version)
|
||||
require.Empty(item.HostIds)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hosts"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/host/static"
|
||||
"github.com/hashicorp/boundary/internal/host/static/store"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
@@ -79,22 +81,43 @@ func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(hl) == 0 {
|
||||
return &pbs.ListHostsResponse{}, nil
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Host, 0, len(hl))
|
||||
res := &perms.Resource{
|
||||
|
||||
res := perms.Resource{
|
||||
ScopeId: authResults.Scope.Id,
|
||||
Type: resource.Host,
|
||||
Pin: req.GetHostCatalogId(),
|
||||
}
|
||||
for _, item := range hl {
|
||||
item.Scope = authResults.Scope
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
res.Id = item.GetPublicId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, item, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
@@ -104,6 +127,8 @@ func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs
|
||||
|
||||
// GetHost implements the interface pbs.HostServiceServer.
|
||||
func (s Service) GetHost(ctx context.Context, req *pbs.GetHostRequest) (*pbs.GetHostResponse, error) {
|
||||
const op = "hosts.(Service).GetHost"
|
||||
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -111,17 +136,37 @@ func (s Service) GetHost(ctx context.Context, req *pbs.GetHostRequest) (*pbs.Get
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
hc, err := s.getFromRepo(ctx, req.GetId())
|
||||
h, err := s.getFromRepo(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hc.Scope = authResults.Scope
|
||||
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
|
||||
return &pbs.GetHostResponse{Item: hc}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, h.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, h, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.GetHostResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// CreateHost implements the interface pbs.HostServiceServer.
|
||||
func (s Service) CreateHost(ctx context.Context, req *pbs.CreateHostRequest) (*pbs.CreateHostResponse, error) {
|
||||
const op = "hosts.(Service).CreateHost"
|
||||
|
||||
if err := validateCreateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,16 +178,36 @@ func (s Service) CreateHost(ctx context.Context, req *pbs.CreateHostRequest) (*p
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h.Scope = authResults.Scope
|
||||
h.AuthorizedActions = authResults.FetchActionSetForId(ctx, h.Id, IdActions).Strings()
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, h.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, h, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.CreateHostResponse{
|
||||
Item: h,
|
||||
Uri: fmt.Sprintf("hosts/%s", h.GetId()),
|
||||
Item: item,
|
||||
Uri: fmt.Sprintf("hosts/%s", item.GetId()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateHost implements the interface pbs.HostServiceServer.
|
||||
func (s Service) UpdateHost(ctx context.Context, req *pbs.UpdateHostRequest) (*pbs.UpdateHostResponse, error) {
|
||||
const op = "hosts.(Service).UpdateHost"
|
||||
|
||||
if err := validateUpdateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -150,13 +215,31 @@ func (s Service) UpdateHost(ctx context.Context, req *pbs.UpdateHostRequest) (*p
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
hc, err := s.updateInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
h, err := s.updateInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hc.Scope = authResults.Scope
|
||||
hc.AuthorizedActions = authResults.FetchActionSetForId(ctx, hc.Id, IdActions).Strings()
|
||||
return &pbs.UpdateHostResponse{Item: hc}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, h.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, h, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.UpdateHostResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// DeleteHost implements the interface pbs.HostServiceServer.
|
||||
@@ -175,7 +258,7 @@ func (s Service) DeleteHost(ctx context.Context, req *pbs.DeleteHostRequest) (*p
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Host, error) {
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*static.Host, error) {
|
||||
repo, err := s.staticRepoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -187,10 +270,10 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Host, error) {
|
||||
if h == nil {
|
||||
return nil, handlers.NotFoundErrorf("Host %q doesn't exist.", id)
|
||||
}
|
||||
return toProto(h, nil)
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, item *pb.Host) (*pb.Host, error) {
|
||||
func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, item *pb.Host) (*static.Host, error) {
|
||||
const op = "hosts.(Service).createInRepo"
|
||||
ha := &pb.StaticHostAttributes{}
|
||||
if err := handlers.StructToProto(item.GetAttributes(), ha); err != nil {
|
||||
@@ -222,10 +305,10 @@ func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, it
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create host but no error returned from repository.")
|
||||
}
|
||||
return toProto(out, nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string, mask []string, item *pb.Host) (*pb.Host, error) {
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string, mask []string, item *pb.Host) (*static.Host, error) {
|
||||
const op = "hosts.(Service).updateInRepo"
|
||||
ha := &pb.StaticHostAttributes{}
|
||||
if err := handlers.StructToProto(item.GetAttributes(), ha); err != nil {
|
||||
@@ -261,7 +344,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string
|
||||
if rowsUpdated == 0 {
|
||||
return nil, handlers.NotFoundErrorf("Host %q doesn't exist or incorrect version provided.", id)
|
||||
}
|
||||
return toProto(out, nil)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) {
|
||||
@@ -277,7 +360,7 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool,
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*pb.Host, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*static.Host, error) {
|
||||
repo, err := s.staticRepoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -286,15 +369,7 @@ func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*pb.Host
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var outHl []*pb.Host
|
||||
for _, h := range hl {
|
||||
p, err := toProto(h, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outHl = append(outHl, p)
|
||||
}
|
||||
return outHl, nil
|
||||
return hl, nil
|
||||
}
|
||||
|
||||
func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Type) (*static.HostCatalog, auth.VerifyResults) {
|
||||
@@ -337,29 +412,56 @@ func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Ty
|
||||
return cat, auth.Verify(ctx, opts...)
|
||||
}
|
||||
|
||||
func toProto(in *static.Host, members []*static.HostSet) (*pb.Host, error) {
|
||||
out := pb.Host{
|
||||
Id: in.GetPublicId(),
|
||||
HostCatalogId: in.GetCatalogId(),
|
||||
Type: host.StaticSubtype.String(),
|
||||
CreatedTime: in.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
|
||||
Version: in.GetVersion(),
|
||||
func toProto(ctx context.Context, in *static.Host, hostSets []*static.HostSet, opt ...handlers.Option) (*pb.Host, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building host proto")
|
||||
}
|
||||
if in.GetDescription() != "" {
|
||||
out.Description = wrapperspb.String(in.GetDescription())
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.Host{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.GetPublicId()
|
||||
}
|
||||
if in.GetName() != "" {
|
||||
out.Name = wrapperspb.String(in.GetName())
|
||||
if outputFields.Has(globals.HostCatalogIdField) {
|
||||
out.HostCatalogId = in.GetCatalogId()
|
||||
}
|
||||
for _, m := range members {
|
||||
out.HostSetIds = append(out.HostSetIds, m.GetPublicId())
|
||||
if outputFields.Has(globals.TypeField) {
|
||||
out.Type = host.StaticSubtype.String()
|
||||
}
|
||||
st, err := handlers.ProtoToStruct(&pb.StaticHostAttributes{Address: wrapperspb.String(in.GetAddress())})
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to convert static attribute to struct: %s", err)
|
||||
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
|
||||
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
|
||||
}
|
||||
if outputFields.Has(globals.NameField) && in.GetName() != "" {
|
||||
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
|
||||
}
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.GetCreateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.UpdatedTimeField) {
|
||||
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.VersionField) {
|
||||
out.Version = in.GetVersion()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
out.AuthorizedActions = opts.WithAuthorizedActions
|
||||
}
|
||||
if outputFields.Has(globals.HostSetIdsField) {
|
||||
for _, hs := range hostSets {
|
||||
out.HostSetIds = append(out.HostSetIds, hs.GetPublicId())
|
||||
}
|
||||
}
|
||||
if outputFields.Has(globals.AttributesField) {
|
||||
st, err := handlers.ProtoToStruct(&pb.StaticHostAttributes{Address: wrapperspb.String(in.GetAddress())})
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to convert static attribute to struct: %s", err)
|
||||
}
|
||||
out.Attributes = st
|
||||
}
|
||||
out.Attributes = st
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -189,14 +189,25 @@ func TestList(t *testing.T) {
|
||||
s, err := hosts.NewService(repoFn)
|
||||
require.NoError(err, "Couldn't create new host set service.")
|
||||
|
||||
// Test non-anonymous listing
|
||||
got, gErr := s.ListHosts(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListHosts(%q) got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
} else {
|
||||
require.NoError(gErr)
|
||||
return
|
||||
}
|
||||
require.NoError(gErr)
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHosts(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
|
||||
// Test anonymous listing
|
||||
got, gErr = s.ListHosts(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, item := range got.GetItems() {
|
||||
require.Nil(item.CreatedTime)
|
||||
require.Nil(item.UpdatedTime)
|
||||
require.Zero(item.Version)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ package handlers
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Test_GetOpts provides unit tests for GetOpts and all the options
|
||||
@@ -36,14 +38,26 @@ func Test_GetOpts(t *testing.T) {
|
||||
opts = GetOpts(WithLogger(hclog.New(nil)))
|
||||
assert.NotNil(opts.WithLogger)
|
||||
})
|
||||
|
||||
t.Run("WithAnonymousListing", func(t *testing.T) {
|
||||
t.Run("WithUserIsAnonymous", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
opts := GetOpts()
|
||||
assert.False(opts.WithAnonymousListing)
|
||||
assert.False(opts.WithUserIsAnonymous)
|
||||
|
||||
opts = GetOpts(WithAnonymousListing(true))
|
||||
assert.True(opts.WithAnonymousListing)
|
||||
opts = GetOpts(WithUserIsAnonymous(true))
|
||||
assert.True(opts.WithUserIsAnonymous)
|
||||
})
|
||||
t.Run("WithOutputFields", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
opts := GetOpts()
|
||||
assert.Nil(opts.WithOutputFields)
|
||||
|
||||
var out perms.OutputFieldsMap
|
||||
|
||||
opts = GetOpts(WithOutputFields(&out))
|
||||
require.NotNil(opts.WithOutputFields)
|
||||
assert.Nil(*opts.WithOutputFields)
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
package handlers
|
||||
|
||||
import "github.com/hashicorp/go-hclog"
|
||||
import (
|
||||
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
// GetOpts - iterate the inbound Options and return a struct
|
||||
func GetOpts(opt ...Option) options {
|
||||
@@ -18,9 +23,13 @@ type Option func(*options)
|
||||
|
||||
// options = how options are represented
|
||||
type options struct {
|
||||
withDiscardUnknownFields bool
|
||||
WithLogger hclog.Logger
|
||||
WithAnonymousListing bool
|
||||
withDiscardUnknownFields bool
|
||||
WithLogger hclog.Logger
|
||||
WithUserIsAnonymous bool
|
||||
WithOutputFields *perms.OutputFieldsMap
|
||||
WithScope *scopes.ScopeInfo
|
||||
WithAuthorizedActions []string
|
||||
WithAuthorizedCollectionActions map[string]*structpb.ListValue
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -43,10 +52,42 @@ func WithLogger(logger hclog.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithAnonymousListing provides an option when creating responses to only include those
|
||||
// desired for listing to anonymous users.
|
||||
func WithAnonymousListing(anonListing bool) Option {
|
||||
// DEPRECATED: Superceded by WithOutputFields. Will be removed once all handlers
|
||||
// have been migrated to that.
|
||||
func WithUserIsAnonymous(anonListing bool) Option {
|
||||
return func(o *options) {
|
||||
o.WithAnonymousListing = anonListing
|
||||
o.WithUserIsAnonymous = anonListing
|
||||
}
|
||||
}
|
||||
|
||||
// WithOutputFields provides an option when creating responses to only include
|
||||
// specific fields
|
||||
func WithOutputFields(fields *perms.OutputFieldsMap) Option {
|
||||
return func(o *options) {
|
||||
o.WithOutputFields = fields
|
||||
}
|
||||
}
|
||||
|
||||
// WithScope provides an option when creating responses to include the given
|
||||
// scope if allowed
|
||||
func WithScope(scp *scopes.ScopeInfo) Option {
|
||||
return func(o *options) {
|
||||
o.WithScope = scp
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthorizedActions provides an option when creating responses to include the given
|
||||
// authorized actions if allowed
|
||||
func WithAuthorizedActions(acts []string) Option {
|
||||
return func(o *options) {
|
||||
o.WithAuthorizedActions = acts
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthorizedCollectionActions provides an option when creating responses to include the given
|
||||
// authorized collection actions if allowed
|
||||
func WithAuthorizedCollectionActions(colActs map[string]*structpb.ListValue) Option {
|
||||
return func(o *options) {
|
||||
o.WithAuthorizedCollectionActions = colActs
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/roles"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/iam/store"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
@@ -105,22 +107,41 @@ func (s Service) ListRoles(ctx context.Context, req *pbs.ListRolesRequest) (*pbs
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return &pbs.ListRolesResponse{}, nil
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Role, 0, len(items))
|
||||
res := &perms.Resource{
|
||||
res := perms.Resource{
|
||||
Type: resource.Role,
|
||||
}
|
||||
for _, item := range items {
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
res.Id = item.GetPublicId()
|
||||
res.ScopeId = item.GetScopeId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.GetScopeId()]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, item, nil, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
@@ -130,6 +151,8 @@ func (s Service) ListRoles(ctx context.Context, req *pbs.ListRolesRequest) (*pbs
|
||||
|
||||
// GetRoles implements the interface pbs.RoleServiceServer.
|
||||
func (s Service) GetRole(ctx context.Context, req *pbs.GetRoleRequest) (*pbs.GetRoleResponse, error) {
|
||||
const op = "roles.(Service).GetRole"
|
||||
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -137,17 +160,37 @@ func (s Service) GetRole(ctx context.Context, req *pbs.GetRoleRequest) (*pbs.Get
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.getFromRepo(ctx, req.GetId())
|
||||
r, prs, rgs, err := s.getFromRepo(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.GetRoleResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.GetRoleResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// CreateRole implements the interface pbs.RoleServiceServer.
|
||||
func (s Service) CreateRole(ctx context.Context, req *pbs.CreateRoleRequest) (*pbs.CreateRoleResponse, error) {
|
||||
const op = "roles.(Service).CreateRole"
|
||||
|
||||
if err := validateCreateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -159,13 +202,33 @@ func (s Service) CreateRole(ctx context.Context, req *pbs.CreateRoleRequest) (*p
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Scope = authResults.Scope
|
||||
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
|
||||
return &pbs.CreateRoleResponse{Item: r, Uri: fmt.Sprintf("roles/%s", r.GetId())}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, r, nil, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.CreateRoleResponse{Item: item, Uri: fmt.Sprintf("roles/%s", item.GetId())}, nil
|
||||
}
|
||||
|
||||
// UpdateRole implements the interface pbs.RoleServiceServer.
|
||||
func (s Service) UpdateRole(ctx context.Context, req *pbs.UpdateRoleRequest) (*pbs.UpdateRoleResponse, error) {
|
||||
const op = "roles.(Service).UpdateRole"
|
||||
|
||||
if err := validateUpdateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -173,13 +236,31 @@ func (s Service) UpdateRole(ctx context.Context, req *pbs.UpdateRoleRequest) (*p
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
r, prs, rgs, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.UpdateRoleResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.UpdateRoleResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// DeleteRole implements the interface pbs.RoleServiceServer.
|
||||
@@ -200,6 +281,8 @@ func (s Service) DeleteRole(ctx context.Context, req *pbs.DeleteRoleRequest) (*p
|
||||
|
||||
// AddRolePrincipals implements the interface pbs.RoleServiceServer.
|
||||
func (s Service) AddRolePrincipals(ctx context.Context, req *pbs.AddRolePrincipalsRequest) (*pbs.AddRolePrincipalsResponse, error) {
|
||||
const op = "roles.(Service).AddRolePrincipals"
|
||||
|
||||
if err := validateAddRolePrincipalsRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -207,17 +290,37 @@ func (s Service) AddRolePrincipals(ctx context.Context, req *pbs.AddRolePrincipa
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
r, err := s.addPrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion())
|
||||
r, prs, rgs, err := s.addPrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Scope = authResults.Scope
|
||||
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
|
||||
return &pbs.AddRolePrincipalsResponse{Item: r}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.AddRolePrincipalsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// SetRolePrincipals implements the interface pbs.RoleServiceServer.
|
||||
func (s Service) SetRolePrincipals(ctx context.Context, req *pbs.SetRolePrincipalsRequest) (*pbs.SetRolePrincipalsResponse, error) {
|
||||
const op = "roles.(Service).SetRolePrincipals"
|
||||
|
||||
if err := validateSetRolePrincipalsRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -225,17 +328,37 @@ func (s Service) SetRolePrincipals(ctx context.Context, req *pbs.SetRolePrincipa
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
r, err := s.setPrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion())
|
||||
r, prs, rgs, err := s.setPrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Scope = authResults.Scope
|
||||
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
|
||||
return &pbs.SetRolePrincipalsResponse{Item: r}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.SetRolePrincipalsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// RemoveRolePrincipals implements the interface pbs.RoleServiceServer.
|
||||
func (s Service) RemoveRolePrincipals(ctx context.Context, req *pbs.RemoveRolePrincipalsRequest) (*pbs.RemoveRolePrincipalsResponse, error) {
|
||||
const op = "roles.(Service).RemoveRolePrincipals"
|
||||
|
||||
if err := validateRemoveRolePrincipalsRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -243,17 +366,37 @@ func (s Service) RemoveRolePrincipals(ctx context.Context, req *pbs.RemoveRolePr
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
r, err := s.removePrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion())
|
||||
r, prs, rgs, err := s.removePrinciplesInRepo(ctx, req.GetId(), req.GetPrincipalIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Scope = authResults.Scope
|
||||
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
|
||||
return &pbs.RemoveRolePrincipalsResponse{Item: r}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.RemoveRolePrincipalsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// AddRoleGrants implements the interface pbs.RoleServiceServer.
|
||||
func (s Service) AddRoleGrants(ctx context.Context, req *pbs.AddRoleGrantsRequest) (*pbs.AddRoleGrantsResponse, error) {
|
||||
const op = "roles.(Service).AddRoleGrants"
|
||||
|
||||
if err := validateAddRoleGrantsRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -261,17 +404,37 @@ func (s Service) AddRoleGrants(ctx context.Context, req *pbs.AddRoleGrantsReques
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
r, err := s.addGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion())
|
||||
r, prs, rgs, err := s.addGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Scope = authResults.Scope
|
||||
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
|
||||
return &pbs.AddRoleGrantsResponse{Item: r}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.AddRoleGrantsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// SetRoleGrants implements the interface pbs.RoleServiceServer.
|
||||
func (s Service) SetRoleGrants(ctx context.Context, req *pbs.SetRoleGrantsRequest) (*pbs.SetRoleGrantsResponse, error) {
|
||||
const op = "roles.(Service).SetRoleGrants"
|
||||
|
||||
if err := validateSetRoleGrantsRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -279,17 +442,37 @@ func (s Service) SetRoleGrants(ctx context.Context, req *pbs.SetRoleGrantsReques
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
r, err := s.setGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion())
|
||||
r, prs, rgs, err := s.setGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Scope = authResults.Scope
|
||||
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
|
||||
return &pbs.SetRoleGrantsResponse{Item: r}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.SetRoleGrantsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// RemoveRoleGrants implements the interface pbs.RoleServiceServer.
|
||||
func (s Service) RemoveRoleGrants(ctx context.Context, req *pbs.RemoveRoleGrantsRequest) (*pbs.RemoveRoleGrantsResponse, error) {
|
||||
const op = "roles.(Service).RemoveRoleGrants"
|
||||
|
||||
if err := validateRemoveRoleGrantsRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -297,34 +480,52 @@ func (s Service) RemoveRoleGrants(ctx context.Context, req *pbs.RemoveRoleGrants
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
r, err := s.removeGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion())
|
||||
r, prs, rgs, err := s.removeGrantsInRepo(ctx, req.GetId(), req.GetGrantStrings(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Scope = authResults.Scope
|
||||
r.AuthorizedActions = authResults.FetchActionSetForId(ctx, r.Id, IdActions).Strings()
|
||||
return &pbs.RemoveRoleGrantsResponse{Item: r}, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Role, error) {
|
||||
repo, err := s.repoFn()
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, r.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, r, prs, rgs, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.RemoveRoleGrantsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
out, pr, roleGrants, err := repo.LookupRole(ctx, id)
|
||||
if err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, handlers.NotFoundErrorf("Role %q doesn't exist.", id)
|
||||
return nil, nil, nil, handlers.NotFoundErrorf("Role %q doesn't exist.", id)
|
||||
}
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.NotFoundErrorf("Role %q doesn't exist.", id)
|
||||
return nil, nil, nil, handlers.NotFoundErrorf("Role %q doesn't exist.", id)
|
||||
}
|
||||
return toProto(out, pr, roleGrants), nil
|
||||
return out, pr, roleGrants, nil
|
||||
}
|
||||
|
||||
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Role) (*pb.Role, error) {
|
||||
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Role) (*iam.Role, error) {
|
||||
const op = "roles.(Service).createInRepo"
|
||||
var opts []iam.Option
|
||||
if item.GetName() != nil {
|
||||
@@ -351,10 +552,10 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Role
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create role but no error returned from repository.")
|
||||
}
|
||||
return toProto(out, nil, nil), nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Role) (*pb.Role, error) {
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Role) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
|
||||
const op = "roles.(Service).updateInRepo"
|
||||
var opts []iam.Option
|
||||
if desc := item.GetDescription(); desc != nil {
|
||||
@@ -370,25 +571,25 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st
|
||||
|
||||
u, err := iam.NewRole(scopeId, opts...)
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build role for update: %v.", err)
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build role for update: %v.", err)
|
||||
}
|
||||
u.PublicId = id
|
||||
dbMask := maskManager.Translate(mask)
|
||||
if len(dbMask) == 0 {
|
||||
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
|
||||
return nil, nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
|
||||
}
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
out, pr, gr, rowsUpdated, err := repo.UpdateRole(ctx, u, version, dbMask)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
return nil, nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
if rowsUpdated == 0 {
|
||||
return nil, handlers.NotFoundErrorf("Role %q doesn't exist or incorrect version provided.", id)
|
||||
return nil, nil, nil, handlers.NotFoundErrorf("Role %q doesn't exist or incorrect version provided.", id)
|
||||
}
|
||||
return toProto(out, pr, gr), nil
|
||||
return out, pr, gr, nil
|
||||
}
|
||||
|
||||
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
@@ -407,7 +608,7 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Role, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*iam.Role, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -416,103 +617,98 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Rol
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var outRl []*pb.Role
|
||||
for _, g := range rl {
|
||||
// TODO: Attach principals and grants to ListRoles response.
|
||||
outRl = append(outRl, toProto(g, nil, nil))
|
||||
}
|
||||
return outRl, nil
|
||||
return rl, nil
|
||||
}
|
||||
|
||||
func (s Service) addPrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*pb.Role, error) {
|
||||
func (s Service) addPrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
|
||||
const op = "roles.(Service).addPrincpleInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
_, err = repo.AddPrincipalRoles(ctx, roleId, version, strutil.RemoveDuplicates(principalIds, false))
|
||||
if err != nil {
|
||||
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add principals to role: %v.", err)
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add principals to role: %v.", err)
|
||||
}
|
||||
out, pr, roleGrants, err := repo.LookupRole(ctx, roleId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after adding principals"))
|
||||
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after adding principals"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding principals to it.")
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding principals to it.")
|
||||
}
|
||||
return toProto(out, pr, roleGrants), nil
|
||||
return out, pr, roleGrants, nil
|
||||
}
|
||||
|
||||
func (s Service) setPrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*pb.Role, error) {
|
||||
func (s Service) setPrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
|
||||
const op = "roles.(Service).setPrinciplesInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
_, _, err = repo.SetPrincipalRoles(ctx, roleId, version, strutil.RemoveDuplicates(principalIds, false))
|
||||
if err != nil {
|
||||
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set principals on role: %v.", err)
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set principals on role: %v.", err)
|
||||
}
|
||||
out, pr, roleGrants, err := repo.LookupRole(ctx, roleId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after setting principals"))
|
||||
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after setting principals"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting principals for it.")
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting principals for it.")
|
||||
}
|
||||
return toProto(out, pr, roleGrants), nil
|
||||
return out, pr, roleGrants, nil
|
||||
}
|
||||
|
||||
func (s Service) removePrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*pb.Role, error) {
|
||||
func (s Service) removePrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
|
||||
const op = "roles.(Service).removePrinciplesInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
_, err = repo.DeletePrincipalRoles(ctx, roleId, version, strutil.RemoveDuplicates(principalIds, false))
|
||||
if err != nil {
|
||||
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove principals from role: %v.", err)
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove principals from role: %v.", err)
|
||||
}
|
||||
out, pr, roleGrants, err := repo.LookupRole(ctx, roleId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after removing principals"))
|
||||
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after removing principals"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing principals from it.")
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing principals from it.")
|
||||
}
|
||||
return toProto(out, pr, roleGrants), nil
|
||||
return out, pr, roleGrants, nil
|
||||
}
|
||||
|
||||
func (s Service) addGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*pb.Role, error) {
|
||||
func (s Service) addGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
|
||||
const op = "service.(Service).addGrantsInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
_, err = repo.AddRoleGrants(ctx, roleId, version, strutil.RemoveDuplicates(grants, false))
|
||||
if err != nil {
|
||||
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add grants to role: %v.", err)
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add grants to role: %v.", err)
|
||||
}
|
||||
out, pr, roleGrants, err := repo.LookupRole(ctx, roleId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after adding grants"))
|
||||
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after adding grants"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding grants to it.")
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after adding grants to it.")
|
||||
}
|
||||
return toProto(out, pr, roleGrants), nil
|
||||
return out, pr, roleGrants, nil
|
||||
}
|
||||
|
||||
func (s Service) setGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*pb.Role, error) {
|
||||
func (s Service) setGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
|
||||
const op = "roles.(Service).setGrantsInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// If no grant was provided, we clear the grants.
|
||||
if grants == nil {
|
||||
@@ -521,37 +717,37 @@ func (s Service) setGrantsInRepo(ctx context.Context, roleId string, grants []st
|
||||
_, _, err = repo.SetRoleGrants(ctx, roleId, version, strutil.RemoveDuplicates(grants, false))
|
||||
if err != nil {
|
||||
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set grants on role: %v.", err)
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set grants on role: %v.", err)
|
||||
}
|
||||
out, pr, roleGrants, err := repo.LookupRole(ctx, roleId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after setting grants"))
|
||||
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up role after setting grants"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting grants on it.")
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after setting grants on it.")
|
||||
}
|
||||
return toProto(out, pr, roleGrants), nil
|
||||
return out, pr, roleGrants, nil
|
||||
}
|
||||
|
||||
func (s Service) removeGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*pb.Role, error) {
|
||||
func (s Service) removeGrantsInRepo(ctx context.Context, roleId string, grants []string, version uint32) (*iam.Role, []iam.PrincipalRole, []*iam.RoleGrant, error) {
|
||||
const op = "roles.(Service).removeGrantsInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
_, err = repo.DeleteRoleGrants(ctx, roleId, version, strutil.RemoveDuplicates(grants, false))
|
||||
if err != nil {
|
||||
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove grants from role: %v", err)
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove grants from role: %v", err)
|
||||
}
|
||||
out, pr, roleGrants, err := repo.LookupRole(ctx, roleId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("uable to look up role after removing grant"))
|
||||
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("uable to look up role after removing grant"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing grants from it.")
|
||||
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup role after removing grants from it.")
|
||||
}
|
||||
return toProto(out, pr, roleGrants), nil
|
||||
return out, pr, roleGrants, nil
|
||||
}
|
||||
|
||||
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
|
||||
@@ -593,57 +789,91 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
|
||||
return auth.Verify(ctx, opts...)
|
||||
}
|
||||
|
||||
func toProto(in *iam.Role, principals []iam.PrincipalRole, grants []*iam.RoleGrant) *pb.Role {
|
||||
out := pb.Role{
|
||||
Id: in.GetPublicId(),
|
||||
ScopeId: in.GetScopeId(),
|
||||
CreatedTime: in.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
|
||||
Version: in.GetVersion(),
|
||||
func toProto(ctx context.Context, in *iam.Role, principals []iam.PrincipalRole, grants []*iam.RoleGrant, opt ...handlers.Option) (*pb.Role, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building role proto")
|
||||
}
|
||||
if in.GetDescription() != "" {
|
||||
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.Role{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.GetPublicId()
|
||||
}
|
||||
if in.GetName() != "" {
|
||||
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
|
||||
if outputFields.Has(globals.ScopeIdField) {
|
||||
out.ScopeId = in.GetScopeId()
|
||||
}
|
||||
for _, p := range principals {
|
||||
principal := &pb.Principal{
|
||||
Id: p.GetPrincipalId(),
|
||||
Type: p.GetType(),
|
||||
ScopeId: p.GetPrincipalScopeId(),
|
||||
}
|
||||
out.Principals = append(out.Principals, principal)
|
||||
out.PrincipalIds = append(out.PrincipalIds, p.GetPrincipalId())
|
||||
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
|
||||
out.Description = wrapperspb.String(in.GetDescription())
|
||||
}
|
||||
for _, g := range grants {
|
||||
out.GrantStrings = append(out.GrantStrings, g.GetRawGrant())
|
||||
parsed, err := perms.Parse(in.GetGrantScopeId(), g.GetRawGrant())
|
||||
if err != nil {
|
||||
// This should never happen as we validate on the way in, but let's
|
||||
// return what we can since we are still returning the raw grant
|
||||
out.Grants = append(out.Grants, &pb.Grant{
|
||||
Raw: g.GetRawGrant(),
|
||||
Canonical: "<parse_error>",
|
||||
Json: nil,
|
||||
})
|
||||
} else {
|
||||
_, actions := parsed.Actions()
|
||||
out.Grants = append(out.Grants, &pb.Grant{
|
||||
Raw: g.GetRawGrant(),
|
||||
Canonical: g.GetCanonicalGrant(),
|
||||
Json: &pb.GrantJson{
|
||||
Id: parsed.Id(),
|
||||
Type: parsed.Type().String(),
|
||||
Actions: actions,
|
||||
},
|
||||
})
|
||||
}
|
||||
if outputFields.Has(globals.NameField) && in.GetName() != "" {
|
||||
out.Name = wrapperspb.String(in.GetName())
|
||||
}
|
||||
if in.GetGrantScopeId() != "" {
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.GetCreateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.UpdatedTimeField) {
|
||||
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.VersionField) {
|
||||
out.Version = in.GetVersion()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
out.AuthorizedActions = opts.WithAuthorizedActions
|
||||
}
|
||||
if outputFields.Has(globals.GrantScopeIdField) && in.GetGrantScopeId() != "" {
|
||||
out.GrantScopeId = &wrapperspb.StringValue{Value: in.GetGrantScopeId()}
|
||||
}
|
||||
return &out
|
||||
if outputFields.Has(globals.PrincipalIdsField) {
|
||||
for _, p := range principals {
|
||||
out.PrincipalIds = append(out.PrincipalIds, p.GetPrincipalId())
|
||||
}
|
||||
}
|
||||
if outputFields.Has(globals.PrincipalsField) {
|
||||
for _, p := range principals {
|
||||
principal := &pb.Principal{
|
||||
Id: p.GetPrincipalId(),
|
||||
Type: p.GetType(),
|
||||
ScopeId: p.GetPrincipalScopeId(),
|
||||
}
|
||||
out.Principals = append(out.Principals, principal)
|
||||
}
|
||||
}
|
||||
if outputFields.Has(globals.GrantStringsField) {
|
||||
for _, g := range grants {
|
||||
out.GrantStrings = append(out.GrantStrings, g.GetRawGrant())
|
||||
}
|
||||
}
|
||||
if outputFields.Has(globals.GrantsField) {
|
||||
for _, g := range grants {
|
||||
parsed, err := perms.Parse(in.GetGrantScopeId(), g.GetRawGrant())
|
||||
if err != nil {
|
||||
// This should never happen as we validate on the way in, but let's
|
||||
// return what we can since we are still returning the raw grant
|
||||
out.Grants = append(out.Grants, &pb.Grant{
|
||||
Raw: g.GetRawGrant(),
|
||||
Canonical: "<parse_error>",
|
||||
Json: nil,
|
||||
})
|
||||
} else {
|
||||
_, actions := parsed.Actions()
|
||||
out.Grants = append(out.Grants, &pb.Grant{
|
||||
Raw: g.GetRawGrant(),
|
||||
Canonical: g.GetCanonicalGrant(),
|
||||
Json: &pb.GrantJson{
|
||||
Id: parsed.Id(),
|
||||
Type: parsed.Type().String(),
|
||||
Actions: actions,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
|
||||
|
||||
@@ -294,14 +294,29 @@ func TestList(t *testing.T) {
|
||||
s, err := roles.NewService(repoFn)
|
||||
require.NoError(err, "Couldn't create new role service.")
|
||||
|
||||
// Test the non-anon case
|
||||
got, gErr := s.ListRoles(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId()), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err))
|
||||
} else {
|
||||
require.NoError(gErr)
|
||||
return
|
||||
}
|
||||
require.NoError(gErr)
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListRoles(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
|
||||
// Test the anon case
|
||||
got, gErr = s.ListRoles(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, item := range got.GetItems() {
|
||||
require.Nil(item.CreatedTime)
|
||||
require.Nil(item.PrincipalIds)
|
||||
require.Nil(item.Principals)
|
||||
require.Nil(item.Grants)
|
||||
require.Nil(item.GrantStrings)
|
||||
require.Nil(item.UpdatedTime)
|
||||
require.Zero(item.Version)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc"
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/iam/store"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
@@ -29,9 +31,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/boundary/sdk/strutil"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
@@ -54,21 +54,33 @@ var (
|
||||
action.List,
|
||||
}
|
||||
|
||||
orgCollectionTypeMap = map[resource.Type]action.ActionSet{
|
||||
resource.AuthMethod: authmethods.CollectionActions,
|
||||
resource.AuthToken: authtokens.CollectionActions,
|
||||
resource.Group: groups.CollectionActions,
|
||||
resource.Role: roles.CollectionActions,
|
||||
resource.Scope: CollectionActions,
|
||||
resource.Session: sessions.CollectionActions,
|
||||
resource.User: users.CollectionActions,
|
||||
}
|
||||
scopeCollectionTypeMapMap = map[string]map[resource.Type]action.ActionSet{
|
||||
scope.Global.String(): {
|
||||
resource.AuthMethod: authmethods.CollectionActions,
|
||||
resource.AuthToken: authtokens.CollectionActions,
|
||||
resource.Group: groups.CollectionActions,
|
||||
resource.Role: roles.CollectionActions,
|
||||
resource.Scope: CollectionActions,
|
||||
resource.Session: sessions.CollectionActions,
|
||||
resource.User: users.CollectionActions,
|
||||
},
|
||||
|
||||
projectCollectionTypeMap = map[resource.Type]action.ActionSet{
|
||||
resource.Group: groups.CollectionActions,
|
||||
resource.HostCatalog: host_catalogs.CollectionActions,
|
||||
resource.Role: roles.CollectionActions,
|
||||
resource.Target: targets.CollectionActions,
|
||||
scope.Org.String(): {
|
||||
resource.AuthMethod: authmethods.CollectionActions,
|
||||
resource.AuthToken: authtokens.CollectionActions,
|
||||
resource.Group: groups.CollectionActions,
|
||||
resource.Role: roles.CollectionActions,
|
||||
resource.Scope: CollectionActions,
|
||||
resource.Session: sessions.CollectionActions,
|
||||
resource.User: users.CollectionActions,
|
||||
},
|
||||
|
||||
scope.Project.String(): {
|
||||
resource.Group: groups.CollectionActions,
|
||||
resource.HostCatalog: host_catalogs.CollectionActions,
|
||||
resource.Role: roles.CollectionActions,
|
||||
resource.Target: targets.CollectionActions,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -97,36 +109,6 @@ func NewService(repo common.IamRepoFactory) (Service, error) {
|
||||
|
||||
var _ pbs.ScopeServiceServer = Service{}
|
||||
|
||||
func populateCollectionAuthorizedActions(ctx context.Context,
|
||||
authResults auth.VerifyResults,
|
||||
item *pb.Scope) error {
|
||||
res := &perms.Resource{
|
||||
ScopeId: item.Id,
|
||||
}
|
||||
mapToRange := orgCollectionTypeMap
|
||||
if item.Type == "project" {
|
||||
mapToRange = projectCollectionTypeMap
|
||||
}
|
||||
// Range over the defined collections and check permissions against those
|
||||
// collections. We use the ID of this scope being returned, not its parent,
|
||||
// hence passing in a resource here.
|
||||
for k, v := range mapToRange {
|
||||
res.Type = k
|
||||
acts := authResults.FetchActionSetForType(ctx, k, v, auth.WithResource(res)).Strings()
|
||||
if len(acts) > 0 {
|
||||
if item.AuthorizedCollectionActions == nil {
|
||||
item.AuthorizedCollectionActions = make(map[string]*structpb.ListValue)
|
||||
}
|
||||
lv, err := structpb.NewList(strutil.StringListToInterfaceList(acts))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
item.AuthorizedCollectionActions[k.String()+"s"] = lv
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListScopes implements the interface pbs.ScopeServiceServer.
|
||||
func (s Service) ListScopes(ctx context.Context, req *pbs.ListScopesRequest) (*pbs.ListScopesResponse, error) {
|
||||
if req.GetScopeId() == "" {
|
||||
@@ -158,39 +140,65 @@ func (s Service) ListScopes(ctx context.Context, req *pbs.ListScopesRequest) (*p
|
||||
return &pbs.ListScopesResponse{}, nil
|
||||
}
|
||||
|
||||
pl, err := s.listFromRepo(ctx, scopeIds, authResults.UserId == auth.AnonymousUserId)
|
||||
pl, err := s.listFromRepo(ctx, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pl) == 0 {
|
||||
return &pbs.ListScopesResponse{}, nil
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Scope, 0, len(pl))
|
||||
res := &perms.Resource{
|
||||
res := perms.Resource{
|
||||
Type: resource.Scope,
|
||||
}
|
||||
for _, item := range pl {
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
res.Id = item.GetPublicId()
|
||||
res.ScopeId = item.GetParentId()
|
||||
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, item); err != nil {
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.GetParentId()]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, scopeCollectionTypeMapMap[item.Type], item.GetPublicId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := ToProto(ctx, item, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
|
||||
SortScopes(finalItems)
|
||||
return &pbs.ListScopesResponse{Items: finalItems}, nil
|
||||
}
|
||||
|
||||
// GetScopes implements the interface pbs.ScopeServiceServer.
|
||||
func (s Service) GetScope(ctx context.Context, req *pbs.GetScopeRequest) (*pbs.GetScopeResponse, error) {
|
||||
const op = "scopes.(Service).GetScope"
|
||||
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -202,21 +210,45 @@ func (s Service) GetScope(ctx context.Context, req *pbs.GetScopeRequest) (*pbs.G
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Scope = authResults.Scope
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
act := IdActions
|
||||
// Can't delete global so elide it
|
||||
if p.Id == "global" {
|
||||
act = act[0:2]
|
||||
if p.GetPublicId() == "global" {
|
||||
act = act[0:3]
|
||||
}
|
||||
p.AuthorizedActions = authResults.FetchActionSetForId(ctx, p.Id, act).Strings()
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, p); err != nil {
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, p.GetPublicId(), act).Strings()))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, scopeCollectionTypeMapMap[p.Type], p.GetPublicId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := ToProto(ctx, p, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbs.GetScopeResponse{Item: p}, nil
|
||||
|
||||
return &pbs.GetScopeResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// CreateScope implements the interface pbs.ScopeServiceServer.
|
||||
func (s Service) CreateScope(ctx context.Context, req *pbs.CreateScopeRequest) (*pbs.CreateScopeResponse, error) {
|
||||
const op = "scopes.(Service).CreateScope"
|
||||
|
||||
if err := validateCreateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -228,16 +260,40 @@ func (s Service) CreateScope(ctx context.Context, req *pbs.CreateScopeRequest) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Scope = authResults.Scope
|
||||
p.AuthorizedActions = authResults.FetchActionSetForId(ctx, p.Id, IdActions).Strings()
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, p); err != nil {
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, p.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, scopeCollectionTypeMapMap[p.Type], p.GetPublicId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := ToProto(ctx, p, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbs.CreateScopeResponse{Item: p, Uri: fmt.Sprintf("scopes/%s", p.GetId())}, nil
|
||||
|
||||
return &pbs.CreateScopeResponse{Item: item, Uri: fmt.Sprintf("scopes/%s", item.GetId())}, nil
|
||||
}
|
||||
|
||||
// UpdateScope implements the interface pbs.ScopeServiceServer.
|
||||
func (s Service) UpdateScope(ctx context.Context, req *pbs.UpdateScopeRequest) (*pbs.UpdateScopeResponse, error) {
|
||||
const op = "scopes.(Service).UpdateScope"
|
||||
|
||||
if err := validateUpdateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -249,12 +305,34 @@ func (s Service) UpdateScope(ctx context.Context, req *pbs.UpdateScopeRequest) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Scope = authResults.Scope
|
||||
p.AuthorizedActions = authResults.FetchActionSetForId(ctx, p.Id, IdActions).Strings()
|
||||
if err := populateCollectionAuthorizedActions(ctx, authResults, p); err != nil {
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, p.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, scopeCollectionTypeMapMap[p.Type], p.GetPublicId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions))
|
||||
}
|
||||
|
||||
item, err := ToProto(ctx, p, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbs.UpdateScopeResponse{Item: p}, nil
|
||||
|
||||
return &pbs.UpdateScopeResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// DeleteScope implements the interface pbs.ScopeServiceServer.
|
||||
@@ -273,22 +351,22 @@ func (s Service) DeleteScope(ctx context.Context, req *pbs.DeleteScopeRequest) (
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Scope, error) {
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*iam.Scope, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := repo.LookupScope(ctx, id)
|
||||
out, err := repo.LookupScope(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p == nil {
|
||||
if out == nil {
|
||||
return nil, handlers.NotFoundErrorf("Scope %q doesn't exist.", id)
|
||||
}
|
||||
return ToProto(p), nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) createInRepo(ctx context.Context, authResults auth.VerifyResults, req *pbs.CreateScopeRequest) (*pb.Scope, error) {
|
||||
func (s Service) createInRepo(ctx context.Context, authResults auth.VerifyResults, req *pbs.CreateScopeRequest) (*iam.Scope, error) {
|
||||
const op = "scopes.(Service).createInRepo"
|
||||
item := req.GetItem()
|
||||
var opts []iam.Option
|
||||
@@ -324,10 +402,10 @@ func (s Service) createInRepo(ctx context.Context, authResults auth.VerifyResult
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create scope but no error returned from repository.")
|
||||
}
|
||||
return ToProto(out), nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) updateInRepo(ctx context.Context, parentScope *pb.ScopeInfo, scopeId string, mask []string, item *pb.Scope) (*pb.Scope, error) {
|
||||
func (s Service) updateInRepo(ctx context.Context, parentScope *pb.ScopeInfo, scopeId string, mask []string, item *pb.Scope) (*iam.Scope, error) {
|
||||
const op = "scope.(Service).updateInRepo"
|
||||
var opts []iam.Option
|
||||
var scopeDesc, scopeName, scopePrimaryAuthMethodId string
|
||||
@@ -384,7 +462,7 @@ func (s Service) updateInRepo(ctx context.Context, parentScope *pb.ScopeInfo, sc
|
||||
if rowsUpdated == 0 {
|
||||
return nil, handlers.NotFoundErrorf("Scope %q doesn't exist or incorrect version provided.", scopeId)
|
||||
}
|
||||
return ToProto(out), nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) deleteFromRepo(ctx context.Context, scopeId string) (bool, error) {
|
||||
@@ -408,7 +486,7 @@ func SortScopes(scps []*pb.Scope) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string, anonUser bool) ([]*pb.Scope, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*iam.Scope, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -417,13 +495,7 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string, anonUser b
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to list scopes: %v", err)
|
||||
}
|
||||
|
||||
var outPl []*pb.Scope
|
||||
for _, scp := range scps {
|
||||
outPl = append(outPl, ToProto(scp, handlers.WithAnonymousListing(anonUser)))
|
||||
}
|
||||
SortScopes(outPl)
|
||||
return outPl, nil
|
||||
return scps, nil
|
||||
}
|
||||
|
||||
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
|
||||
@@ -465,29 +537,52 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
|
||||
return auth.Verify(ctx, opts...)
|
||||
}
|
||||
|
||||
func ToProto(in *iam.Scope, opt ...handlers.Option) *pb.Scope {
|
||||
anonListing := handlers.GetOpts(opt...).WithAnonymousListing
|
||||
out := pb.Scope{
|
||||
Id: in.GetPublicId(),
|
||||
ScopeId: in.GetParentId(),
|
||||
Type: in.GetType(),
|
||||
func ToProto(ctx context.Context, in *iam.Scope, opt ...handlers.Option) (*pb.Scope, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building scope proto")
|
||||
}
|
||||
if in.GetDescription() != "" {
|
||||
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.Scope{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.GetPublicId()
|
||||
}
|
||||
if in.GetName() != "" {
|
||||
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
|
||||
if outputFields.Has(globals.ScopeIdField) {
|
||||
out.ScopeId = in.GetParentId()
|
||||
}
|
||||
if in.GetPrimaryAuthMethodId() != "" {
|
||||
out.PrimaryAuthMethodId = &wrapperspb.StringValue{Value: in.GetPrimaryAuthMethodId()}
|
||||
if outputFields.Has(globals.TypeField) {
|
||||
out.Type = in.GetType()
|
||||
}
|
||||
if !anonListing {
|
||||
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
|
||||
out.Description = wrapperspb.String(in.GetDescription())
|
||||
}
|
||||
if outputFields.Has(globals.NameField) && in.GetName() != "" {
|
||||
out.Name = wrapperspb.String(in.GetName())
|
||||
}
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.GetCreateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.UpdatedTimeField) {
|
||||
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.VersionField) {
|
||||
out.Version = in.GetVersion()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
out.AuthorizedActions = opts.WithAuthorizedActions
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
|
||||
out.AuthorizedCollectionActions = opts.WithAuthorizedCollectionActions
|
||||
}
|
||||
if outputFields.Has(globals.PrimaryAuthMethodId) && in.GetPrimaryAuthMethodId() != "" {
|
||||
out.PrimaryAuthMethodId = &wrapperspb.StringValue{Value: in.GetPrimaryAuthMethodId()}
|
||||
}
|
||||
|
||||
return &out
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers/scopes"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
@@ -290,13 +291,16 @@ func TestList(t *testing.T) {
|
||||
_, err = repo.DeleteScope(context.Background(), p2.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
|
||||
outputFields := perms.OutputFieldsMap(nil).SelfOrDefaults("u_auth")
|
||||
var initialOrgs []*pb.Scope
|
||||
globalScope := &pb.ScopeInfo{Id: "global", Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"}
|
||||
oNoProjectsProto := scopes.ToProto(oNoProjects)
|
||||
oNoProjectsProto, err := scopes.ToProto(context.Background(), oNoProjects, handlers.WithOutputFields(&outputFields))
|
||||
require.NoError(t, err)
|
||||
oNoProjectsProto.Scope = globalScope
|
||||
oNoProjectsProto.AuthorizedActions = testAuthorizedActions
|
||||
oNoProjectsProto.AuthorizedCollectionActions = orgAuthorizedCollectionActions
|
||||
oWithProjectsProto := scopes.ToProto(oWithProjects)
|
||||
oWithProjectsProto, err := scopes.ToProto(context.Background(), oWithProjects, handlers.WithOutputFields(&outputFields))
|
||||
require.NoError(t, err)
|
||||
oWithProjectsProto.Scope = globalScope
|
||||
oWithProjectsProto.AuthorizedActions = testAuthorizedActions
|
||||
oWithProjectsProto.AuthorizedCollectionActions = orgAuthorizedCollectionActions
|
||||
@@ -342,7 +346,7 @@ func TestList(t *testing.T) {
|
||||
require.NoError(err, "Couldn't create new role service.")
|
||||
|
||||
// Test with non-anonymous listing first
|
||||
got, gErr := s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId, auth.WithUserId("u_auth")), tc.req)
|
||||
got, gErr := s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListScopes(%+v) got error\n%v, wanted\n%v", tc.req, gErr, tc.err)
|
||||
@@ -351,8 +355,9 @@ func TestList(t *testing.T) {
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListScopes(%q) got response\n%q\nwanted\n%q", tc.req, got, tc.res)
|
||||
|
||||
// Now test with anonymous listing
|
||||
got, gErr = s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), tc.req)
|
||||
got, gErr = s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId, auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, item := range got.GetItems() {
|
||||
assert.Nil(item.CreatedTime)
|
||||
assert.Nil(item.UpdatedTime)
|
||||
@@ -455,7 +460,7 @@ func TestList(t *testing.T) {
|
||||
require.NoError(err, "Couldn't create new role service.")
|
||||
|
||||
// Test with non-anonymous listing first
|
||||
got, gErr := s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId, auth.WithUserId("u_auth")), tc.req)
|
||||
got, gErr := s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListScopes(%+v) got error\n%v, wanted\n%v", tc.req, gErr, tc.err)
|
||||
@@ -465,8 +470,9 @@ func TestList(t *testing.T) {
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListScopes(%q) got response\n%q, wanted\n%q", tc.req, got, tc.res)
|
||||
|
||||
// Now test with anonymous listing
|
||||
got, gErr = s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId), tc.req)
|
||||
got, gErr = s.ListScopes(auth.DisabledAuthTestContext(repoFn, tc.scopeId, auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, item := range got.GetItems() {
|
||||
assert.Nil(item.CreatedTime)
|
||||
assert.Nil(item.UpdatedTime)
|
||||
|
||||
@@ -5,11 +5,13 @@ import (
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/sessions"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
@@ -18,6 +20,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -62,6 +65,8 @@ var _ pbs.SessionServiceServer = Service{}
|
||||
|
||||
// GetSessions implements the interface pbs.SessionServiceServer.
|
||||
func (s Service) GetSession(ctx context.Context, req *pbs.GetSessionRequest) (*pbs.GetSessionResponse, error) {
|
||||
const op = "sessions.(Service).GetSession"
|
||||
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -74,17 +79,42 @@ func (s Service) GetSession(ctx context.Context, req *pbs.GetSessionRequest) (*p
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authzdActions := authResults.FetchActionSetForId(ctx, ses.Id, IdActions)
|
||||
var outputFields perms.OutputFieldsMap
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, ses.GetPublicId(), IdActions)
|
||||
|
||||
// Check to see if we need to verify Read vs. just ReadSelf
|
||||
if ses.GetUserId() != authResults.UserId {
|
||||
if !authzdActions.HasAction(action.Read) {
|
||||
if ses.UserId != authResults.UserId {
|
||||
if !authorizedActions.HasAction(action.Read) {
|
||||
return nil, handlers.ForbiddenError()
|
||||
}
|
||||
outputFields = authResults.FetchOutputFields(perms.Resource{
|
||||
Id: ses.GetPublicId(),
|
||||
ScopeId: ses.ScopeId,
|
||||
Type: resource.Session,
|
||||
}, action.Read).SelfOrDefaults(authResults.UserId)
|
||||
} else {
|
||||
var ok bool
|
||||
outputFields, ok = requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
}
|
||||
|
||||
ses.Scope = authResults.Scope
|
||||
ses.AuthorizedActions = authzdActions.Strings()
|
||||
return &pbs.GetSessionResponse{Item: ses}, nil
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions.Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, ses, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.GetSessionResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// ListSessions implements the interface pbs.SessionServiceServer.
|
||||
@@ -116,32 +146,48 @@ func (s Service) ListSessions(ctx context.Context, req *pbs.ListSessionsRequest)
|
||||
return &pbs.ListSessionsResponse{}, nil
|
||||
}
|
||||
|
||||
seslist, err := s.listFromRepo(ctx, session.WithScopeIds(scopeIds))
|
||||
sesList, err := s.listFromRepo(ctx, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(sesList) == 0 {
|
||||
return &pbs.ListSessionsResponse{}, nil
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Session, 0, len(seslist))
|
||||
res := &perms.Resource{
|
||||
finalItems := make([]*pb.Session, 0, len(sesList))
|
||||
res := perms.Resource{
|
||||
Type: resource.Session,
|
||||
}
|
||||
for _, item := range seslist {
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res))
|
||||
for _, item := range sesList {
|
||||
res.Id = item.GetPublicId()
|
||||
res.ScopeId = item.ScopeId
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res))
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if authorizedActions.OnlySelf() && item.GetUserId() != authResults.UserId {
|
||||
if authorizedActions.OnlySelf() && item.UserId != authResults.UserId {
|
||||
continue
|
||||
}
|
||||
|
||||
item.AuthorizedActions = authorizedActions.Strings()
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.ScopeId]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions.Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, item, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
@@ -153,6 +199,8 @@ func (s Service) ListSessions(ctx context.Context, req *pbs.ListSessionsRequest)
|
||||
|
||||
// CancelSession implements the interface pbs.SessionServiceServer.
|
||||
func (s Service) CancelSession(ctx context.Context, req *pbs.CancelSessionRequest) (*pbs.CancelSessionResponse, error) {
|
||||
const op = "sessions.(Service).CancelSession"
|
||||
|
||||
if err := validateCancelRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -165,24 +213,50 @@ func (s Service) CancelSession(ctx context.Context, req *pbs.CancelSessionReques
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authzdActions := authResults.FetchActionSetForId(ctx, ses.Id, IdActions)
|
||||
// Check to see if we need to verify Cancel vs. just CancelSelf
|
||||
if ses.GetUserId() != authResults.UserId {
|
||||
if !authzdActions.HasAction(action.Cancel) {
|
||||
|
||||
var outputFields perms.OutputFieldsMap
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, ses.GetPublicId(), IdActions)
|
||||
|
||||
// Check to see if we need to verify Read vs. just ReadSelf
|
||||
if ses.UserId != authResults.UserId {
|
||||
if !authorizedActions.HasAction(action.Cancel) {
|
||||
return nil, handlers.ForbiddenError()
|
||||
}
|
||||
outputFields = authResults.FetchOutputFields(perms.Resource{
|
||||
Id: ses.GetPublicId(),
|
||||
ScopeId: ses.ScopeId,
|
||||
Type: resource.Session,
|
||||
}, action.Cancel).SelfOrDefaults(authResults.UserId)
|
||||
} else {
|
||||
var ok bool
|
||||
outputFields, ok = requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
}
|
||||
|
||||
ses, err = s.cancelInRepo(ctx, req.GetId(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ses.Scope = authResults.Scope
|
||||
ses.AuthorizedActions = authzdActions.Strings()
|
||||
return &pbs.CancelSessionResponse{Item: ses}, nil
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions.Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, ses, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbs.CancelSessionResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Session, error) {
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*session.Session, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -197,26 +271,22 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Session, error
|
||||
if sess == nil {
|
||||
return nil, handlers.NotFoundErrorf("Session %q doesn't exist.", id)
|
||||
}
|
||||
return toProto(sess), nil
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, opts ...session.Option) ([]*pb.Session, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*session.Session, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seslist, err := repo.ListSessions(ctx, opts...)
|
||||
sesList, err := repo.ListSessions(ctx, session.WithScopeIds(scopeIds))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var outSl []*pb.Session
|
||||
for _, ses := range seslist {
|
||||
outSl = append(outSl, toProto(ses))
|
||||
}
|
||||
return outSl, nil
|
||||
return sesList, nil
|
||||
}
|
||||
|
||||
func (s Service) cancelInRepo(ctx context.Context, id string, version uint32) (*pb.Session, error) {
|
||||
func (s Service) cancelInRepo(ctx context.Context, id string, version uint32) (*session.Session, error) {
|
||||
const op = "sessions.(Service).cancelInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
@@ -226,7 +296,7 @@ func (s Service) cancelInRepo(ctx context.Context, id string, version uint32) (*
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to update session"))
|
||||
}
|
||||
return toProto(out), nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
|
||||
@@ -276,42 +346,89 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
|
||||
return auth.Verify(ctx, opts...)
|
||||
}
|
||||
|
||||
func toProto(in *session.Session) *pb.Session {
|
||||
out := pb.Session{
|
||||
Id: in.GetPublicId(),
|
||||
ScopeId: in.ScopeId,
|
||||
TargetId: in.TargetId,
|
||||
Version: in.Version,
|
||||
UserId: in.UserId,
|
||||
HostId: in.HostId,
|
||||
HostSetId: in.HostSetId,
|
||||
AuthTokenId: in.AuthTokenId,
|
||||
Endpoint: in.Endpoint,
|
||||
Type: target.SubtypeFromId(in.TargetId).String(),
|
||||
// TODO: Provide the ServerType and the ServerId when that information becomes relevant in the API.
|
||||
func toProto(ctx context.Context, in *session.Session, opt ...handlers.Option) (*pb.Session, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building auth token proto")
|
||||
}
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
CreatedTime: in.CreateTime.GetTimestamp(),
|
||||
UpdatedTime: in.UpdateTime.GetTimestamp(),
|
||||
ExpirationTime: in.ExpirationTime.GetTimestamp(),
|
||||
Certificate: in.Certificate,
|
||||
TerminationReason: in.TerminationReason,
|
||||
out := pb.Session{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.GetPublicId()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeIdField) {
|
||||
out.ScopeId = in.ScopeId
|
||||
}
|
||||
if outputFields.Has(globals.TargetIdField) {
|
||||
out.TargetId = in.TargetId
|
||||
}
|
||||
if outputFields.Has(globals.TypeField) {
|
||||
out.Type = target.SubtypeFromId(in.TargetId).String()
|
||||
}
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.CreateTime.GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.UpdatedTimeField) {
|
||||
out.UpdatedTime = in.UpdateTime.GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.VersionField) {
|
||||
out.Version = in.Version
|
||||
}
|
||||
if outputFields.Has(globals.UserIdField) {
|
||||
out.UserId = in.UserId
|
||||
}
|
||||
if outputFields.Has(globals.HostIdField) {
|
||||
out.HostId = in.HostId
|
||||
}
|
||||
if outputFields.Has(globals.HostSetIdField) {
|
||||
out.HostSetId = in.HostSetId
|
||||
}
|
||||
if outputFields.Has(globals.AuthTokenIdField) {
|
||||
out.AuthTokenId = in.AuthTokenId
|
||||
}
|
||||
if outputFields.Has(globals.EndpointField) {
|
||||
out.Endpoint = in.Endpoint
|
||||
}
|
||||
if outputFields.Has(globals.HostIdField) {
|
||||
out.HostId = in.HostId
|
||||
}
|
||||
if outputFields.Has(globals.ExpirationTimeField) {
|
||||
out.ExpirationTime = in.ExpirationTime.GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.CertificateField) {
|
||||
out.Certificate = in.Certificate
|
||||
}
|
||||
if outputFields.Has(globals.TerminationReasonField) {
|
||||
out.TerminationReason = in.TerminationReason
|
||||
}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
out.AuthorizedActions = opts.WithAuthorizedActions
|
||||
}
|
||||
// TODO: Provide the ServerType and the ServerId when that information becomes relevant in the API.
|
||||
if len(in.States) > 0 {
|
||||
out.Status = in.States[0].Status.String()
|
||||
if outputFields.Has(globals.StatusField) {
|
||||
out.Status = in.States[0].Status.String()
|
||||
}
|
||||
if outputFields.Has(globals.StatesField) {
|
||||
for _, s := range in.States {
|
||||
sessState := &pb.SessionState{
|
||||
Status: s.Status.String(),
|
||||
}
|
||||
if s.StartTime != nil {
|
||||
sessState.StartTime = s.StartTime.GetTimestamp()
|
||||
}
|
||||
if s.EndTime != nil {
|
||||
sessState.EndTime = s.EndTime.GetTimestamp()
|
||||
}
|
||||
out.States = append(out.States, sessState)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, s := range in.States {
|
||||
sessState := &pb.SessionState{
|
||||
Status: s.Status.String(),
|
||||
}
|
||||
if s.StartTime != nil {
|
||||
sessState.StartTime = s.StartTime.GetTimestamp()
|
||||
}
|
||||
if s.EndTime != nil {
|
||||
sessState.EndTime = s.EndTime.GetTimestamp()
|
||||
}
|
||||
out.States = append(out.States, sessState)
|
||||
}
|
||||
return &out
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
|
||||
|
||||
@@ -387,24 +387,44 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
s, err := sessions.NewService(sessRepoFn, iamRepoFn)
|
||||
require.NoError(t, err, "Couldn't create new session service.")
|
||||
require.NoError(err, "Couldn't create new session service.")
|
||||
|
||||
// Test without anon user
|
||||
got, gErr := s.ListSessions(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(t, gErr)
|
||||
assert.True(t, errors.Is(gErr, tc.err), "ListSessions(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
} else {
|
||||
require.NoError(t, gErr)
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListSessions(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
return
|
||||
}
|
||||
require.NoError(gErr)
|
||||
if tc.res != nil {
|
||||
require.Equal(t, len(tc.res.GetItems()), len(got.GetItems()), "Didn't get expected number of sessions: %v", got.GetItems())
|
||||
require.Equal(len(tc.res.GetItems()), len(got.GetItems()), "Didn't get expected number of sessions: %v", got.GetItems())
|
||||
for i, wantSess := range tc.res.GetItems() {
|
||||
assert.True(t, got.GetItems()[i].GetExpirationTime().AsTime().Sub(wantSess.GetExpirationTime().AsTime()) < 10*time.Millisecond)
|
||||
assert.True(got.GetItems()[i].GetExpirationTime().AsTime().Sub(wantSess.GetExpirationTime().AsTime()) < 10*time.Millisecond)
|
||||
wantSess.ExpirationTime = got.GetItems()[i].GetExpirationTime()
|
||||
}
|
||||
}
|
||||
assert.Empty(t, cmp.Diff(got, tc.res, protocmp.Transform()), "ListSessions(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListSessions(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
|
||||
// Test with anon user
|
||||
got, gErr = s.ListSessions(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, item := range got.GetItems() {
|
||||
require.Empty(item.Version)
|
||||
require.Empty(item.UserId)
|
||||
require.Empty(item.HostId)
|
||||
require.Empty(item.HostSetId)
|
||||
require.Empty(item.AuthTokenId)
|
||||
require.Empty(item.Endpoint)
|
||||
require.Nil(item.CreatedTime)
|
||||
require.Nil(item.ExpirationTime)
|
||||
require.Nil(item.UpdatedTime)
|
||||
require.Empty(item.Certificate)
|
||||
require.Empty(item.TerminationReason)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/host/static"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
|
||||
@@ -145,26 +147,45 @@ func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) (
|
||||
return &pbs.ListTargetsResponse{}, nil
|
||||
}
|
||||
|
||||
ul, err := s.listFromRepo(ctx, scopeIds)
|
||||
tl, err := s.listFromRepo(ctx, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(tl) == 0 {
|
||||
return &pbs.ListTargetsResponse{}, nil
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.Target, 0, len(ul))
|
||||
res := &perms.Resource{
|
||||
finalItems := make([]*pb.Target, 0, len(tl))
|
||||
res := perms.Resource{
|
||||
Type: resource.Target,
|
||||
}
|
||||
for _, item := range ul {
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
for _, item := range tl {
|
||||
res.Id = item.GetPublicId()
|
||||
res.ScopeId = item.GetScopeId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.GetScopeId()]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, item, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
@@ -174,6 +195,8 @@ func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) (
|
||||
|
||||
// GetTargets implements the interface pbs.TargetServiceServer.
|
||||
func (s Service) GetTarget(ctx context.Context, req *pbs.GetTargetRequest) (*pbs.GetTargetResponse, error) {
|
||||
const op = "targets.(Service).GetTarget"
|
||||
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -181,17 +204,37 @@ func (s Service) GetTarget(ctx context.Context, req *pbs.GetTargetRequest) (*pbs
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.getFromRepo(ctx, req.GetId())
|
||||
t, ts, err := s.getFromRepo(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.GetTargetResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, t, ts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.GetTargetResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// CreateTarget implements the interface pbs.TargetServiceServer.
|
||||
func (s Service) CreateTarget(ctx context.Context, req *pbs.CreateTargetRequest) (*pbs.CreateTargetResponse, error) {
|
||||
const op = "targets.(Service).CreateTarget"
|
||||
|
||||
if err := validateCreateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -199,17 +242,37 @@ func (s Service) CreateTarget(ctx context.Context, req *pbs.CreateTargetRequest)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.createInRepo(ctx, req.GetItem())
|
||||
t, ts, err := s.createInRepo(ctx, req.GetItem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.CreateTargetResponse{Item: u, Uri: fmt.Sprintf("targets/%s", u.GetId())}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, t, ts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.CreateTargetResponse{Item: item, Uri: fmt.Sprintf("targets/%s", item.GetId())}, nil
|
||||
}
|
||||
|
||||
// UpdateTarget implements the interface pbs.TargetServiceServer.
|
||||
func (s Service) UpdateTarget(ctx context.Context, req *pbs.UpdateTargetRequest) (*pbs.UpdateTargetResponse, error) {
|
||||
const op = "targets.(Service).UpdateTarget"
|
||||
|
||||
if err := validateUpdateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -217,13 +280,31 @@ func (s Service) UpdateTarget(ctx context.Context, req *pbs.UpdateTargetRequest)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
t, ts, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.UpdateTargetResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, t, ts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.UpdateTargetResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// DeleteTarget implements the interface pbs.TargetServiceServer.
|
||||
@@ -244,6 +325,8 @@ func (s Service) DeleteTarget(ctx context.Context, req *pbs.DeleteTargetRequest)
|
||||
|
||||
// AddTargetHostSets implements the interface pbs.TargetServiceServer.
|
||||
func (s Service) AddTargetHostSets(ctx context.Context, req *pbs.AddTargetHostSetsRequest) (*pbs.AddTargetHostSetsResponse, error) {
|
||||
const op = "targets.(Service).AddTargetHostSets"
|
||||
|
||||
if err := validateAddRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -251,17 +334,37 @@ func (s Service) AddTargetHostSets(ctx context.Context, req *pbs.AddTargetHostSe
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.addInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion())
|
||||
t, ts, err := s.addInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.AddTargetHostSetsResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, t, ts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.AddTargetHostSetsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// SetTargetHostSets implements the interface pbs.TargetServiceServer.
|
||||
func (s Service) SetTargetHostSets(ctx context.Context, req *pbs.SetTargetHostSetsRequest) (*pbs.SetTargetHostSetsResponse, error) {
|
||||
const op = "targets.(Service).SetTargetHostSets"
|
||||
|
||||
if err := validateSetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -269,17 +372,37 @@ func (s Service) SetTargetHostSets(ctx context.Context, req *pbs.SetTargetHostSe
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.setInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion())
|
||||
t, ts, err := s.setInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.SetTargetHostSetsResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, t, ts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.SetTargetHostSetsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// RemoveTargetHostSets implements the interface pbs.TargetServiceServer.
|
||||
func (s Service) RemoveTargetHostSets(ctx context.Context, req *pbs.RemoveTargetHostSetsRequest) (*pbs.RemoveTargetHostSetsResponse, error) {
|
||||
const op = "targets.(Service).RemoveTargetHostSets"
|
||||
|
||||
if err := validateRemoveRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -287,13 +410,31 @@ func (s Service) RemoveTargetHostSets(ctx context.Context, req *pbs.RemoveTarget
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.removeInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion())
|
||||
t, ts, err := s.removeInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.RemoveTargetHostSetsResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, t, ts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.RemoveTargetHostSetsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSessionRequest) (*pbs.AuthorizeSessionResponse, error) {
|
||||
@@ -574,25 +715,25 @@ HostSetIterationLoop:
|
||||
return &pbs.AuthorizeSessionResponse{Item: ret}, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Target, error) {
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (target.Target, []*target.TargetSet, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
u, m, err := repo.LookupTarget(ctx, id)
|
||||
if err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id)
|
||||
return nil, nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id)
|
||||
}
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if u == nil {
|
||||
return nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id)
|
||||
return nil, nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id)
|
||||
}
|
||||
return toProto(u, m)
|
||||
return u, m, nil
|
||||
}
|
||||
|
||||
func (s Service) createInRepo(ctx context.Context, item *pb.Target) (*pb.Target, error) {
|
||||
func (s Service) createInRepo(ctx context.Context, item *pb.Target) (target.Target, []*target.TargetSet, error) {
|
||||
const op = "targets.(Service).createInRepo"
|
||||
opts := []target.Option{target.WithName(item.GetName().GetValue())}
|
||||
if item.GetDescription() != nil {
|
||||
@@ -609,30 +750,30 @@ func (s Service) createInRepo(ctx context.Context, item *pb.Target) (*pb.Target,
|
||||
}
|
||||
tcpAttrs := &pb.TcpTargetAttributes{}
|
||||
if err := handlers.StructToProto(item.GetAttributes(), tcpAttrs); err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Provided attributes don't match expected format.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Provided attributes don't match expected format.")
|
||||
}
|
||||
if tcpAttrs.GetDefaultPort().GetValue() != 0 {
|
||||
opts = append(opts, target.WithDefaultPort(tcpAttrs.GetDefaultPort().GetValue()))
|
||||
}
|
||||
u, err := target.NewTcpTarget(item.GetScopeId(), opts...)
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for creation: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for creation: %v.", err)
|
||||
}
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
out, m, err := repo.CreateTcpTarget(ctx, u)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create target"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create target"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create target but no error returned from repository.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create target but no error returned from repository.")
|
||||
}
|
||||
return toProto(out, m)
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Target) (*pb.Target, error) {
|
||||
func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Target) (target.Target, []*target.TargetSet, error) {
|
||||
const op = "targets.(Service).updateInRepo"
|
||||
var opts []target.Option
|
||||
if desc := item.GetDescription(); desc != nil {
|
||||
@@ -652,7 +793,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st
|
||||
}
|
||||
tcpAttrs := &pb.TcpTargetAttributes{}
|
||||
if err := handlers.StructToProto(item.GetAttributes(), tcpAttrs); err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Provided attributes don't match expected format.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Provided attributes don't match expected format.")
|
||||
}
|
||||
if tcpAttrs.GetDefaultPort().GetValue() != 0 {
|
||||
opts = append(opts, target.WithDefaultPort(tcpAttrs.GetDefaultPort().GetValue()))
|
||||
@@ -660,25 +801,25 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st
|
||||
version := item.GetVersion()
|
||||
u, err := target.NewTcpTarget(scopeId, opts...)
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for update: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for update: %v.", err)
|
||||
}
|
||||
u.PublicId = id
|
||||
dbMask := maskManager.Translate(mask)
|
||||
if len(dbMask) == 0 {
|
||||
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
|
||||
return nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
|
||||
}
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
out, m, rowsUpdated, err := repo.UpdateTcpTarget(ctx, u, version, dbMask)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to update target"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to update target"))
|
||||
}
|
||||
if rowsUpdated == 0 {
|
||||
return nil, handlers.NotFoundErrorf("Target %q not found or incorrect version provided.", id)
|
||||
return nil, nil, handlers.NotFoundErrorf("Target %q not found or incorrect version provided.", id)
|
||||
}
|
||||
return toProto(out, m)
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
@@ -697,7 +838,7 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Target, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]target.Target, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -706,74 +847,66 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Tar
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var outUl []*pb.Target
|
||||
for _, u := range ul {
|
||||
o, err := toProto(u, nil)
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to convert value to proto: %v.", err)
|
||||
}
|
||||
outUl = append(outUl, o)
|
||||
}
|
||||
return outUl, nil
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func (s Service) addInRepo(ctx context.Context, targetId string, hostSetId []string, version uint32) (*pb.Target, error) {
|
||||
func (s Service) addInRepo(ctx context.Context, targetId string, hostSetId []string, version uint32) (target.Target, []*target.TargetSet, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
out, m, err := repo.AddTargetHostSets(ctx, targetId, version, strutil.RemoveDuplicates(hostSetId, false))
|
||||
if err != nil {
|
||||
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add host sets to target: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add host sets to target: %v.", err)
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after adding host sets to it.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after adding host sets to it.")
|
||||
}
|
||||
return toProto(out, m)
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) setInRepo(ctx context.Context, targetId string, hostSetIds []string, version uint32) (*pb.Target, error) {
|
||||
func (s Service) setInRepo(ctx context.Context, targetId string, hostSetIds []string, version uint32) (target.Target, []*target.TargetSet, error) {
|
||||
const op = "targets.(Service).setInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
_, _, err = repo.SetTargetHostSets(ctx, targetId, version, strutil.RemoveDuplicates(hostSetIds, false))
|
||||
if err != nil {
|
||||
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set host sets in target: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set host sets in target: %v.", err)
|
||||
}
|
||||
|
||||
out, m, err := repo.LookupTarget(ctx, targetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after setting host sets"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after setting host sets"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after setting host sets for it.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after setting host sets for it.")
|
||||
}
|
||||
return toProto(out, m)
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) removeInRepo(ctx context.Context, targetId string, hostSetIds []string, version uint32) (*pb.Target, error) {
|
||||
func (s Service) removeInRepo(ctx context.Context, targetId string, hostSetIds []string, version uint32) (target.Target, []*target.TargetSet, error) {
|
||||
const op = "targets.(Service).removeInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
_, err = repo.DeleteTargeHostSets(ctx, targetId, version, strutil.RemoveDuplicates(hostSetIds, false))
|
||||
if err != nil {
|
||||
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove host sets from target: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove host sets from target: %v.", err)
|
||||
}
|
||||
out, m, err := repo.LookupTarget(ctx, targetId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after removing host sets"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after removing host sets"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after removing host sets from it.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after removing host sets from it.")
|
||||
}
|
||||
return toProto(out, m)
|
||||
return out, m, nil
|
||||
}
|
||||
|
||||
func (s Service) authResult(ctx context.Context, id string, a action.Type, lookupOpt ...target.Option) auth.VerifyResults {
|
||||
@@ -829,41 +962,76 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type, looku
|
||||
return ret
|
||||
}
|
||||
|
||||
func toProto(in target.Target, m []*target.TargetSet) (*pb.Target, error) {
|
||||
out := pb.Target{
|
||||
Id: in.GetPublicId(),
|
||||
ScopeId: in.GetScopeId(),
|
||||
CreatedTime: in.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
|
||||
Version: in.GetVersion(),
|
||||
Type: target.TcpTargetType.String(),
|
||||
SessionMaxSeconds: wrapperspb.UInt32(in.GetSessionMaxSeconds()),
|
||||
SessionConnectionLimit: wrapperspb.Int32(in.GetSessionConnectionLimit()),
|
||||
func toProto(ctx context.Context, in target.Target, m []*target.TargetSet, opt ...handlers.Option) (*pb.Target, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building target proto")
|
||||
}
|
||||
if in.GetDescription() != "" {
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.Target{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.GetPublicId()
|
||||
}
|
||||
if outputFields.Has(globals.ScopeIdField) {
|
||||
out.ScopeId = in.GetScopeId()
|
||||
}
|
||||
if outputFields.Has(globals.TypeField) {
|
||||
out.Type = target.TcpTargetType.String()
|
||||
}
|
||||
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
|
||||
out.Description = wrapperspb.String(in.GetDescription())
|
||||
}
|
||||
if in.GetName() != "" {
|
||||
if outputFields.Has(globals.NameField) && in.GetName() != "" {
|
||||
out.Name = wrapperspb.String(in.GetName())
|
||||
}
|
||||
if in.GetWorkerFilter() != "" {
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.GetCreateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.UpdatedTimeField) {
|
||||
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.VersionField) {
|
||||
out.Version = in.GetVersion()
|
||||
}
|
||||
if outputFields.Has(globals.SessionMaxSecondsField) {
|
||||
out.SessionMaxSeconds = wrapperspb.UInt32(in.GetSessionMaxSeconds())
|
||||
}
|
||||
if outputFields.Has(globals.SessionConnectionLimitField) {
|
||||
out.SessionConnectionLimit = wrapperspb.Int32(in.GetSessionConnectionLimit())
|
||||
}
|
||||
if outputFields.Has(globals.WorkerFilterField) && in.GetWorkerFilter() != "" {
|
||||
out.WorkerFilter = wrapperspb.String(in.GetWorkerFilter())
|
||||
}
|
||||
attrs := &pb.TcpTargetAttributes{}
|
||||
if in.GetDefaultPort() > 0 {
|
||||
attrs.DefaultPort = &wrappers.UInt32Value{Value: in.GetDefaultPort()}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
st, err := handlers.ProtoToStruct(attrs)
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "failed building password attribute struct: %v", err)
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
out.AuthorizedActions = opts.WithAuthorizedActions
|
||||
}
|
||||
out.Attributes = st
|
||||
for _, hs := range m {
|
||||
out.HostSetIds = append(out.HostSetIds, hs.GetPublicId())
|
||||
out.HostSets = append(out.HostSets, &pb.HostSet{
|
||||
Id: hs.GetPublicId(),
|
||||
HostCatalogId: hs.GetCatalogId(),
|
||||
})
|
||||
if outputFields.Has(globals.HostSetIdsField) {
|
||||
for _, hs := range m {
|
||||
out.HostSetIds = append(out.HostSetIds, hs.GetPublicId())
|
||||
}
|
||||
}
|
||||
if outputFields.Has(globals.HostSetsField) {
|
||||
for _, hs := range m {
|
||||
out.HostSets = append(out.HostSets, &pb.HostSet{
|
||||
Id: hs.GetPublicId(),
|
||||
HostCatalogId: hs.GetCatalogId(),
|
||||
})
|
||||
}
|
||||
}
|
||||
if outputFields.Has(globals.AttributesField) {
|
||||
attrs := &pb.TcpTargetAttributes{}
|
||||
if in.GetDefaultPort() > 0 {
|
||||
attrs.DefaultPort = &wrappers.UInt32Value{Value: in.GetDefaultPort()}
|
||||
}
|
||||
st, err := handlers.ProtoToStruct(attrs)
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "failed building password attribute struct: %v", err)
|
||||
}
|
||||
out.Attributes = st
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
@@ -243,14 +243,29 @@ func TestList(t *testing.T) {
|
||||
s, err := testService(t, conn, kms, wrapper)
|
||||
require.NoError(err, "Couldn't create new host set service.")
|
||||
|
||||
// Test with non-anon user
|
||||
got, gErr := s.ListTargets(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListTargets(%q) got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
|
||||
} else {
|
||||
require.NoError(gErr)
|
||||
return
|
||||
}
|
||||
require.NoError(gErr)
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListTargets(%q) scope %q, got response %q, wanted %q", tc.name, tc.req.GetScopeId(), got, tc.res)
|
||||
|
||||
// Test with anon user
|
||||
got, gErr = s.ListTargets(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, item := range got.GetItems() {
|
||||
require.Empty(item.Version)
|
||||
require.Nil(item.CreatedTime)
|
||||
require.Nil(item.UpdatedTime)
|
||||
require.Nil(item.SessionMaxSeconds)
|
||||
require.Nil(item.SessionConnectionLimit)
|
||||
require.Empty(item.WorkerFilter)
|
||||
require.Nil(item.Attributes)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc"
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/iam/store"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common/scopeids"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
@@ -104,21 +106,41 @@ func (s Service) ListUsers(ctx context.Context, req *pbs.ListUsersRequest) (*pbs
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ul) == 0 {
|
||||
return &pbs.ListUsersResponse{}, nil
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.User, 0, len(ul))
|
||||
res := &perms.Resource{
|
||||
res := perms.Resource{
|
||||
Type: resource.User,
|
||||
}
|
||||
for _, item := range ul {
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) == 0 {
|
||||
res.Id = item.GetPublicId()
|
||||
res.ScopeId = item.GetScopeId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.GetScopeId()]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, item, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
@@ -128,6 +150,8 @@ func (s Service) ListUsers(ctx context.Context, req *pbs.ListUsersRequest) (*pbs
|
||||
|
||||
// GetUsers implements the interface pbs.UserServiceServer.
|
||||
func (s Service) GetUser(ctx context.Context, req *pbs.GetUserRequest) (*pbs.GetUserResponse, error) {
|
||||
const op = "users.(Service).GetUser"
|
||||
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -135,17 +159,37 @@ func (s Service) GetUser(ctx context.Context, req *pbs.GetUserRequest) (*pbs.Get
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.getFromRepo(ctx, req.GetId())
|
||||
u, accts, err := s.getFromRepo(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.GetUserResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, u.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, u, accts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.GetUserResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// CreateUser implements the interface pbs.UserServiceServer.
|
||||
func (s Service) CreateUser(ctx context.Context, req *pbs.CreateUserRequest) (*pbs.CreateUserResponse, error) {
|
||||
const op = "users.(Service).CreateUser"
|
||||
|
||||
if err := validateCreateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -157,13 +201,32 @@ func (s Service) CreateUser(ctx context.Context, req *pbs.CreateUserRequest) (*p
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.CreateUserResponse{Item: u, Uri: fmt.Sprintf("users/%s", u.GetId())}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, u.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, u, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbs.CreateUserResponse{Item: item, Uri: fmt.Sprintf("users/%s", item.GetId())}, nil
|
||||
}
|
||||
|
||||
// UpdateUser implements the interface pbs.UserServiceServer.
|
||||
func (s Service) UpdateUser(ctx context.Context, req *pbs.UpdateUserRequest) (*pbs.UpdateUserResponse, error) {
|
||||
const op = "users.(Service).UpdateUser"
|
||||
|
||||
if err := validateUpdateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -171,13 +234,31 @@ func (s Service) UpdateUser(ctx context.Context, req *pbs.UpdateUserRequest) (*p
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
u, accts, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.UpdateUserResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, u.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, u, accts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.UpdateUserResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// DeleteUser implements the interface pbs.UserServiceServer.
|
||||
@@ -198,6 +279,8 @@ func (s Service) DeleteUser(ctx context.Context, req *pbs.DeleteUserRequest) (*p
|
||||
|
||||
// AddUserAccounts implements the interface pbs.GroupServiceServer.
|
||||
func (s Service) AddUserAccounts(ctx context.Context, req *pbs.AddUserAccountsRequest) (*pbs.AddUserAccountsResponse, error) {
|
||||
const op = "users.(Service).AddUserAccounts"
|
||||
|
||||
if err := validateAddUserAccountsRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -205,17 +288,37 @@ func (s Service) AddUserAccounts(ctx context.Context, req *pbs.AddUserAccountsRe
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.addInRepo(ctx, req.GetId(), req.GetAccountIds(), req.GetVersion())
|
||||
u, accts, err := s.addInRepo(ctx, req.GetId(), req.GetAccountIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.AddUserAccountsResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, u.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, u, accts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.AddUserAccountsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// SetUserAccounts implements the interface pbs.GroupServiceServer.
|
||||
func (s Service) SetUserAccounts(ctx context.Context, req *pbs.SetUserAccountsRequest) (*pbs.SetUserAccountsResponse, error) {
|
||||
const op = "users.(Service).SetUserAccounts"
|
||||
|
||||
if err := validateSetUserAccountsRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -223,17 +326,37 @@ func (s Service) SetUserAccounts(ctx context.Context, req *pbs.SetUserAccountsRe
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.setInRepo(ctx, req.GetId(), req.GetAccountIds(), req.GetVersion())
|
||||
u, accts, err := s.setInRepo(ctx, req.GetId(), req.GetAccountIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.SetUserAccountsResponse{Item: u}, nil
|
||||
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, u.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, u, accts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.SetUserAccountsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
// RemoveUserAccounts implements the interface pbs.GroupServiceServer.
|
||||
func (s Service) RemoveUserAccounts(ctx context.Context, req *pbs.RemoveUserAccountsRequest) (*pbs.RemoveUserAccountsResponse, error) {
|
||||
const op = "users.(Service).RemoveUserAccounts"
|
||||
|
||||
if err := validateRemoveUserAccountsRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -241,34 +364,52 @@ func (s Service) RemoveUserAccounts(ctx context.Context, req *pbs.RemoveUserAcco
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
u, err := s.removeInRepo(ctx, req.GetId(), req.GetAccountIds(), req.GetVersion())
|
||||
u, accts, err := s.removeInRepo(ctx, req.GetId(), req.GetAccountIds(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scope = authResults.Scope
|
||||
u.AuthorizedActions = authResults.FetchActionSetForId(ctx, u.Id, IdActions).Strings()
|
||||
return &pbs.RemoveUserAccountsResponse{Item: u}, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.User, error) {
|
||||
repo, err := s.repoFn()
|
||||
outputFields, ok := requests.OutputFields(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.Internal, op, "no request context found")
|
||||
}
|
||||
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, u.GetPublicId(), IdActions).Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, u, accts, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbs.RemoveUserAccountsResponse{Item: item}, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*iam.User, []string, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
u, accts, err := repo.LookupUser(ctx, id)
|
||||
if err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, handlers.NotFoundErrorf("User %q doesn't exist.", id)
|
||||
return nil, nil, handlers.NotFoundErrorf("User %q doesn't exist.", id)
|
||||
}
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if u == nil {
|
||||
return nil, handlers.NotFoundErrorf("User %q doesn't exist.", id)
|
||||
return nil, nil, handlers.NotFoundErrorf("User %q doesn't exist.", id)
|
||||
}
|
||||
return toProto(u, accts), nil
|
||||
return u, accts, nil
|
||||
}
|
||||
|
||||
func (s Service) createInRepo(ctx context.Context, orgId string, item *pb.User) (*pb.User, error) {
|
||||
func (s Service) createInRepo(ctx context.Context, orgId string, item *pb.User) (*iam.User, error) {
|
||||
const op = "users.(Service).createInRepo"
|
||||
var opts []iam.Option
|
||||
if item.GetName() != nil {
|
||||
@@ -292,10 +433,10 @@ func (s Service) createInRepo(ctx context.Context, orgId string, item *pb.User)
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create user but no error returned from repository.")
|
||||
}
|
||||
return toProto(out, nil), nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) updateInRepo(ctx context.Context, orgId, id string, mask []string, item *pb.User) (*pb.User, error) {
|
||||
func (s Service) updateInRepo(ctx context.Context, orgId, id string, mask []string, item *pb.User) (*iam.User, []string, error) {
|
||||
const op = "users.(Service).updateInRepo"
|
||||
var opts []iam.Option
|
||||
if desc := item.GetDescription(); desc != nil {
|
||||
@@ -307,25 +448,25 @@ func (s Service) updateInRepo(ctx context.Context, orgId, id string, mask []stri
|
||||
version := item.GetVersion()
|
||||
u, err := iam.NewUser(orgId, opts...)
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build user for update: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build user for update: %v.", err)
|
||||
}
|
||||
u.PublicId = id
|
||||
dbMask := maskManager.Translate(mask)
|
||||
if len(dbMask) == 0 {
|
||||
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
|
||||
return nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
|
||||
}
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
out, accts, rowsUpdated, err := repo.UpdateUser(ctx, u, version, dbMask)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to update user"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to update user"))
|
||||
}
|
||||
if rowsUpdated == 0 {
|
||||
return nil, handlers.NotFoundErrorf("User %q doesn't exist or incorrect version provided.", id)
|
||||
return nil, nil, handlers.NotFoundErrorf("User %q doesn't exist or incorrect version provided.", id)
|
||||
}
|
||||
return toProto(out, accts), nil
|
||||
return out, accts, nil
|
||||
}
|
||||
|
||||
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
@@ -344,7 +485,7 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.User, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*iam.User, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -353,71 +494,67 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*pb.Use
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var outUl []*pb.User
|
||||
for _, u := range ul {
|
||||
outUl = append(outUl, toProto(u, nil))
|
||||
}
|
||||
return outUl, nil
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func (s Service) addInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*pb.User, error) {
|
||||
func (s Service) addInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*iam.User, []string, error) {
|
||||
const op = "users.(Service).addInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
_, err = repo.AddUserAccounts(ctx, userId, version, strutil.RemoveDuplicates(accountIds, false))
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add accounts to user: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add accounts to user: %v.", err)
|
||||
}
|
||||
out, accts, err := repo.LookupUser(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up user after adding accounts"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up user after adding accounts"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup user after adding accounts to it.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup user after adding accounts to it.")
|
||||
}
|
||||
return toProto(out, accts), nil
|
||||
return out, accts, nil
|
||||
}
|
||||
|
||||
func (s Service) setInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*pb.User, error) {
|
||||
func (s Service) setInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*iam.User, []string, error) {
|
||||
const op = "users.(Service).setInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
_, err = repo.SetUserAccounts(ctx, userId, version, strutil.RemoveDuplicates(accountIds, false))
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set accounts for the user: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set accounts for the user: %v.", err)
|
||||
}
|
||||
out, accts, err := repo.LookupUser(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up user after setting accounts"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up user after setting accounts"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup user after setting accounts for it.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup user after setting accounts for it.")
|
||||
}
|
||||
return toProto(out, accts), nil
|
||||
return out, accts, nil
|
||||
}
|
||||
|
||||
func (s Service) removeInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*pb.User, error) {
|
||||
func (s Service) removeInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*iam.User, []string, error) {
|
||||
const op = "users.(Service).removeInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
_, err = repo.DeleteUserAccounts(ctx, userId, version, strutil.RemoveDuplicates(accountIds, false))
|
||||
if err != nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove accounts from user: %v.", err)
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove accounts from user: %v.", err)
|
||||
}
|
||||
out, accts, err := repo.LookupUser(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to look up user after removing accounts"))
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up user after removing accounts"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup user after removing accounts from it.")
|
||||
return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup user after removing accounts from it.")
|
||||
}
|
||||
return toProto(out, accts), nil
|
||||
return out, accts, nil
|
||||
}
|
||||
|
||||
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
|
||||
@@ -459,41 +596,66 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
|
||||
return auth.Verify(ctx, opts...)
|
||||
}
|
||||
|
||||
func toProto(in *iam.User, accts []string) *pb.User {
|
||||
out := pb.User{
|
||||
Id: in.GetPublicId(),
|
||||
ScopeId: in.GetScopeId(),
|
||||
CreatedTime: in.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
|
||||
Version: in.GetVersion(),
|
||||
AccountIds: accts,
|
||||
func toProto(ctx context.Context, in *iam.User, accts []string, opt ...handlers.Option) (*pb.User, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building user proto")
|
||||
}
|
||||
if in.GetDescription() != "" {
|
||||
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.User{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.GetPublicId()
|
||||
}
|
||||
if in.GetName() != "" {
|
||||
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
|
||||
if outputFields.Has(globals.ScopeIdField) {
|
||||
out.ScopeId = in.GetScopeId()
|
||||
}
|
||||
if in.GetPrimaryAccountId() != "" {
|
||||
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
|
||||
out.Description = wrapperspb.String(in.GetDescription())
|
||||
}
|
||||
if outputFields.Has(globals.NameField) && in.GetName() != "" {
|
||||
out.Name = wrapperspb.String(in.GetName())
|
||||
}
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.GetCreateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.UpdatedTimeField) {
|
||||
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
|
||||
}
|
||||
if outputFields.Has(globals.VersionField) {
|
||||
out.Version = in.GetVersion()
|
||||
}
|
||||
if outputFields.Has(globals.AccountIdsField) {
|
||||
out.AccountIds = accts
|
||||
}
|
||||
if outputFields.Has(globals.PrimaryAccountIdField) {
|
||||
out.PrimaryAccountId = in.GetPrimaryAccountId()
|
||||
}
|
||||
if in.GetLoginName() != "" {
|
||||
if outputFields.Has(globals.LoginNameField) {
|
||||
out.LoginName = in.GetLoginName()
|
||||
}
|
||||
if in.GetFullName() != "" {
|
||||
if outputFields.Has(globals.FullNameField) {
|
||||
out.FullName = in.GetFullName()
|
||||
}
|
||||
if in.GetEmail() != "" {
|
||||
if outputFields.Has(globals.EmailField) {
|
||||
out.Email = in.GetEmail()
|
||||
}
|
||||
for _, a := range accts {
|
||||
out.Accounts = append(out.Accounts, &pb.Account{
|
||||
Id: a,
|
||||
// TODO: Update this when an account can be associated with a user from a different scope.
|
||||
ScopeId: in.GetScopeId(),
|
||||
})
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
return &out
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
out.AuthorizedActions = opts.WithAuthorizedActions
|
||||
}
|
||||
if outputFields.Has(globals.AccountsField) {
|
||||
for _, a := range accts {
|
||||
out.Accounts = append(out.Accounts, &pb.Account{
|
||||
Id: a,
|
||||
// TODO: Update this when an account can be associated with a user from a different scope.
|
||||
ScopeId: in.GetScopeId(),
|
||||
})
|
||||
}
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
|
||||
|
||||
@@ -227,7 +227,7 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
|
||||
// Populate these users into the total
|
||||
ctx = auth.DisabledAuthTestContext(repoFn, oWithUsers.GetPublicId())
|
||||
ctx = auth.DisabledAuthTestContext(repoFn, oWithUsers.GetPublicId(), auth.WithUserId("u_auth"))
|
||||
usersInOrg, err := s.ListUsers(ctx, &pbs.ListUsersRequest{ScopeId: oWithUsers.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
totalUsers = append(totalUsers, usersInOrg.GetItems()...)
|
||||
@@ -278,10 +278,12 @@ func TestList(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
require.NoError(err, "Couldn't create new user service.")
|
||||
|
||||
// Test with non-anon user
|
||||
got, gErr := s.ListUsers(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId()), tc.req)
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListUsers(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
return
|
||||
}
|
||||
gotUsers := sortableUsers{
|
||||
users: got.GetItems(),
|
||||
@@ -298,6 +300,18 @@ func TestList(t *testing.T) {
|
||||
tc.res.Items = resUsers.users
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListUsers(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
// Test with anon user
|
||||
|
||||
got, gErr = s.ListUsers(auth.DisabledAuthTestContext(repoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
|
||||
require.NoError(gErr)
|
||||
assert.Len(got.Items, len(tc.res.Items))
|
||||
for _, item := range got.GetItems() {
|
||||
require.Empty(item.Version)
|
||||
require.Nil(item.CreatedTime)
|
||||
require.Nil(item.UpdatedTime)
|
||||
require.Nil(item.Accounts)
|
||||
require.Nil(item.AccountIds)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user