-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UI: LDAP Hierarchical roles #28824
UI: LDAP Hierarchical roles #28824
Conversation
@@ -3,46 +3,92 @@ | |||
* SPDX-License-Identifier: BUSL-1.1 | |||
*/ | |||
|
|||
import NamedPathAdapter from 'vault/adapters/named-path'; | |||
import ApplicationAdapter from 'vault/adapters/application'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can't use name
as the primaryKey because static and dynamic roles can technically have the same name
import { encodePath } from 'vault/utils/path-encoding-helpers'; | ||
import { service } from '@ember/service'; | ||
import AdapterError from '@ember-data/adapter/error'; | ||
import { addManyToArray } from 'vault/helpers/add-to-array'; | ||
import sortObjects from 'vault/utils/sort-objects'; | ||
|
||
export default class LdapRoleAdapter extends NamedPathAdapter { | ||
export const ldapRoleID = (type, name) => `type:${type}::name:${name}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is because ember data likes IDs and Vault doesn't return them. Pulled this into a helper here so it can be reused in tests and mirage so this didn't have to be updated in multiple places
ui/app/adapters/ldap/role.js
Outdated
urlForUpdateRecord(name, modelName, snapshot) { | ||
const { backend, type } = snapshot.record; | ||
return this.getURL(backend, this.pathForRoleType(type), name); | ||
createOrUpdate(store, modelSchema, snapshot) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we're no longer extending the NamedPathAdapter
, had to add these methods to manually return ID info
CI Results: |
return { id: ldapRoleID(type, name), backend, name, type }; | ||
} | ||
|
||
_getURL(backend, path, name) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added _
to denote private methods that are only used within this adapter to clearly distinguish them from builtin adapter methods
Build Results: |
…lt into ui/ldap-hierarchical-roles
const options = this.args.roles | ||
// hierarchical roles are not selectable | ||
.filter((r: LdapRoleModel) => !r.name.endsWith('/')) | ||
// *hack alert* - type is set as id so it renders beside name in search select |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice work. That PR description is what dreams are made of. One question, nothing blocking.
@hasChevron={{false}} | ||
data-test-popup-menu-trigger="{{role.type}} {{role.name}}" | ||
/> | ||
{{#if (this.isHierarchical role.name)}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 Does this need fn
wrapped around, so it's clear you're passing role.name into this.isHierarchical()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually a template helper so it has slightly different syntax 😄 Docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL, thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice 🧼 :)
...ldapBreadcrumbs(roleAncestry.path_to_role, roleAncestry.type, backendModel.id, true), | ||
]; | ||
|
||
// must call 'set' so breadcrumbs update as we navigate through directories |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good call out.
import type Transition from '@ember/routing/transition'; | ||
import type LdapRoleModel from 'vault/models/ldap/role'; | ||
import type SecretEngineModel from 'vault/models/secret-engine'; | ||
import type Controller from '@ember/controller'; | ||
import type { Breadcrumb } from 'vault/vault/app-types'; | ||
|
||
interface LdapRolesRouteModel { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these are locally type definitions, I find simpler names to be easier to read and follow. WhenTheyGetReallyLong it's hard to map them back to their definition in my opinion 😅
Description
This PR adds hierarchical role navigation to the UI for LDAP roles which was added to the CLI in 1.17 (Hierarchical Paths). These changes were nontrivial due to the API and GUI design, which I'll outline below for consideration during future list views and navigation with path style resources.
Screen.Recording.2024-11-04.at.12.10.31.PM.mov
Listing hierarchical roles in LDAP returns only top-level items. For example, this
ldap
engine has the dynamic role paths:admin/role1
,admin/nested/role2
,my-role
. A LIST request has to be made at each level to glean this. Roles are also dependent on:type
and so the same must be done for static roles withstatic-role
in the path instead ofrole
.Considerations
Existing list patterns
KV v2 has a similar pattern because secret paths can be nested (i.e.
nested/path/to/secret
). The route file defines separate routes,list
is for listing secrets in the engine andlist-directory
is for secret paths that end in a forward slash. The list route inherits from list-directory.js because much of the routing logic is the same. In LDAP, I chose to create a base route model class to share thelazyPaginatedQuery
method but maintain separate model hooks.The major difference here is in LDAP navigating through hierarchical roles does not update the filter input and
pageFilter
maps one to one to the filter input. I opted for this to separate concerns: url paths and navigation is unrelated to filtering the list results which is the responsibility of query params.On the top, navigating into the
admin/
subdirectory changes the URL and breadcrumbs, but the filter is not updated unless a user wants to filter within that directory. In KV v2, clicking into a subdirectory adds that path to the filter, even though the URL is not updated with the correspondingpageFilter
In both engines, filtering updates the query params, but in KV v2 only the suffix is included (which makes sense because we're inside that secret path, however it's confusing UX because the filter input does not match the URL query params)
Pagination
The original implementation of LDAP roles uses the same model
ldap/role
for both dynamic and static roles. Typically, we want model schemas to map closely to the API params (see Models docs for our expected ember data model patterns). Following this pattern,ldap/role
should have been a base class with any shared params (likename
) and for defining adapter/serializer methods as those are the same. Static and dynamic role models would inherit this class and then define type-specific fields and params (see #28829 for what this would look like).Abandoned because the list view displays both static and dynamic roles and our client-side pagination service relies on these being the same model.
SearchSelect
The API structure of LDAP roles makes components like
SearchSelect
unusable out of the box as it requires aLIST
request is made at each level. For the first iteration of the hierarchical roles implementation, we decided to filter out pathed roles, so only top-level roles are available for quickly generating credentials here:TODO only if you're a HashiCorp employee
to N, N-1, and N-2, using the
backport/ent/x.x.x+ent
labels. If this PR is in the CE repo, you should only backport to N, using thebackport/x.x.x
label, not the enterprise labels.of a public function, even if that change is in a CE file, double check that
applying the patch for this PR to the ENT repo and running tests doesn't
break any tests. Sometimes ENT only tests rely on public functions in CE
files.
in the PR description, commit message, or branch name.
description. Also, make sure the changelog is in this PR, not in your ENT PR.