ui: Ensure the partition is passed through to the request for the SSO auth URL (#11979)
* Make sure the mocks reflect the requested partition/namespace * Ensure partition is passed through to the HTTP adapter * Pass AuthMethod object through to TokenSource in order to use Partition * Change up docs and add potential improvements for future * Pass the query partition back onto the response * Make sure the OIDC callback mock returns a Partition * Enable OIDC provider mock overwriting during acceptance testing * Make sure we can enable partitions and SSO post bootup only required ...for now * Wire up oidc provider mocking * Add SSO full auth flow acceptance tests
This commit is contained in:
parent
a217d13e1b
commit
fc8e89d640
|
@ -0,0 +1,4 @@
|
||||||
|
```release-note:bug
|
||||||
|
ui: Ensure partition query parameter is passed through to all OIDC related API
|
||||||
|
requests
|
||||||
|
```
|
|
@ -154,7 +154,7 @@ as |TabState IgnoredGuard IgnoredAction tabDispatch tabState|>
|
||||||
@nspace={{or this.value.Namespace @nspace}}
|
@nspace={{or this.value.Namespace @nspace}}
|
||||||
@partition={{or this.value.Partition @partition}}
|
@partition={{or this.value.Partition @partition}}
|
||||||
@type={{if this.value.Name 'oidc' 'secret'}}
|
@type={{if this.value.Name 'oidc' 'secret'}}
|
||||||
@value={{if this.value.Name this.value.Name this.value}}
|
@value={{this.value}}
|
||||||
@onchange={{queue (action dispatch "RESET") @onsubmit}}
|
@onchange={{queue (action dispatch "RESET") @onsubmit}}
|
||||||
@onerror={{queue (action (mut this.error) value="error.errors.firstObject") (action dispatch "ERROR")}}
|
@onerror={{queue (action (mut this.error) value="error.errors.firstObject") (action dispatch "ERROR")}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -21,6 +21,15 @@ This component **does not store the resulting token**, it only emits it via
|
||||||
its `onchange` argument/event handler. Errors are emitted via the `onerror`
|
its `onchange` argument/event handler. Errors are emitted via the `onerror`
|
||||||
argument/event handler.
|
argument/event handler.
|
||||||
|
|
||||||
|
## Potential improvements
|
||||||
|
|
||||||
|
We could decide to remove the `@type` argument and always require an object
|
||||||
|
passed to `@value` instead of a `String|Object`. Alternatively we could still
|
||||||
|
allow `String|Object`. Then inside the component we could decide whether to
|
||||||
|
use the Consul or SSO depending on the shape of the `@value` argument. All in
|
||||||
|
all this means we can remove the `@type` argument making a slimmer component
|
||||||
|
API.
|
||||||
|
|
||||||
```hbs preview-template
|
```hbs preview-template
|
||||||
<figure>
|
<figure>
|
||||||
<figcaption>Provide a widget to login with</figcaption>
|
<figcaption>Provide a widget to login with</figcaption>
|
||||||
|
@ -75,7 +84,7 @@ argument/event handler.
|
||||||
| `nspace` | `String` | | The name of the current namespace |
|
| `nspace` | `String` | | The name of the current namespace |
|
||||||
| `partition` | `String` | | The name of the current partition |
|
| `partition` | `String` | | The name of the current partition |
|
||||||
| `type` | `String` | | `secret` or `oidc`. `secret` is just traditional login, whereas `oidc` uses the users OIDC provider |
|
| `type` | `String` | | `secret` or `oidc`. `secret` is just traditional login, whereas `oidc` uses the users OIDC provider |
|
||||||
| `value` | `String` | | When `type` is `secret` this should be the users secret. When `type` is `oidc` this should be the name of the `AuthMethod` to use for authentication |
|
| `value` | `String|Object` | | When `type` is `secret` this should be the users secret. When `type` is `oidc` this should be object returned by Consul's AuthMethod HTTP API endpoint |
|
||||||
| `onchange` | `Function` | | The action to fire when the data changes. Emits an Event-like object with a `data` property containing the jwt data, in this case the autorizationCode and the status |
|
| `onchange` | `Function` | | The action to fire when the data changes. Emits an Event-like object with a `data` property containing the jwt data, in this case the autorizationCode and the status |
|
||||||
| `onerror` | `Function` | | The action to fire when an error occurs. Emits ErrorEvent object with an `error` property containing the Error. |
|
| `onerror` | `Function` | | The action to fire when an error occurs. Emits ErrorEvent object with an `error` property containing the Error. |
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,10 @@ as |State Guard Action dispatch state|>
|
||||||
@cond={{this.isSecret}}
|
@cond={{this.isSecret}}
|
||||||
/>
|
/>
|
||||||
{{#let
|
{{#let
|
||||||
(uri '/${partition}/{$nspace}/${dc}'
|
(uri '/${partition}/${nspace}/${dc}'
|
||||||
(hash
|
(hash
|
||||||
partition=@partition
|
partition=(or @value.Partition @partition)
|
||||||
nspace=@nspace
|
nspace=(or @value.Namespace @nspace)
|
||||||
dc=@dc
|
dc=@dc
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -30,7 +30,7 @@ as |State Guard Action dispatch state|>
|
||||||
<DataSource
|
<DataSource
|
||||||
@src={{uri (concat path '/oidc/provider/${value}')
|
@src={{uri (concat path '/oidc/provider/${value}')
|
||||||
(hash
|
(hash
|
||||||
value=@value
|
value=@value.Name
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@onchange={{queue (action (mut this.provider) value="data") (action dispatch "SUCCESS")}}
|
@onchange={{queue (action (mut this.provider) value="data") (action dispatch "SUCCESS")}}
|
||||||
|
|
|
@ -22,6 +22,7 @@ export default class OidcSerializer extends Serializer {
|
||||||
cb(headers, {
|
cb(headers, {
|
||||||
Name: query.id,
|
Name: query.id,
|
||||||
Namespace: query.ns,
|
Namespace: query.ns,
|
||||||
|
Partition: query.partition,
|
||||||
...body,
|
...body,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|
|
@ -40,7 +40,7 @@ export default class OidcProviderService extends RepositoryService {
|
||||||
// with an empty `ns=` Consul will use the namespace that is assigned to
|
// with an empty `ns=` Consul will use the namespace that is assigned to
|
||||||
// the token, and when we get the response we can pick that back off the
|
// the token, and when we get the response we can pick that back off the
|
||||||
// responses `Namespace` property. As we don't receive a `Namespace`
|
// responses `Namespace` property. As we don't receive a `Namespace`
|
||||||
// property here, we have to figure this out ourselves. Biut we also want
|
// property here, we have to figure this out ourselves. But we also want
|
||||||
// to make this completely invisible to 'the application engineer/a
|
// to make this completely invisible to 'the application engineer/a
|
||||||
// template engineer'. This feels like the best place/way to do it as we
|
// template engineer'. This feels like the best place/way to do it as we
|
||||||
// are already in a asynchronous method, and we avoid adding extra 'just
|
// are already in a asynchronous method, and we avoid adding extra 'just
|
||||||
|
@ -54,6 +54,7 @@ export default class OidcProviderService extends RepositoryService {
|
||||||
const token = (await this.settings.findBySlug('token')) || {};
|
const token = (await this.settings.findBySlug('token')) || {};
|
||||||
return super.findBySlug({
|
return super.findBySlug({
|
||||||
ns: params.ns || token.Namespace || 'default',
|
ns: params.ns || token.Namespace || 'default',
|
||||||
|
partition: params.partition || token.Partition || 'default',
|
||||||
dc: params.dc,
|
dc: params.dc,
|
||||||
id: params.id,
|
id: params.id,
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,11 @@
|
||||||
typeof location.search.ns !== 'undefined' ? location.search.ns :
|
typeof location.search.ns !== 'undefined' ? location.search.ns :
|
||||||
typeof http.body.Namespace !== 'undefined' ? http.body.Namespace : 'default'
|
typeof http.body.Namespace !== 'undefined' ? http.body.Namespace : 'default'
|
||||||
}",
|
}",
|
||||||
|
"Partition": "${
|
||||||
|
typeof location.search.partition !== 'undefined' ?
|
||||||
|
location.search.partition :
|
||||||
|
typeof http.body.Partition !== 'undefined' ? http.body.Partition : 'default'
|
||||||
|
}",
|
||||||
"Local": false,
|
"Local": false,
|
||||||
"Description": "AuthMethod: ${http.body.AuthMethod}; Code: ${http.body.Code}; State: ${http.body.State}; - ${fake.lorem.sentence()}",
|
"Description": "AuthMethod: ${http.body.AuthMethod}; Code: ${http.body.Code}; State: ${http.body.State}; - ${fake.lorem.sentence()}",
|
||||||
"Policies": [
|
"Policies": [
|
||||||
|
|
|
@ -16,8 +16,8 @@ return `
|
||||||
"Name": "${name.split(' ').join('-').toLowerCase()}",
|
"Name": "${name.split(' ').join('-').toLowerCase()}",
|
||||||
"DisplayName": "${name}",
|
"DisplayName": "${name}",
|
||||||
"Kind": "${fake.helpers.randomize(['no-icon', 'google', 'okta', 'auth0', 'microsoft'])}",
|
"Kind": "${fake.helpers.randomize(['no-icon', 'google', 'okta', 'auth0', 'microsoft'])}",
|
||||||
"Namespace": "default",
|
"Namespace": "${typeof location.search.ns !== 'undefined' ? location.search.ns : 'default'}",
|
||||||
"Partition": "default"
|
"Partition": "${typeof location.search.partition !== 'undefined' ? location.search.partition : 'default'}"
|
||||||
}
|
}
|
||||||
`})
|
`})
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,3 +19,33 @@ Feature: login
|
||||||
headers:
|
headers:
|
||||||
X-Consul-Token: something
|
X-Consul-Token: something
|
||||||
---
|
---
|
||||||
|
@onlyNamespaceable
|
||||||
|
Scenario: Logging in via SSO
|
||||||
|
Given 1 datacenter model with the value "dc-1"
|
||||||
|
And SSO is enabled
|
||||||
|
And partitions are enabled
|
||||||
|
And 1 oidcProvider model from yaml
|
||||||
|
---
|
||||||
|
- DisplayName: Okta
|
||||||
|
Name: okta
|
||||||
|
Kind: okta
|
||||||
|
---
|
||||||
|
When I visit the services page for yaml
|
||||||
|
---
|
||||||
|
dc: dc-1
|
||||||
|
---
|
||||||
|
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"
|
||||||
|
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
|
||||||
|
|
|
@ -43,6 +43,9 @@ export default function(type, value, doc = document) {
|
||||||
case 'authMethod':
|
case 'authMethod':
|
||||||
key = 'CONSUL_AUTH_METHOD_COUNT';
|
key = 'CONSUL_AUTH_METHOD_COUNT';
|
||||||
break;
|
break;
|
||||||
|
case 'oidcProvider':
|
||||||
|
key = 'CONSUL_OIDC_PROVIDER_COUNT';
|
||||||
|
break;
|
||||||
case 'nspace':
|
case 'nspace':
|
||||||
key = 'CONSUL_NSPACE_COUNT';
|
key = 'CONSUL_NSPACE_COUNT';
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -40,6 +40,9 @@ export default function(type) {
|
||||||
case 'authMethod':
|
case 'authMethod':
|
||||||
requests = ['/v1/acl/auth-methods', '/v1/acl/auth-method/'];
|
requests = ['/v1/acl/auth-methods', '/v1/acl/auth-method/'];
|
||||||
break;
|
break;
|
||||||
|
case 'oidcProvider':
|
||||||
|
requests = ['/v1/internal/ui/oidc-auth-methods'];
|
||||||
|
break;
|
||||||
case 'nspace':
|
case 'nspace':
|
||||||
requests = ['/v1/namespaces', '/v1/namespace/'];
|
requests = ['/v1/namespaces', '/v1/namespace/'];
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -103,9 +103,16 @@ export default function({
|
||||||
let location = context.owner.lookup(`location:${locationType}`);
|
let location = context.owner.lookup(`location:${locationType}`);
|
||||||
return location.getURLFrom();
|
return location.getURLFrom();
|
||||||
};
|
};
|
||||||
|
const oidcProvider = function(name, response) {
|
||||||
|
const context = helpers.getContext();
|
||||||
|
const provider = context.owner.lookup('torii-provider:oidc-with-url');
|
||||||
|
provider.popup.open = async function() {
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
models(library, create, setCookie);
|
models(library, create, setCookie);
|
||||||
http(library, respondWith, setCookie);
|
http(library, respondWith, setCookie, oidcProvider);
|
||||||
visit(library, pages, utils.setCurrentPage, reset);
|
visit(library, pages, utils.setCurrentPage, reset);
|
||||||
click(library, utils.find, helpers.click);
|
click(library, utils.find, helpers.click);
|
||||||
form(library, utils.find, helpers.fillIn, helpers.triggerKeyEvent, utils.getCurrentPage);
|
form(library, utils.find, helpers.fillIn, helpers.triggerKeyEvent, utils.getCurrentPage);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default function(scenario, respondWith, set) {
|
export default function(scenario, respondWith, set, oidc) {
|
||||||
// respondWith should set the url to return a certain response shape
|
// respondWith should set the url to return a certain response shape
|
||||||
scenario
|
scenario
|
||||||
.given(['the url "$endpoint" responds with a $status status'], function(url, status) {
|
.given(['the url "$endpoint" responds with a $status status'], function(url, status) {
|
||||||
|
@ -12,6 +12,9 @@ export default function(scenario, respondWith, set) {
|
||||||
}
|
}
|
||||||
respondWith(url, data);
|
respondWith(url, data);
|
||||||
})
|
})
|
||||||
|
.given(['the "$provider" oidcProvider responds with from yaml\n$yaml'], function(name, data) {
|
||||||
|
oidc(name, data);
|
||||||
|
})
|
||||||
.given('a network latency of $number', function(number) {
|
.given('a network latency of $number', function(number) {
|
||||||
set('CONSUL_LATENCY', number);
|
set('CONSUL_LATENCY', number);
|
||||||
});
|
});
|
||||||
|
|
|
@ -38,6 +38,14 @@ export default function(scenario, create, set, win = window, doc = document) {
|
||||||
.given(['ACLs are disabled'], function() {
|
.given(['ACLs are disabled'], function() {
|
||||||
doc.cookie = `CONSUL_ACLS_ENABLE=0`;
|
doc.cookie = `CONSUL_ACLS_ENABLE=0`;
|
||||||
})
|
})
|
||||||
|
.given(['SSO is enabled'], function() {
|
||||||
|
doc.cookie = `CONSUL_SSO_ENABLE=1`;
|
||||||
|
set('CONSUL_SSO_ENABLE', 1);
|
||||||
|
})
|
||||||
|
.given(['partitions are enabled'], function() {
|
||||||
|
doc.cookie = `CONSUL_PARTITIONS_ENABLE=1`;
|
||||||
|
set('CONSUL_PARTITIONS_ENABLE', 1);
|
||||||
|
})
|
||||||
.given(['the default ACL policy is "$policy"'], function(policy) {
|
.given(['the default ACL policy is "$policy"'], function(policy) {
|
||||||
set('CONSUL_ACL_POLICY', policy);
|
set('CONSUL_ACL_POLICY', policy);
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue