-
Notifications
You must be signed in to change notification settings - Fork 8.1k
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
Introduce Access Agreement UI. #63563
Introduce Access Agreement UI. #63563
Conversation
Pinging @elastic/kibana-security (Team:Security) |
9320a37
to
5f43585
Compare
@@ -8,11 +8,12 @@ import { EuiIcon, EuiSpacer, EuiTitle } from '@elastic/eui'; | |||
import React from 'react'; | |||
|
|||
interface Props { | |||
className?: string; |
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.
note: motivation behind this change is that I wanted to make body of this component wider for access notice as we have much more text there (600px
instead of 480px
)
@@ -15,7 +15,6 @@ import { | |||
} from './plugin'; | |||
|
|||
export { SecurityPluginSetup, SecurityPluginStart }; | |||
export { SessionInfo } from './types'; |
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.
note: it doesn't seem like we want it to be a part of our public API, feels like our own internal thing.
/** | ||
* The set of flags used to describe various aspects of the user session. | ||
*/ | ||
flags?: { |
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.
note: if we wouldn't have plans to switch to server side sessions, I'd use a bitmask field here instead to save space 🙂 For now will just make it optional.
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.
question I'm not opposed to this structure, but do you have other flags in mind that we'll add in the future?
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.
That's actually a good point, I had something else in mind at the beginning, but now I can't think of anything. Maybe flags
is too premature indeed, I'll switch to the top level property for now, thanks!
(this.options.config.authc.providers as Record<string, any>)[session.provider.type]?.[ | ||
session.provider.name | ||
]?.accessNotice && | ||
request.url.pathname !== ACCESS_NOTICE_ROUTE |
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.
note: technically pathname
can have a trailing slash, but I'm ignoring this here to simplify the check because of the following reasons:
- When we redirect to this page we don't add trailing slash
- If user goes directly to this page and adds a trailing slash, they will end up at
/security/access_notice?next=%2Fsecurity%2Faccess_notice%2F
assuming they haven't acknowledged access notice yet, that means after acknowledgement they will be redirect to/security/access_notice
(since Kibana server will eventually issue a redirected to the same URL but without trailing slash), after second acknowledgement or just by navigating to another page everything will go back to normal.
I'm not worried about the second point since it's a user mistake after all and it doesn't have that bad consequences.
@@ -225,25 +234,19 @@ export function createConfig( | |||
const sortedProviders: Array<{ |
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.
note: since we're adding more common config properties the amount of info we're duplicating in sortedProviders
is increasing, so I decided to take a step back and keep authc.providers
as the only source of truth.
@@ -71,14 +71,10 @@ describe('Security Plugin', () => { | |||
"authc": Object { | |||
"createAPIKey": [Function], | |||
"getCurrentUser": [Function], | |||
"getSessionInfo": [Function], |
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.
note: technically it's a breaking change (if we already started to treat changed plugin contracts as such), but nobody in Kibana was using it and I'm not sure we want to expose this to 3rd party plugins yet. Starting from 8.0 I'd not do that 🙂
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.
Yeah, now is a great time to clean this up!
const session = await authc.getSessionInfo(request); | ||
const accessNotice = | ||
(session && | ||
(config.authc.providers as Record<string, any>)[session.provider.type]?.[ |
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.
note: extracting config from config.authc.providers
is a bit ugly since we don't know what providers are configured and there is no "type link" between session provider field and this config (and may not be since session can rely on old config). If we see this pattern more and more, we'll create a helper method instead.
5f43585
to
5c395ef
Compare
@azasypkin A few design tweaks coming... I'll have a design PR for you to review. |
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.
@azasypkin here is a design PR with a few changes that more closely aligns to the modal content styling we are after. If it looks good, simply merge: azasypkin#1
Access page design adjustments
Looks great, thanks a lot! Merged and updated screenshot in description. |
@azasypkin I'll need to make some changes based upon some feedback I received from @cchaos . I'll get that going straight away. |
2nd design PR 👉 azasypkin#2 |
… generalize flags just yet, use object format for `accessAgreement` config.
… generalize flags just yet, use object format for `accessAgreement` config, use loading screen.
Yes indeed, the Kibana logo should now be the cluster logo. |
Roger that, changed to |
@legrego PR should be ready for another pass! But since we're still waiting for some details on this feature that will require a couple of changes here and there (in a separate commit though), feel free to postpone review. |
} | ||
|
||
if (!this.options.license.getFeatures().allowAccessAgreement) { | ||
throw new Error('Current license does not allow access agreement acknowledgement.'); |
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.
note: this doesn't sound like an audit event, but rather as a developer mistake, hence just throwing error here.
// If license doesn't allow access agreement we shouldn't handle request. | ||
if (!license.getFeatures().allowAccessAgreement) { | ||
logger.warn(`Attempted to acknowledge access agreement when license doesn allow it.`); | ||
return response.notFound(); |
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.
note: I was considering two options here, either to return 403 or 404. I picked 404 and the rational is that license doesn't provide this feature and that basically means that feature is missing (not found).. What do you think?
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 a 403
would make more sense here (with reason in the response body), so that we're consistent with the base licensed route handler functionality:
return responseToolkit.forbidden({ body: { message: licenseCheck.message! } }); |
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.
Okay, makes sense too, let's go with 403
. By reason you mean our own custom message, right? Currently SecurityLicense
doesn't keep the optional message that Licensing plugin would return in case of failed check...
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.
Discussed over Slack, we'll go with custom Current license doesn't support access agreement.
message.
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.
Looks great, thanks @azasypkin! Just a couple nits/comments, then I think we're good to go!
// If license doesn't allow access agreement we shouldn't handle request. | ||
if (!license.getFeatures().allowAccessAgreement) { | ||
logger.warn(`Attempted to acknowledge access agreement when license doesn allow it.`); | ||
return response.notFound(); |
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 a 403
would make more sense here (with reason in the response body), so that we're consistent with the base licensed route handler functionality:
return responseToolkit.forbidden({ body: { message: licenseCheck.message! } }); |
>( | ||
handler: RequestHandler<P, Q, B, M, R> | ||
) => { | ||
const licensedRouteHandler: RequestHandler<P, Q, B, M, R> = ( |
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.
note for future us: we should consider updating this to accept a minimum license level once we have more than one route that requires an elevated license
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.
To be honest I have mixed feelings about this since feature that depends on license level can rely on multiple endpoints and keeping all of them in sync may be tedious. Unless we refactor SecurityLicense
to include something similar to feature <-> min license level
mapping that will be the only source of truth.
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.
That’s a great point, a single source of truth is much more beneficial than the potential duplication of license checks
@@ -94,7 +98,7 @@ describe('license features', function() { | |||
} | |||
}); | |||
|
|||
it('should show login page and other security elements, allow RBAC but forbid role mappings, DLS, and sub-feature privileges if license is basic.', () => { | |||
it('should show login page and other security elements, allow RBAC but forbid role mappings, access agreement, DLS, and sub-feature privileges if license is basic.', () => { |
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 getting a bit out of hand 😅What do you think about something like this?
it('should show login page and other security elements, allow RBAC but forbid role mappings, access agreement, DLS, and sub-feature privileges if license is basic.', () => { | |
it('should show login page and other security elements, allow RBAC but forbid paid features if license is basic.', () => { |
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.
🙂 yeah
expect(routeConfig.validate).toBe(false); | ||
}); | ||
|
||
it('returns 404 if current license doesnt allow access agreement acknowledgement.', async () => { |
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('returns 404 if current license doesnt allow access agreement acknowledgement.', async () => { | |
it(`returns 404 if current license doesn't allow access agreement acknowledgement.`, async () => { |
createLicensedRouteHandler(async (context, request, response) => { | ||
// If license doesn't allow access agreement we shouldn't handle request. | ||
if (!license.getFeatures().allowAccessAgreement) { | ||
logger.warn(`Attempted to acknowledge access agreement when license doesn allow it.`); |
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.
logger.warn(`Attempted to acknowledge access agreement when license doesn allow it.`); | |
logger.warn(`Attempted to acknowledge access agreement when license doesn't allow it.`); |
…rt access agreement, minor test fixes.
💚 Build SucceededHistory
To update your PR or re-run it, just comment with: |
Co-authored-by: Ryan Keairns <contactryank@gmail.com>
7.x/7.8.0: a3790c8 |
This PR introduces the Access Agreement UI that works like that:
accessAgreement
configuration option now. Option accepts markdown string as a value./security/access_agreement
only if:accessAgreement.message
configuredUI/UX:
Config:
Blocked by: Design Team and stakeholders feedback is pendingFixes: #18176
"Release Note: every provider can now be configured with the access agreement message (markdown syntax) that will be presented to the users after login. Users won't be able to use Kibana until they acknowledge this agreement."