-
Notifications
You must be signed in to change notification settings - Fork 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
Fix ACL requirements for job details UI #11672
Conversation
Ember Asset Size actionAs of fe643d1 Files that got Bigger 🚨:
Files that stayed the same size 🤷:
|
Ember Test Audit comparison
|
Ember Test Audit flaky testsEmber Test Audit detected these flaky tests on main:
|
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.
I am not versed in all of nomad's ACL minutiae, but from a code perspective, this looks fine. Just one comment about the preloading of all nodes only for read (and not write) users?
// Optimizing future node look ups by preemptively loading all nodes if | ||
// necessary and allowed. | ||
if (model.get('hasClientStatus') && this.can.can('read client')) { | ||
await this.store.findAll('node'); |
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.
Do users with write access not need this optimization for some reason?
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.
I'm not sure if this is how it works in other projects, but in Nomad ACL system a write
permission assumes that you have read
as well:
https://github.com/hashicorp/nomad/pull/11672/files#diff-e4c09a2e6416b7748fae6b3d5e3010f68eb770c0b1d757eed0ab8287146bb1b2R18
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 diff is exposing how tightly-coupled the jobs route hierarchy template components are. Particularly, based on how many repeat conditionals we're using across the templates and the high use of prop-drilling.
I'm concerned that we won't be able to actually catch future bugs and continue bloating our templates with conditional checks.
Let's chat regarding drawing out the behavior for ACL token checks and move the conditional checks up the job chain to clean up the repeat code in our templates.
We can accomplish this with using a contextual component pattern and delete a couple hundred lines of code in the process.
@gotoClients={{this.gotoClients}} /> | ||
|
||
<JobPage::Parts::Summary @job={{this.job}} @forceCollapsed="true" /> | ||
{{#if (can "read client")}} |
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 check can go into the JobClientStatusSummary
.
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.
Unfortunately it cant'...as soon as Ember accesses jobClientStatus
it will make an API request to get the Node information, which will 403.
I moved the check to the param access instead, does this look better?
<JobPage::Parts::JobClientStatusSummary
@job={{this.job}}
@jobClientStatus={{if (can "read client") this.jobClientStatus}}
@forceCollapsed={{cannot "read client"}}
@gotoClients={{this.gotoClients}} />
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.
Hmmm... I think this breaks my mental model of Ember, and my understanding of the Ember Way.
Currently, we're loading all the nodes into the store when we enter the jobs/job route. And then, we peek the store (no API call) when we're in a nested controller. To my knowledge this isn't a data loading component either.
Can we move this conversation to Whimsical and map this out? That behavior sounds like a lurking bug.
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.
Currently, we're loading all the nodes into the store when we enter the jobs/job route.
That's the problem I'm solving: we don't want to do this if the user's ACL token doesn't have permission to access the /v1/node
API endpoint.
But if we try to read a node from the store, Ember will attempt to fetch it but will receive a 403
.
Not sure if this what you're looking for, but this is the flow:
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're using the computed macro
jobClientStatus
in the wrong place.JobClientStatusSummary
is the lowest common ancestor that should compute and store this derived state. - The API call happens on the route which is protected by the
ember-can
check. The associated controllers aren't fetchingnodes
(with the exception of thejobs/job/clients
controller, and this tab should be protected using the can helper -- this is another associated bug for this issue) because they're peaking from the store. The component is not fetching data either. Now, there's one more concern, the computed macrojobClientStatus
this should not be making any API calls, if it is then its a bug.
@@ -8,7 +8,7 @@ | |||
{{/if}} | |||
<li data-test-tab="allocations"><LinkTo @route="jobs.job.allocations" @query={{hash namespace=this.job.namespace.id}} @model={{@job}} @activeClass="is-active">Allocations</LinkTo></li> | |||
<li data-test-tab="evaluations"><LinkTo @route="jobs.job.evaluations" @query={{hash namespace=this.job.namespace.id}} @model={{@job}} @activeClass="is-active">Evaluations</LinkTo></li> | |||
{{#if (and this.job.hasClientStatus (not this.job.hasChildren))}} | |||
{{#if (and (can "read client") (and this.job.hasClientStatus (not this.job.hasChildren)))}} |
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 should move the shouldDisplayClientInformation
into AbstractJob
and then we'll have access to this property.
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.
It doesn't seem to work for the subnav. Do components inherit property from parents?
get isExpanded() { | ||
if (this.forceCollapsed) return false; |
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.
Lets turn forceCollapsed
into a reactive getter and then this no longer needs to be a computed property.
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.
Not sure if I follow.
forcedCollapsed
is an external property that is set by the caller to force the collapse state. isExpanded
is the getter used to set the component state.
Yeah, there's quite a bit of repetition, but also these pages are similar but not identical. We could try to DRY some of it, but personally I'm not sure it would be bring enough benefits. There's always some quirky details that differ among them.
The test suite uses a common module, so a lot of the shared behaviour is tested automatically across all job types.
Not sure if I follow this part. The ACL check conditionally renders a specific part of the page, so I think it needs to be done at the component level? |
assert.equal( | ||
currentURL(), | ||
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace) | ||
const expectedURL = new URL( | ||
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace), | ||
window.location | ||
); | ||
const gotURL = new URL(currentURL(), window.location); | ||
|
||
assert.deepEqual(gotURL.path, expectedURL.path); | ||
assert.deepEqual(gotURL.searchParams, expectedURL.searchParams); | ||
assert.equal(document.title, `Job ${job.name} - Nomad`); | ||
}); | ||
|
||
test('the subnav links to overview', async function(assert) { | ||
await JobDetail.tabFor('overview').visit(); | ||
assert.equal( | ||
currentURL(), | ||
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace) | ||
|
||
const expectedURL = new URL( | ||
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace), | ||
window.location | ||
); | ||
const gotURL = new URL(currentURL(), window.location); | ||
|
||
assert.deepEqual(gotURL.path, expectedURL.path); | ||
assert.deepEqual(gotURL.searchParams, expectedURL.searchParams); |
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.
I have no idea why these started to fail 🤷
The currentURL
would have and extra query params for sort
, so I'm parsing the URL to ignore order.
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 fails on periodic and parameterized jobs. Likely points to some more lurking bugs in how we handle periodic and parameterized jobs.
await Tokens.visit(); | ||
await Tokens.secret(clientToken.secretId).submit(); |
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.
I think that we can't set a token directly into localStorage
after visiting a page? I'm not sure if I missing something, but it didn't work in a few acceptance tests where visit
is called in the beforeEach
hook, so I had to do it this way.
And TBH I kind of like it, feels more intentional 😅
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 need to establish boundaries for API's we don't control. Ideally, we should wrap localStorage access into a service and then write mocks.
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 a bug. We initialize query parameters on period
and parameterized
jobs whereas we don't follow this behavior for other jobs. Will need to take a deeper dive into this.
Ember Test Audit flaky testsEmber Test Audit detected these flaky tests on main:
|
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're using the computed macro jobClientStatus in the wrong place. JobClientStatusSummary is the lowest common ancestor that should compute and store this derived state.
The API call happens on the route which is protected by the ember-can check. The associated controllers aren't fetching nodes (with the exception of the jobs/job/clients controller, and this tab should be protected using the can helper -- this is another associated bug for this issue) because they're peaking from the store. The component is not fetching data either. Now, there's one more concern, the computed macro jobClientStatus this should not be making any API calls, if it is then its a bug.
3. There's some more lurking bugs that this PR raises as mentioned in #2.
4. Now that tests are failing and we're circumnavigating currentURL()
, I think I need to take a deeper look into this.
Can you give me 2 days to close out the breadcrumbs work so I can take a few hours to review this?
Sure 👍 |
Ember Test Audit flaky testsEmber Test Audit detected these flaky tests on main:
|
I'm going to lock this pull request because it has been closed for 120 days ⏳. This helps our maintainers find and focus on the active contributions. |
#11078 added a new component to compute and display the job status in individual clients. This resulted in an unexpected increase in the ACL permissions required to navigate to the job detail page. Namely the job detail page would now require
This resulted in unexpected
Not Authorized
error page (#11660).This PR adds a new ability to check for read node permission. It also updates the job details components to only render pieces and components that require node data when ACL permits.