Pre-populate partition on sso login

This commit is contained in:
wenincode 2022-10-19 17:26:25 -06:00
parent 702b63d819
commit f89fc309ff
7 changed files with 297 additions and 285 deletions

View File

@ -51,8 +51,10 @@
/>
{{#if (can "use SSO")}}
<authForm.Method @matches="sso">
{{log (concat "Partition Parent: " @partition)}}
<OidcSelect
@dc={{@dc.Name}}
@partition={{@partition}}
@nspace={{@nspace}}
@disabled={{authForm.disabled}}
@onchange={{authForm.submit}}

View File

@ -1,76 +1,54 @@
<StateChart
@src={{this.chart}}
as |State Guard ChartAction dispatch state|>
<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
State=State
Guard=Guard
Action=ChartAction
dispatch=dispatch
state=state
)
as |chart|}}
{{#let
(hash
reset=(action dispatch "RESET")
reset=(action dispatch 'RESET')
focus=this.focus
disabled=(state-matches state "loading")
disabled=(state-matches state 'loading')
error=(queue
(action dispatch "ERROR")
(action (mut this.error) value="error.errors.firstObject")
(action dispatch 'ERROR') (action (mut this.error) value='error.errors.firstObject')
)
submit=(queue
(action (mut this.value))
(action dispatch "SUBMIT")
submit=(queue (action (mut this.value)) (action dispatch 'SUBMIT'))
)
)
as |exported|}}
<Guard
@name="hasValue"
@cond={{this.hasValue}}
/>
as |exported|
}}
<Guard @name='hasValue' @cond={{this.hasValue}} />
{{!TODO: Call this reset or similar }}
<chart.Action
@name="clearError"
@name='clearError'
@exec={{queue (action (mut this.error) undefined) (action (mut this.secret) undefined)}}
/>
<div
class="auth-form"
...attributes
>
<div class='auth-form' ...attributes>
<StateChart
@src={{this.tabsChart}}
as |TabState IgnoredGuard IgnoredAction tabDispatch tabState|>
as |TabState IgnoredGuard IgnoredAction tabDispatch tabState|
>
{{#if (can 'use SSO')}}
<TabNav
@items={{array
(hash
label='Token'
selected=(state-matches tabState 'token')
)
(hash
label='SSO'
selected=(state-matches tabState 'sso')
)
(hash label='Token' selected=(state-matches tabState 'token'))
(hash label='SSO' selected=(state-matches tabState 'sso'))
}}
@onclick={{queue (action tabDispatch) (action dispatch "RESET")}}
@onclick={{queue (action tabDispatch) (action dispatch 'RESET')}}
/>
{{/if}}
<State @matches="error">
<State @matches='error'>
{{#if this.error.status}}
<Notice
@type="error"
role="alert"
as |notice|>
<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.
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.
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.
@ -95,13 +73,14 @@ as |TabState IgnoredGuard IgnoredAction tabDispatch tabState|>
</Notice>
{{/if}}
</State>
<TabState @matches="token">
<form
onsubmit={{action dispatch "SUBMIT"}}
>
<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')}}
class={{concat
'type-password'
(if (and (state-matches state 'error') (not this.error.status)) ' has-error')
}}
>
<span>Log in with a token</span>
@ -110,30 +89,27 @@ as |TabState IgnoredGuard IgnoredAction tabDispatch tabState|>
{{! turn them into text inputs during acceptance testing }}
<input
{{did-insert (set this 'input')}}
disabled={{state-matches state "loading"}}
disabled={{state-matches state 'loading'}}
type={{if (eq (env 'environment') 'testing') 'text' 'password'}}
name="auth[SecretID]"
placeholder="SecretID"
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")
(action (mut this.secret) value='target.value')
(action (mut this.value) value='target.value')
(action dispatch 'TYPING')
}}
/>
<State @matches="error">
<State @matches='error'>
{{#if (not this.error.status)}}
<strong role="alert">
<strong role='alert'>
Please enter your secret
</strong>
{{/if}}
</State>
</label>
</fieldset>
<Action
@type="submit"
disabled={{state-matches state "loading"}}
>
<Action @type='submit' disabled={{state-matches state 'loading'}}>
Log in
</Action>
</form>
@ -146,17 +122,19 @@ as |TabState IgnoredGuard IgnoredAction tabDispatch tabState|>
</em>
</StateChart>
</div>
<State @matches="loading">
<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")}}
@onchange={{queue (action dispatch 'RESET') @onsubmit}}
@onerror={{queue
(action (mut this.error) value='error.errors.firstObject')
(action dispatch 'ERROR')
}}
/>
</State>
{{/let}}

View File

@ -1,38 +1,25 @@
<StateChart
@src={{chart}}
as |State Guard ChartAction dispatch state|>
<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
)
(hash State=State Guard=Guard Action=ChartAction dispatch=dispatch state=state)
as |chart|
}}
@onchange={{queue (action (mut this.items) value="data") (fn dispatch "SUCCESS")}}
@onerror={{queue (fn dispatch "RESET") @onerror}}
<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">
<State @matches='loaded'>
<Action
{{on 'click' (queue (set this 'partition' '') (fn dispatch "RESET"))}}
class="reset"
{{on 'click' (queue (set this 'partition' '') (fn dispatch 'RESET'))}}
class='reset'
>
Choose different Partition
</Action>
@ -40,10 +27,11 @@ as |chart|}}
<StateChart
@src={{state-chart 'validate'}}
as |ignoredState ignoredGuard ignoredAction formDispatch state|>
as |ignoredState ignoredGuard ignoredAction formDispatch state|
>
<TextInput
@name="partition"
@label="Admin Partition"
@name='partition'
@label='Admin Partition'
@item={{this}}
@validations={{hash
partition=(array
@ -53,20 +41,17 @@ as |chart|}}
)
)
}}
@placeholder="Enter your Partition"
@oninput={{action (mut this.partition) value="target.value"}}
@chart={{hash
state=state
dispatch=formDispatch
}}
@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">
<State @matches='idle'>
<Action
{{disabled (or (lt this.partition.length 1) (state-matches state "error"))}}
{{on "click" (fn dispatch "LOAD")}}
{{disabled (or (lt this.partition.length 1) (state-matches state 'error'))}}
{{on 'click' (fn dispatch 'LOAD')}}
>
Choose provider
</Action>
@ -74,11 +59,11 @@ as |chart|}}
</StateChart>
<State @matches="loading">
<Progress aria-label="Loading" />
<State @matches='loading'>
<Progress aria-label='Loading' />
</State>
<State @matches="loaded">
<State @matches='loaded'>
{{#if (lt this.items.length 3)}}
<ul>
@ -87,10 +72,12 @@ as |chart|}}
<Action
class={{concat item.Kind '-oidc-provider'}}
disabled={{@disabled}}
@type="button"
@type='button'
{{on 'click' (fn @onchange item)}}
>
Continue with {{or item.DisplayName item.Name}}{{#if (not-eq item.Namespace 'default')}} ({{item.Namespace}}){{/if}}
Continue with
{{or item.DisplayName item.Name}}{{#if (not-eq item.Namespace 'default')}}
({{item.Namespace}}){{/if}}
</Action>
</li>
{{/each}}
@ -101,8 +88,8 @@ as |chart|}}
{{#let (or this.provider (object-at 0 this.items)) as |item|}}
<OptionInput
@label="SSO Provider"
@name="provider"
@label='SSO Provider'
@name='provider'
@item={{this}}
@selected={{item}}
@items={{this.items}}
@ -110,19 +97,15 @@ as |chart|}}
@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 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)}}
>
<Action @type='button' {{disabled @disabled}} {{on 'click' (fn @onchange item)}}>
Log in
</Action>

View File

@ -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;
}
}
}

View File

@ -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"
@ -49,3 +50,36 @@ Feature: login
And a POST request was made to "/v1/acl/oidc/callback?dc=dc-1&ns=@!namespace&partition=partition"
And "[data-notification]" has the "notification-authorize" class
And "[data-notification]" has the "success" class
Scenario: Logging in via SSO with a partition chosen
Given 1 datacenter model with the value "dc-1"
And SSO is enabled
And partitions are enabled
And 1 partition model with the value "_example-partition"
And 1 oidcProvider model from yaml
---
- DisplayName: Okta
Name: okta
Kind: okta
---
When I visit the services page for yaml
---
dc: dc-1
partition: example-partition
---
And the "okta" oidcProvider responds with from yaml
---
state: state-123456789/abcdefghijklmnopqrstuvwxyz
code: code-abcdefghijklmnopqrstuvwxyz/123456789
---
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 "example-partition"
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"
And I click ".okta-oidc-provider"
Then a POST request was made to "/v1/acl/oidc/auth-url?dc=dc-1&ns=@!namespace&partition=partition"
And a POST request was made to "/v1/acl/oidc/callback?dc=dc-1&ns=@!namespace&partition=partition"
And "[data-notification]" has the "notification-authorize" class
And "[data-notification]" has the "success" class

View File

@ -66,6 +66,8 @@ export function visitable(path, encoder = encodeURIComponent) {
let path = paths.shift();
if (typeof dynamicSegmentsAndQueryParams.nspace !== 'undefined') {
path = `/:nspace${path}`;
} else if (typeof dynamicSegmentsAndQueryParams.partition !== 'undefined') {
path = `/:partition${path}`;
}
params = assign({}, dynamicSegmentsAndQueryParams);
let fullPath;

View File

@ -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}`
);
});
}