Merge pull request #15085 from hashicorp/ui/feature/net-889-prepopulate-partition-sso-login
ui: NET-889 pre-populate partition SSO login
This commit is contained in:
commit
ec7a7ca7d6
|
@ -53,6 +53,7 @@
|
|||
<authForm.Method @matches="sso">
|
||||
<OidcSelect
|
||||
@dc={{@dc.Name}}
|
||||
@partition={{@partition}}
|
||||
@nspace={{@nspace}}
|
||||
@disabled={{authForm.disabled}}
|
||||
@onchange={{authForm.submit}}
|
||||
|
|
|
@ -1,164 +1,142 @@
|
|||
<StateChart
|
||||
@src={{this.chart}}
|
||||
as |State Guard ChartAction dispatch state|>
|
||||
{{#let
|
||||
(hash
|
||||
State=State
|
||||
Guard=Guard
|
||||
Action=ChartAction
|
||||
dispatch=dispatch
|
||||
state=state
|
||||
)
|
||||
as |chart|}}
|
||||
{{#let
|
||||
(hash
|
||||
reset=(action dispatch "RESET")
|
||||
focus=this.focus
|
||||
disabled=(state-matches state "loading")
|
||||
error=(queue
|
||||
(action dispatch "ERROR")
|
||||
(action (mut this.error) value="error.errors.firstObject")
|
||||
)
|
||||
submit=(queue
|
||||
(action (mut this.value))
|
||||
(action dispatch "SUBMIT")
|
||||
)
|
||||
)
|
||||
as |exported|}}
|
||||
<Guard
|
||||
@name="hasValue"
|
||||
@cond={{this.hasValue}}
|
||||
/>
|
||||
{{!TODO: Call this reset or similar }}
|
||||
<chart.Action
|
||||
@name="clearError"
|
||||
@exec={{queue (action (mut this.error) undefined) (action (mut this.secret) undefined)}}
|
||||
/>
|
||||
<div
|
||||
class="auth-form"
|
||||
...attributes
|
||||
>
|
||||
<StateChart
|
||||
@src={{this.tabsChart}}
|
||||
as |TabState IgnoredGuard IgnoredAction tabDispatch tabState|>
|
||||
{{#if (can 'use SSO')}}
|
||||
<TabNav
|
||||
@items={{array
|
||||
(hash
|
||||
label='Token'
|
||||
selected=(state-matches tabState 'token')
|
||||
<StateChart @src={{this.chart}} as |State Guard ChartAction dispatch state|>
|
||||
{{#let
|
||||
(hash State=State Guard=Guard Action=ChartAction dispatch=dispatch state=state)
|
||||
as |chart|
|
||||
}}
|
||||
{{#let
|
||||
(hash
|
||||
reset=(action dispatch 'RESET')
|
||||
focus=this.focus
|
||||
disabled=(state-matches state 'loading')
|
||||
error=(queue
|
||||
(action dispatch 'ERROR') (action (mut this.error) value='error.errors.firstObject')
|
||||
)
|
||||
(hash
|
||||
label='SSO'
|
||||
selected=(state-matches tabState 'sso')
|
||||
)
|
||||
}}
|
||||
@onclick={{queue (action tabDispatch) (action dispatch "RESET")}}
|
||||
/>
|
||||
{{/if}}
|
||||
<State @matches="error">
|
||||
{{#if this.error.status}}
|
||||
<Notice
|
||||
@type="error"
|
||||
role="alert"
|
||||
as |notice|>
|
||||
<notice.Body>
|
||||
<p>
|
||||
{{#if this.value.Name}}
|
||||
{{#if (eq this.error.status '403')}}
|
||||
<strong>Consul login failed</strong><br />
|
||||
We received a token from your OIDC provider but could not log in to Consul with it.
|
||||
{{else if (eq this.error.status '401')}}
|
||||
<strong>Could not log in to provider</strong><br />
|
||||
The OIDC provider has rejected this access token. Please have an administrator check your auth method configuration.
|
||||
{{else if (eq this.error.status '499')}}
|
||||
<strong>SSO log in window closed</strong><br />
|
||||
The OIDC provider window was closed. Please try again.
|
||||
{{else}}
|
||||
<strong>Error</strong><br />
|
||||
{{this.error.detail}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if (eq this.error.status '403')}}
|
||||
<strong>Invalid token</strong><br />
|
||||
The token entered does not exist. Please enter a valid token to log in.
|
||||
{{else if (eq this.error.status '404')}}
|
||||
<strong>No providers</strong><br />
|
||||
No SSO providers are configured for that Partition.
|
||||
{{else}}
|
||||
<strong>Error</strong><br />
|
||||
{{this.error.detail}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</p>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{/if}}
|
||||
</State>
|
||||
<TabState @matches="token">
|
||||
<form
|
||||
onsubmit={{action dispatch "SUBMIT"}}
|
||||
>
|
||||
<fieldset>
|
||||
<label
|
||||
class={{concat "type-password" (if (and (state-matches state 'error') (not this.error.status)) ' has-error')}}
|
||||
submit=(queue (action (mut this.value)) (action dispatch 'SUBMIT'))
|
||||
)
|
||||
as |exported|
|
||||
}}
|
||||
<Guard @name='hasValue' @cond={{this.hasValue}} />
|
||||
{{!TODO: Call this reset or similar }}
|
||||
<chart.Action
|
||||
@name='clearError'
|
||||
@exec={{queue (action (mut this.error) undefined) (action (mut this.secret) undefined)}}
|
||||
/>
|
||||
<div class='auth-form' ...attributes>
|
||||
<StateChart
|
||||
@src={{this.tabsChart}}
|
||||
as |TabState IgnoredGuard IgnoredAction tabDispatch tabState|
|
||||
>
|
||||
<span>Log in with a token</span>
|
||||
|
||||
{{! Blink/Webkit based seem to leak password inputs }}
|
||||
{{! this will only occur during acceptance testing so }}
|
||||
{{! turn them into text inputs during acceptance testing }}
|
||||
<input
|
||||
{{did-insert (set this 'input')}}
|
||||
disabled={{state-matches state "loading"}}
|
||||
type={{if (eq (env 'environment') 'testing') 'text' 'password'}}
|
||||
name="auth[SecretID]"
|
||||
placeholder="SecretID"
|
||||
value={{this.secret}}
|
||||
oninput={{queue
|
||||
(action (mut this.secret) value="target.value")
|
||||
(action (mut this.value) value="target.value")
|
||||
(action dispatch "TYPING")
|
||||
}}
|
||||
/>
|
||||
<State @matches="error">
|
||||
{{#if (not this.error.status)}}
|
||||
<strong role="alert">
|
||||
Please enter your secret
|
||||
</strong>
|
||||
{{#if (can 'use SSO')}}
|
||||
<TabNav
|
||||
@items={{array
|
||||
(hash label='Token' selected=(state-matches tabState 'token'))
|
||||
(hash label='SSO' selected=(state-matches tabState 'sso'))
|
||||
}}
|
||||
@onclick={{queue (action tabDispatch) (action dispatch 'RESET')}}
|
||||
/>
|
||||
{{/if}}
|
||||
<State @matches='error'>
|
||||
{{#if this.error.status}}
|
||||
<Notice @type='error' role='alert' as |notice|>
|
||||
<notice.Body>
|
||||
<p>
|
||||
{{#if this.value.Name}}
|
||||
{{#if (eq this.error.status '403')}}
|
||||
<strong>Consul login failed</strong><br />
|
||||
We received a token from your OIDC provider but could not log in to Consul
|
||||
with it.
|
||||
{{else if (eq this.error.status '401')}}
|
||||
<strong>Could not log in to provider</strong><br />
|
||||
The OIDC provider has rejected this access token. Please have an
|
||||
administrator check your auth method configuration.
|
||||
{{else if (eq this.error.status '499')}}
|
||||
<strong>SSO log in window closed</strong><br />
|
||||
The OIDC provider window was closed. Please try again.
|
||||
{{else}}
|
||||
<strong>Error</strong><br />
|
||||
{{this.error.detail}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if (eq this.error.status '403')}}
|
||||
<strong>Invalid token</strong><br />
|
||||
The token entered does not exist. Please enter a valid token to log in.
|
||||
{{else if (eq this.error.status '404')}}
|
||||
<strong>No providers</strong><br />
|
||||
No SSO providers are configured for that Partition.
|
||||
{{else}}
|
||||
<strong>Error</strong><br />
|
||||
{{this.error.detail}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</p>
|
||||
</notice.Body>
|
||||
</Notice>
|
||||
{{/if}}
|
||||
</State>
|
||||
</label>
|
||||
</fieldset>
|
||||
<Action
|
||||
@type="submit"
|
||||
disabled={{state-matches state "loading"}}
|
||||
>
|
||||
Log in
|
||||
</Action>
|
||||
</form>
|
||||
</TabState>
|
||||
<TabState @matches='token'>
|
||||
<form onsubmit={{action dispatch 'SUBMIT'}}>
|
||||
<fieldset>
|
||||
<label
|
||||
class={{concat
|
||||
'type-password'
|
||||
(if (and (state-matches state 'error') (not this.error.status)) ' has-error')
|
||||
}}
|
||||
>
|
||||
<span>Log in with a token</span>
|
||||
|
||||
{{yield (assign exported (hash Method=TabState))}}
|
||||
{{! Blink/Webkit based seem to leak password inputs }}
|
||||
{{! this will only occur during acceptance testing so }}
|
||||
{{! turn them into text inputs during acceptance testing }}
|
||||
<input
|
||||
{{did-insert (set this 'input')}}
|
||||
disabled={{state-matches state 'loading'}}
|
||||
type={{if (eq (env 'environment') 'testing') 'text' 'password'}}
|
||||
name='auth[SecretID]'
|
||||
placeholder='SecretID'
|
||||
value={{this.secret}}
|
||||
oninput={{queue
|
||||
(action (mut this.secret) value='target.value')
|
||||
(action (mut this.value) value='target.value')
|
||||
(action dispatch 'TYPING')
|
||||
}}
|
||||
/>
|
||||
<State @matches='error'>
|
||||
{{#if (not this.error.status)}}
|
||||
<strong role='alert'>
|
||||
Please enter your secret
|
||||
</strong>
|
||||
{{/if}}
|
||||
</State>
|
||||
</label>
|
||||
</fieldset>
|
||||
<Action @type='submit' disabled={{state-matches state 'loading'}}>
|
||||
Log in
|
||||
</Action>
|
||||
</form>
|
||||
</TabState>
|
||||
|
||||
<em>
|
||||
Contact your administrator for login credentials.
|
||||
</em>
|
||||
{{yield (assign exported (hash Method=TabState))}}
|
||||
|
||||
<em>
|
||||
Contact your administrator for login credentials.
|
||||
</em>
|
||||
</StateChart>
|
||||
|
||||
</div>
|
||||
<State @matches='loading'>
|
||||
<TokenSource
|
||||
@dc={{@dc}}
|
||||
@nspace={{or this.value.Namespace @nspace}}
|
||||
@partition={{or this.value.Partition @partition}}
|
||||
@type={{if this.value.Name 'oidc' 'secret'}}
|
||||
@value={{this.value}}
|
||||
@onchange={{queue (action dispatch 'RESET') @onsubmit}}
|
||||
@onerror={{queue
|
||||
(action (mut this.error) value='error.errors.firstObject')
|
||||
(action dispatch 'ERROR')
|
||||
}}
|
||||
/>
|
||||
</State>
|
||||
{{/let}}
|
||||
{{/let}}
|
||||
</StateChart>
|
||||
|
||||
|
||||
</div>
|
||||
<State @matches="loading">
|
||||
<TokenSource
|
||||
@dc={{@dc}}
|
||||
@nspace={{or this.value.Namespace @nspace}}
|
||||
@partition={{or this.value.Partition @partition}}
|
||||
@type={{if this.value.Name 'oidc' 'secret'}}
|
||||
@value={{this.value}}
|
||||
@onchange={{queue (action dispatch "RESET") @onsubmit}}
|
||||
@onerror={{queue (action (mut this.error) value="error.errors.firstObject") (action dispatch "ERROR")}}
|
||||
/>
|
||||
</State>
|
||||
{{/let}}
|
||||
{{/let}}
|
||||
</StateChart>
|
|
@ -1,134 +1,117 @@
|
|||
<StateChart
|
||||
@src={{chart}}
|
||||
as |State Guard ChartAction dispatch state|>
|
||||
{{#let
|
||||
(hash
|
||||
State=State
|
||||
Guard=Guard
|
||||
Action=ChartAction
|
||||
dispatch=dispatch
|
||||
state=state
|
||||
)
|
||||
as |chart|}}
|
||||
<StateChart @src={{chart}} as |State Guard ChartAction dispatch state|>
|
||||
{{#let
|
||||
(hash State=State Guard=Guard Action=ChartAction dispatch=dispatch state=state)
|
||||
as |chart|
|
||||
}}
|
||||
|
||||
<div
|
||||
class="oidc-select"
|
||||
...attributes
|
||||
>
|
||||
<State @notMatches="idle">
|
||||
<DataSource
|
||||
@src={{uri '/${partition}/${nspace}/${dc}/oidc/providers'
|
||||
(hash
|
||||
partition=this.partition
|
||||
nspace=@nspace
|
||||
dc=@dc
|
||||
)
|
||||
}}
|
||||
@onchange={{queue (action (mut this.items) value="data") (fn dispatch "SUCCESS")}}
|
||||
@onerror={{queue (fn dispatch "RESET") @onerror}}
|
||||
/>
|
||||
</State>
|
||||
|
||||
<State @matches="loaded">
|
||||
<Action
|
||||
{{on 'click' (queue (set this 'partition' '') (fn dispatch "RESET"))}}
|
||||
class="reset"
|
||||
>
|
||||
Choose different Partition
|
||||
</Action>
|
||||
</State>
|
||||
|
||||
<StateChart
|
||||
@src={{state-chart 'validate'}}
|
||||
as |ignoredState ignoredGuard ignoredAction formDispatch state|>
|
||||
<TextInput
|
||||
@name="partition"
|
||||
@label="Admin Partition"
|
||||
@item={{this}}
|
||||
@validations={{hash
|
||||
partition=(array
|
||||
(hash
|
||||
test='^[a-zA-Z0-9]([a-zA-Z0-9-]{0,62}[a-zA-Z0-9])?$'
|
||||
error='Name must be a valid DNS hostname.'
|
||||
)
|
||||
)
|
||||
}}
|
||||
@placeholder="Enter your Partition"
|
||||
@oninput={{action (mut this.partition) value="target.value"}}
|
||||
@chart={{hash
|
||||
state=state
|
||||
dispatch=formDispatch
|
||||
}}
|
||||
/>
|
||||
|
||||
{{! this belongs to the outer StateChart but we need }}
|
||||
{{! to understand validation state }}
|
||||
<State @matches="idle">
|
||||
<Action
|
||||
{{disabled (or (lt this.partition.length 1) (state-matches state "error"))}}
|
||||
{{on "click" (fn dispatch "LOAD")}}
|
||||
>
|
||||
Choose provider
|
||||
</Action>
|
||||
</State>
|
||||
|
||||
</StateChart>
|
||||
|
||||
<State @matches="loading">
|
||||
<Progress aria-label="Loading" />
|
||||
</State>
|
||||
|
||||
<State @matches="loaded">
|
||||
{{#if (lt this.items.length 3)}}
|
||||
|
||||
<ul>
|
||||
{{#each this.items as |item|}}
|
||||
<li>
|
||||
<Action
|
||||
class={{concat item.Kind '-oidc-provider'}}
|
||||
disabled={{@disabled}}
|
||||
@type="button"
|
||||
{{on 'click' (fn @onchange item)}}
|
||||
>
|
||||
Continue with {{or item.DisplayName item.Name}}{{#if (not-eq item.Namespace 'default')}} ({{item.Namespace}}){{/if}}
|
||||
</Action>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#let (or this.provider (object-at 0 this.items)) as |item|}}
|
||||
|
||||
<OptionInput
|
||||
@label="SSO Provider"
|
||||
@name="provider"
|
||||
@item={{this}}
|
||||
@selected={{item}}
|
||||
@items={{this.items}}
|
||||
@onchange={{action (mut this.provider)}}
|
||||
@disabled={{@disabled}}
|
||||
>
|
||||
<:option as |option|>
|
||||
<span
|
||||
class={{concat option.item.Kind '-oidc-provider'}}
|
||||
>
|
||||
{{or option.item.DisplayName option.item.Name}}{{#if (not-eq option.item.Namespace 'default')}} ({{option.item.Namespace}}){{/if}}
|
||||
</span>
|
||||
</:option>
|
||||
</OptionInput>
|
||||
<div class='oidc-select' ...attributes>
|
||||
<State @notMatches='idle'>
|
||||
<DataSource
|
||||
@src={{uri
|
||||
'/${partition}/${nspace}/${dc}/oidc/providers'
|
||||
(hash partition=this.partition nspace=@nspace dc=@dc)
|
||||
}}
|
||||
@onchange={{queue (action (mut this.items) value='data') (fn dispatch 'SUCCESS')}}
|
||||
@onerror={{queue (fn dispatch 'RESET') @onerror}}
|
||||
/>
|
||||
</State>
|
||||
|
||||
<State @matches='loaded'>
|
||||
<Action
|
||||
@type="button"
|
||||
{{disabled @disabled}}
|
||||
{{on 'click' (fn @onchange item)}}
|
||||
{{on 'click' (queue (set this 'partition' '') (fn dispatch 'RESET'))}}
|
||||
class='reset'
|
||||
>
|
||||
Log in
|
||||
Choose different Partition
|
||||
</Action>
|
||||
</State>
|
||||
|
||||
{{/let}}
|
||||
{{/if}}
|
||||
</State>
|
||||
<StateChart
|
||||
@src={{state-chart 'validate'}}
|
||||
as |ignoredState ignoredGuard ignoredAction formDispatch state|
|
||||
>
|
||||
<TextInput
|
||||
@name='partition'
|
||||
@label='Admin Partition'
|
||||
@item={{this}}
|
||||
@validations={{hash
|
||||
partition=(array
|
||||
(hash
|
||||
test='^[a-zA-Z0-9]([a-zA-Z0-9-]{0,62}[a-zA-Z0-9])?$'
|
||||
error='Name must be a valid DNS hostname.'
|
||||
)
|
||||
)
|
||||
}}
|
||||
@placeholder='Enter your Partition'
|
||||
@oninput={{action (mut this.partition) value='target.value'}}
|
||||
@chart={{hash state=state dispatch=formDispatch}}
|
||||
/>
|
||||
|
||||
{{! this belongs to the outer StateChart but we need }}
|
||||
{{! to understand validation state }}
|
||||
<State @matches='idle'>
|
||||
<Action
|
||||
{{disabled (or (lt this.partition.length 1) (state-matches state 'error'))}}
|
||||
{{on 'click' (fn dispatch 'LOAD')}}
|
||||
>
|
||||
Choose provider
|
||||
</Action>
|
||||
</State>
|
||||
|
||||
</StateChart>
|
||||
|
||||
<State @matches='loading'>
|
||||
<Progress aria-label='Loading' />
|
||||
</State>
|
||||
|
||||
<State @matches='loaded'>
|
||||
{{#if (lt this.items.length 3)}}
|
||||
|
||||
<ul>
|
||||
{{#each this.items as |item|}}
|
||||
<li>
|
||||
<Action
|
||||
class={{concat item.Kind '-oidc-provider'}}
|
||||
disabled={{@disabled}}
|
||||
@type='button'
|
||||
{{on 'click' (fn @onchange item)}}
|
||||
>
|
||||
Continue with
|
||||
{{or item.DisplayName item.Name}}{{#if (not-eq item.Namespace 'default')}}
|
||||
({{item.Namespace}}){{/if}}
|
||||
</Action>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#let (or this.provider (object-at 0 this.items)) as |item|}}
|
||||
|
||||
<OptionInput
|
||||
@label='SSO Provider'
|
||||
@name='provider'
|
||||
@item={{this}}
|
||||
@selected={{item}}
|
||||
@items={{this.items}}
|
||||
@onchange={{action (mut this.provider)}}
|
||||
@disabled={{@disabled}}
|
||||
>
|
||||
<:option as |option|>
|
||||
<span class={{concat option.item.Kind '-oidc-provider'}}>
|
||||
{{or option.item.DisplayName option.item.Name}}{{#if
|
||||
(not-eq option.item.Namespace 'default')
|
||||
}} ({{option.item.Namespace}}){{/if}}
|
||||
</span>
|
||||
</:option>
|
||||
</OptionInput>
|
||||
|
||||
<Action @type='button' {{disabled @disabled}} {{on 'click' (fn @onchange item)}}>
|
||||
Log in
|
||||
</Action>
|
||||
|
||||
{{/let}}
|
||||
{{/if}}
|
||||
</State>
|
||||
</div>
|
||||
{{/let}}
|
||||
</StateChart>
|
||||
{{/let}}
|
||||
</StateChart>
|
||||
|
|
|
@ -4,9 +4,14 @@ import { tracked } from '@glimmer/tracking';
|
|||
import chart from './chart.xstate';
|
||||
|
||||
export default class OidcSelect extends Component {
|
||||
@tracked partition = '';
|
||||
@tracked partition = 'default';
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.chart = chart;
|
||||
|
||||
if (this.args.partition) {
|
||||
this.partition = this.args.partition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ Feature: login
|
|||
---
|
||||
And I click login on the navigation
|
||||
And I click "[data-test-tab=tab_sso] button"
|
||||
Then the "[name='partition']" input should have the value "default"
|
||||
And I type "partition" into "[name=partition]"
|
||||
And I click ".oidc-select button"
|
||||
Then a GET request was made to "/v1/internal/ui/oidc-auth-methods?dc=dc-1&ns=@namespace&partition=partition"
|
||||
|
|
|
@ -85,5 +85,13 @@ export default function (scenario, assert, pauseUntil, find, currentURL, clipboa
|
|||
})
|
||||
.then(['the title should be "$title"'], function (title) {
|
||||
assert.equal(document.title, title, `Expected the document.title to equal "${title}"`);
|
||||
})
|
||||
.then(['the "$selector" input should have the value "$value"'], function (selector, value) {
|
||||
const $el = find(selector);
|
||||
assert.equal(
|
||||
$el.value,
|
||||
value,
|
||||
`Expected the input at ${selector} to have value ${value}, but it had ${$el.value}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue