From 52cf2c0c5384d126b65bcbb69a666f6a427be6e1 Mon Sep 17 00:00:00 2001 From: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com> Date: Tue, 18 Jul 2023 15:12:47 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20ldap=20auth-method=20new?= =?UTF-8?q?=20form=20component=20(#1782)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 ldap auth-method new form component βœ… Closes: ICU-10073 * style: πŸ’„ fix linting error and add comment * style: πŸ’„ add spacing and make titles more bold * Update ui/admin/app/components/form/auth-method/ldap/index.hbs Co-authored-by: Dharini Jeeva * refactor: πŸ’‘ pr feedback updates --------- Co-authored-by: Dharini Jeeva --- .../api/addon/generated/models/auth-method.js | 6 + .../mirage/generated/factories/auth-method.js | 1 + addons/core/translations/resources/en-us.yaml | 79 +++ .../app/components/form/auth-method/index.hbs | 2 +- .../form/auth-method/ldap/index.hbs | 662 ++++++++++++++++++ .../components/form/auth-method/ldap/index.js | 81 +++ .../form/auth-method/oidc/index.hbs | 12 +- .../app/routes/scopes/scope/auth-methods.js | 9 +- ui/admin/app/styles/app.scss | 10 + .../scope/auth-methods/auth-method/index.hbs | 2 +- .../scopes/scope/auth-methods/new.hbs | 2 +- 11 files changed, 853 insertions(+), 13 deletions(-) create mode 100644 ui/admin/app/components/form/auth-method/ldap/index.hbs create mode 100644 ui/admin/app/components/form/auth-method/ldap/index.js diff --git a/addons/api/addon/generated/models/auth-method.js b/addons/api/addon/generated/models/auth-method.js index b13a3fd71..36a82026a 100644 --- a/addons/api/addon/generated/models/auth-method.js +++ b/addons/api/addon/generated/models/auth-method.js @@ -221,6 +221,12 @@ export default class GeneratedAuthMethodModel extends BaseModel { }) enable_groups; + @attr('boolean', { + for: 'ldap', + isNestedAttribute: true, + }) + use_token_groups; + @attr('string', { for: 'ldap', isNestedAttribute: true, diff --git a/addons/api/mirage/generated/factories/auth-method.js b/addons/api/mirage/generated/factories/auth-method.js index 0385f023d..67bfb4196 100644 --- a/addons/api/mirage/generated/factories/auth-method.js +++ b/addons/api/mirage/generated/factories/auth-method.js @@ -73,6 +73,7 @@ export default Factory.extend({ 'preferredName=fullName', 'preferredEmail=email', ], + use_token_groups: false, }; break; } diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index db9086208..4d5914ab6 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -44,6 +44,8 @@ auth-method: new-from-claim: New From Claim new-to-claim: New To Claim new-certificate: New Certificate + from-attr: From Attribute + to-attr: To Attribute types: password: Password oidc: OIDC @@ -62,6 +64,83 @@ auth-method: make-primary-confirm: 'A primary auth method auto-creates users on their first authentication attempt if needed. Note: if you change the primary auth method, the previous primary will no longer auto-create users.' remove-as-primary-confirm: Removing the primary auth method may lead to authentication failures for this scope. no-see-window: Don't see the new window? + form: + urls: + label: Server address (URL and port) + help: 'The domain name or IP address of the LDAP server. (Example: ldap.example.com:389)' + certificates: + label: Certificates + help: PEM encoded x509 certificates in ASN.1 DER form that can be used as trust anchors when connecting to an LDAP provider. + client_certificate: + label: Client TLS Certificate + help: Used to provide to the LDAP server for mTLS connections. Must be x509 PEM encoded. + client_certificate_key: + label: Client TLS Key + help: Used with the ClientTLSCert for mTLS connections to the LDAP server. Must be x509 PEM encoded. + start_tls: + label: Use StartTLS + help: Use StartTLS to create a secure connection. + insecure_tls: + label: Skip SSL Verification + help: Skip SSL certificate verification for LDAP server. Use with caution! + bind_dn: + label: Bind DN + help: Distinguished name of entry to bind when performing user and group search. + bind_password: + label: Bind Password + help: Password to use with BindDN when searching for user. + upn_domain: + label: UPN Domain + help: Add this domain to the username when authenticating. + discover_dn: + label: Discover DN + help: Use anonymous bind to discover the bind DN of a user. + anon_group_search: + label: Anonymous Group Search + help: Use anonymous binds when searching for LDAP groups. + user_dn: + label: User DN + help: Base DN for user search. + user_attr: + label: User Attribute + help: Attribute on user entry matching the username passed during authentication. + user_filter: + label: User Filter + help: Go template used to filter users based on LDAP attributes. + group_dn: + label: Group DN + help: LDAP search base to use for group membership search. + group_attr: + label: Group Attribute + help: LDAP attribute to follow to enumerate user group membership. Default is β€œcn”. + group_filter: + label: Group Filter + help: Template used when constructing the group membership query. + enable_groups: + label: Enable Groups + help: Find an authenticated user's groups during authentication. + use_token_groups: + label: Use Token Groups + help: Active Directory only. Uses a user's tokenGroups attribute to find their group memberships. + account_attribute_maps: + label: Account Attribute Maps + help: Attribute maps from custom attributes to the standard fullname and email account attributes. These maps are represented as key=value where the key equals the from_attribute, and the value equals the to_attribute. For example, "preferredName=fullName". + section: + connection: + title: Connection + help: This group includes inputs for configuring the LDAP server itself, such as the server name, port, and authentication settings. + authenticated-search: + title: Authenticated search + help: Used when Boundary authenticates to the LDAP server. + anonymous-search: + title: Anonymous search + help: Allows you to bind anonymously to the directory when searching for user entries. + user-entries: + title: User entries + help: Before authenticating an end user, Boundary must first find the end user’s entry in the directory. + group-entries: + title: Group entries + help: After authenticating an end user, we need to find out which groups the user is a member of. The configuration for this can vary depending on your LDAP server and your directory schema. group: title: Group title_plural: Groups diff --git a/ui/admin/app/components/form/auth-method/index.hbs b/ui/admin/app/components/form/auth-method/index.hbs index 9d7e8b293..edc84fc09 100644 --- a/ui/admin/app/components/form/auth-method/index.hbs +++ b/ui/admin/app/components/form/auth-method/index.hbs @@ -12,6 +12,6 @@ cancel=@cancel removeItemByIndex=@removeItemByIndex addStringItem=@addStringItem - addAccountClaimMapItem=@addAccountClaimMapItem + addAccountMapItem=@addAccountMapItem }} {{/if}} \ No newline at end of file diff --git a/ui/admin/app/components/form/auth-method/ldap/index.hbs b/ui/admin/app/components/form/auth-method/ldap/index.hbs new file mode 100644 index 000000000..7516ed675 --- /dev/null +++ b/ui/admin/app/components/form/auth-method/ldap/index.hbs @@ -0,0 +1,662 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: MPL-2.0 +}} + + + + + {{t 'form.type.label'}} + + + + {{t 'form.name.label'}} + {{t 'form.name.help'}} + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'form.description.label'}} + {{t 'form.description.help'}} + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + +
+

{{t + 'resources.auth-method.section.connection.title' + }}

+ {{t + 'resources.auth-method.section.connection.help' + }} +
+ + + {{t 'resources.auth-method.form.urls.label'}} + {{t 'resources.auth-method.form.urls.help'}} + {{#if @model.errors.urls}} + + {{#each @model.errors.urls as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.auth-method.form.certificates.label'}} + {{t + 'resources.auth-method.form.certificates.help' + }} + {{#let (unique-id) as |valueHeaderID|}} + + + <:head as |H|> + + {{t 'titles.value'}} + {{t 'titles.actions'}} + + + <:body as |B|> + {{#each @model.certificates as |cert i|}} + + + + + + + + + {{/each}} + + + + + + + + + + + + {{/let}} + {{#if @model.errors.certificates}} + + {{#each @model.errors.certificates as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + {{#if @model.isNew}} + + {{t + 'resources.auth-method.form.client_certificate.label' + }} + {{t + 'resources.auth-method.form.client_certificate.help' + }} + {{#if @model.errors.client_certificate}} + + {{#each @model.errors.client_certificate as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t + 'resources.auth-method.form.client_certificate_key.label' + }} + {{t + 'resources.auth-method.form.client_certificate_key.help' + }} + {{#if @model.errors.client_certificate_key}} + + {{#each @model.errors.client_certificate_key as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + {{/if}} + + + {{t 'resources.auth-method.form.start_tls.label'}} + {{t + 'resources.auth-method.form.start_tls.help' + }} + + + + {{t 'resources.auth-method.form.insecure_tls.label'}} + {{t + 'resources.auth-method.form.insecure_tls.help' + }} + + +
+

{{t + 'resources.auth-method.section.authenticated-search.title' + }}

+ {{t + 'resources.auth-method.section.authenticated-search.help' + }} +
+ + + {{t 'resources.auth-method.form.bind_dn.label'}} + {{t 'resources.auth-method.form.bind_dn.help'}} + {{#if @model.errors.bind_dn}} + + {{#each @model.errors.bind_dn as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + {{#if @model.isNew}} + + {{t 'resources.auth-method.form.bind_password.label'}} + {{t + 'resources.auth-method.form.bind_password.help' + }} + {{#if @model.errors.bind_password}} + + {{#each @model.errors.bind_password as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + {{/if}} + + + {{t 'resources.auth-method.form.upn_domain.label'}} + {{t + 'resources.auth-method.form.upn_domain.help' + }} + {{#if @model.errors.upn_domain}} + + {{#each @model.errors.upn_domain as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + +
+

{{t + 'resources.auth-method.section.anonymous-search.title' + }}

+ {{t + 'resources.auth-method.section.anonymous-search.help' + }} +
+ + + {{t 'resources.auth-method.form.discover_dn.label'}} + {{t + 'resources.auth-method.form.discover_dn.help' + }} + + + + {{t + 'resources.auth-method.form.anon_group_search.label' + }} + {{t + 'resources.auth-method.form.anon_group_search.help' + }} + + +
+

{{t + 'resources.auth-method.section.user-entries.title' + }}

+ {{t + 'resources.auth-method.section.user-entries.help' + }} +
+ + + {{t 'resources.auth-method.form.user_dn.label'}} + {{t 'resources.auth-method.form.user_dn.help'}} + {{#if @model.errors.user_dn}} + + {{#each @model.errors.user_dn as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.auth-method.form.user_attr.label'}} + {{t + 'resources.auth-method.form.user_attr.help' + }} + {{#if @model.errors.user_attr}} + + {{#each @model.errors.user_attr as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.auth-method.form.user_filter.label'}} + {{t + 'resources.auth-method.form.user_filter.help' + }} + {{#if @model.errors.user_filter}} + + {{#each @model.errors.user_filter as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t + 'resources.auth-method.form.account_attribute_maps.label' + }} + {{t + 'resources.auth-method.form.account_attribute_maps.help' + }} + {{#let (unique-id) (unique-id) as |fromHeaderID toHeaderID|}} + + + <:head as |H|> + + {{t + 'resources.auth-method.titles.from-attr' + }} + {{t + 'resources.auth-method.titles.to-attr' + }} + {{t 'titles.actions'}} + + + <:body as |B|> + {{#each @model.account_attribute_maps as |attrMap i|}} + + + + + + + + {{#each this.toAttributes as |toAttrOption|}} + + {{/each}} + + + + + + + + {{/each}} + + + + + + + + {{#each this.toAttributes as |toAttrOption|}} + + {{/each}} + + + + + + + + + + + {{/let}} + {{#if @model.errors.account_attribute_maps}} + + {{#each @model.errors.account_attribute_maps as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + +
+

{{t + 'resources.auth-method.section.group-entries.title' + }}

+ {{t + 'resources.auth-method.section.group-entries.help' + }} +
+ + + {{t 'resources.auth-method.form.group_dn.label'}} + {{t + 'resources.auth-method.form.group_dn.help' + }} + {{#if @model.errors.group_dn}} + + {{#each @model.errors.group_dn as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.auth-method.form.group_attr.label'}} + {{t + 'resources.auth-method.form.group_attr.help' + }} + {{#if @model.errors.group_attr}} + + {{#each @model.errors.group_attr as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.auth-method.form.group_filter.label'}} + {{t + 'resources.auth-method.form.group_filter.help' + }} + {{#if @model.errors.group_filter}} + + {{#each @model.errors.group_filter as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.auth-method.form.enable_groups.label'}} + {{t + 'resources.auth-method.form.enable_groups.help' + }} + + + + {{t 'resources.auth-method.form.use_token_groups.label'}} + {{t + 'resources.auth-method.form.use_token_groups.help' + }} + + + {{#if (can 'save model' @model)}} + + {{/if}} +
\ No newline at end of file diff --git a/ui/admin/app/components/form/auth-method/ldap/index.js b/ui/admin/app/components/form/auth-method/ldap/index.js new file mode 100644 index 000000000..26644cc2d --- /dev/null +++ b/ui/admin/app/components/form/auth-method/ldap/index.js @@ -0,0 +1,81 @@ +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; +import { options } from 'api/models/auth-method'; + +export default class FormAuthMethodLdapComponent extends Component { + // =attributes + /** + * @type {object} + */ + toAttributes = options.ldap.account_attribute_maps.to; + + /** + * @type {string} + */ + newToAttribute = options.ldap.account_attribute_maps.to[0]; + + /** + * @type {string} + */ + @tracked newFromAttribute = ''; + + /** + * @type {string} + */ + @tracked newCertificate = ''; + + /** + * @type {string} + */ + @tracked urlsArrayString = this.parseUrlsArray(); + + //actions + + /** + * @param {object} event + */ + @action + toggleField(event) { + const { name: field } = event.target; + this.args.model[field] = !this.args.model[field]; + } + + @action + addCertificate() { + if (this.newCertificate) { + this.args.addStringItem('certificates', this.newCertificate); + } + this.newCertificate = ''; + } + + @action + addAttributeMap() { + if (this.newFromAttribute) { + this.args.addAccountMapItem( + 'account_attribute_maps', + this.newFromAttribute, + this.newToAttribute + ); + } + this.newFromAttribute = ''; + } + + /** + * @param {object} event + */ + @action + setUrls(event) { + const { value } = event.target; + this.urlsArrayString = value; + const array = value.split(',').map((item) => ({ value: item })); + this.args.model.set('urls', array); + } + + /** + * @returns {string} + */ + parseUrlsArray() { + return (this.args.model.urls || []).map((item) => item.value).toString(); + } +} diff --git a/ui/admin/app/components/form/auth-method/oidc/index.hbs b/ui/admin/app/components/form/auth-method/oidc/index.hbs index b4ce7cdee..75c35a11a 100644 --- a/ui/admin/app/components/form/auth-method/oidc/index.hbs +++ b/ui/admin/app/components/form/auth-method/oidc/index.hbs @@ -11,11 +11,6 @@ @showEditToggle={{if @model.isNew false true}} as |form| > - {{t 'actions.add'}} diff --git a/ui/admin/app/routes/scopes/scope/auth-methods.js b/ui/admin/app/routes/scopes/scope/auth-methods.js index b5522e3db..17a22e2e8 100644 --- a/ui/admin/app/routes/scopes/scope/auth-methods.js +++ b/ui/admin/app/routes/scopes/scope/auth-methods.js @@ -191,16 +191,17 @@ export default class ScopesScopeAuthMethodsRoute extends Route { } /** - * Adds an account claim map fragment to the passed OIDC `authMethod`. + * Adds an account map fragment to the passed `authMethod`. * @param {AuthMethodModel} authMethod + * @param {string} field * @param {string} from * @param {string} to */ @action - async addAccountClaimMapItem(authMethod, from, to) { - const existingArray = authMethod.account_claim_maps ?? []; + async addAccountMapItem(authMethod, field, from, to) { + const existingArray = authMethod[field] ?? []; const array = [...existingArray, { from, to }]; - authMethod.set('account_claim_maps', array); + authMethod.set(field, array); } /** diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 58700d1c9..43b62bdc1 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -976,3 +976,13 @@ } } } + +// ldap form sections +.ldap-section { + margin-top: 4rem; + + .hds-form-helper-text { + margin-top: 0.5rem; + margin-bottom: 1.5rem; + } +} diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/index.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/index.hbs index 4e0ee1e05..9a27415c1 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/index.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/index.hbs @@ -29,7 +29,7 @@ @cancel={{route-action 'cancel' @model}} @removeItemByIndex={{route-action 'removeItemByIndex' @model}} @addStringItem={{route-action 'addStringItem' @model}} - @addAccountClaimMapItem={{route-action 'addAccountClaimMapItem' @model}} + @addAccountMapItem={{route-action 'addAccountMapItem' @model}} /> diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/new.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/new.hbs index 81ba9dd6c..efb859739 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/new.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/new.hbs @@ -25,7 +25,7 @@ @cancel={{route-action 'cancel' @model}} @removeItemByIndex={{route-action 'removeItemByIndex' @model}} @addStringItem={{route-action 'addStringItem' @model}} - @addAccountClaimMapItem={{route-action 'addAccountClaimMapItem' @model}} + @addAccountMapItem={{route-action 'addAccountMapItem' @model}} /> \ No newline at end of file