Skip to content

Conversation

@sebelga
Copy link
Contributor

@sebelga sebelga commented Jul 2, 2019

I had a few comments on Jen's PR https://github.com/elastic/kibana/pull/39966/files/e20aefbac90dbc4048630f52694d73dc6108e734

So as it is merged I decided to create this proposal. 😊
My main point was that I thought it overkills to add an app state with a reducer just to make the permission available through context. Aside, a state implies IMO that it will change, and here we load the permissions when the app loads and never change them afterward.

This PR bring a proposal of a small (reusable) "lib" (never know how to call those) that, if we agree on the architecture, will have to find its place in our future "x-pack/plugins/elasticsearch-ui-shared" plugin (in that plugin we will also put my current work for the forms, our useRequest() hook, and all our reusable static code.

@cjcenizal @jen-huang @alisonelizabeth looking forward to hearing your thoughts on this, cheers!

Screen Shot 2019-07-02 at 14 34 19

Screen Shot 2019-07-02 at 16 06 17

@sebelga sebelga added non-issue Indicates to automation that a pull request should not appear in the release notes Team:Kibana Management Dev Tools, Index Management, Upgrade Assistant, ILM, Ingest Node Pipelines, and more t// labels Jul 2, 2019
@elasticmachine
Copy link
Contributor

Pinging @elastic/es-ui

</HashRouter>
</I18nContext>
<AuthorizationProvider
permissionEndpoint={httpService.addBasePath(`${API_BASE_PATH}permissions`)}
Copy link
Contributor Author

@sebelga sebelga Jul 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, instead of having each app creating their handler for the permissions, we could have a single handler in our shared plugin, that would expose something like:

GET /<our-shared-plugin_name>/authorization/permissions?app=snapshot_restore

We could then remove the need of this permissionEndpoint prop here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a single handler may get complex since I think the needs of each app will differ from each other. I like this approach for now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it would be complex. 75% of the handler code is generic and very little specific to the app. We would simply need to create 1 extension file / app for what is specific.
The benefit would be that we could then simply inject the AuthroizatioProvider anywhere, add an extension to the shared plugin "/privileges" endpoint handler and be done.

We can do that as a second pass 😊

@elasticmachine
Copy link
Contributor

💔 Build Failed

@sebelga
Copy link
Contributor Author

sebelga commented Jul 2, 2019

retest

@elasticmachine
Copy link
Contributor

💔 Build Failed

@sebelga
Copy link
Contributor Author

sebelga commented Jul 3, 2019

retest

@elasticmachine
Copy link
Contributor

💔 Build Failed

@sebelga
Copy link
Contributor Author

sebelga commented Jul 3, 2019

retest

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@jen-huang jen-huang added release_note:skip Skip the PR/issue when compiling release notes v7.4.0 v8.0.0 labels Jul 10, 2019
Copy link
Contributor

@jen-huang jen-huang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall I like this proposal, and I think it makes a lot of sense to build something re-usable. I have left some thoughts and comments that I think should be looked at before giving my final stamp 🙂 Thanks for doing this!

<section data-test-subj="restoreList">{content}</section>
) : (
<NotAuthorizedSection
sectionName="snapshot restore status"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to be localized

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my comments in NotAuthorizedSection invalidates the above

) : (
<EuiPageContent>
<NotAuthorizedSection
sectionName="Snapshot and Restore"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to be localized

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my comments in NotAuthorizedSection invalidates the above

return false;
};

export const WithPrivileges = ({ privileges, children }: Props) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we consider renaming this to something like PrivilegesProvider or something else that makes it clear that the usage is a HOC?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to keep the "Provider" term for our context Provider and not mix both. As, indeed, it is not an HOC, I renamed it <AuthPrivileges></AuthPrivileges>. Let me know what you think

id="xpack.snapshotRestore.restoreList.deniedPermissionTitle"
defaultMessage="You're missing {privilegeType} privileges"
values={{
privilegeType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will run into localization problems since cluster/index won't be part of the translation. see my next comment

<p>
<FormattedMessage
id="xpack.snapshotRestore.restoreList.deniedPermissionDescription"
defaultMessage="To view {sectionName}, you must have {privilegesCount,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the messaging for missing index-level privileges is slightly different: relevant LOC

how about changing this component so that the title and body are part of Props? this would make it similar to our SectionError and SectionLoading components

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I will update the component

</div>
<WithPrivileges privileges="cluster">
{({ isLoading, hasPrivileges, missingPrivileges }) => (
<Fragment>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this Fragment isn't needed

</HashRouter>
</I18nContext>
<AuthorizationProvider
permissionEndpoint={httpService.addBasePath(`${API_BASE_PATH}permissions`)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a single handler may get complex since I think the needs of each app will differ from each other. I like this approach for now

return error ? (
<SectionError
title={
<FormattedMessage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not loving having to pass FormattedMessage just for this error message. what do you think about returning error as part of value so that the consumer can handle the error UI instead? this will also let us be more flexible with the error message, for example displaying Error checking Snapshot and Restore permissions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was not very happy with it either. I updated the code to let the consumer deal with the error.

Note: I think we need, in a separate PR, to create our own <CoreProvider> that would just provide the core object, so we can use translation in our reusable components. I already needed it in a few places. So we would have a CoreProvider + AppDependencies (other plugins)

}: Props) => {
const { error, data: permissionsData } = useLoadPermissions(permissionEndpoint);

const isLoaded = typeof permissionsData !== 'undefined';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use loading that is returned from useLoadPermissions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why, I will use it 😊


// Note: here we have to serialize the HTTP response
// If we move forward with proposal, we would need to update the SR server response
// to implement the interface below so we don't need the serialization.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the permissions contract needs to be worked through more carefully. for SR, access to the entire app requires all cluster privileges, and access to Restore status requires all index privileges. the implementation logic in this PR works great for that use case, but what about for more granular UI details?

for example, maybe we want to check if user has access to just read snapshots/repositories and allow them overall app access, but disallow delete/edit/restore through UI if they don't have more admin privileges. read and admin privileges are both cluster type privileges in this case, so the current implementation wouldn't allow for this type of granular checking.

btw this comment does not block this PR! it is just some thoughts, after all I was the one who structured this app's permission response this way 😄 I am working on SLM UI for 7.4, which will also live inside Snapshot and Restore. I'm sure part of that work will be adjusting the app permissions to account for this new section of the app, so I am happy to iterate and improve the current implementation when I get there

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the logic to support this use case and be able to be granular.

Now, if we require all the privileges we need to provide cluster.*
If we need only some privileges we can provide cluster.someSpecifiPrivilege

Copy link
Contributor

@cjcenizal cjcenizal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @sebelga! I agree with Jen's suggestions about changing NotAuthorizedSection to accept title and body props, and to change the AuthorizationProvider to delegate handling of the error state to the consumer.

In addition to this, can we change "authorization" and "privileges" to "permissions" in the code and file names so that this concern is identified by one term instead of three?

title={
<FormattedMessage
id="xpack.snapshotRestore.app.checkingPermissionsErrorMessage"
defaultMessage="Error checking permissions"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we standardize on language, e.g. use "permissions" or "authorization" everywhere? For example, we could rename this file permissions_provider or change this message to "Error checking authorization."

Copy link
Contributor Author

@sebelga sebelga Jul 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that 3 terms are too many 😊 But one thing is the Authorization service (which is the feature we are bringing here) and then what this feature provides. The Authorization provider can return different types of data: user roles, user privileges, user access, user permissions, ...

Jen seems to have used "privilege" and "permission" interchangeably. The API path is "/permissions" but privileges are returned 😊 I will stick with "permissions" and use that word everywhere.

[EDIT]: But then... Elasticsearch uses the word "privilege". :/ I'll use "privileges" to simplify things.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From our docs, all words are used: Authorization, roles, permissions and privileges 😊

Screen Shot 2019-07-16 at 12 16 56

return false;
};

export const WithPrivileges = ({ privileges, children }: Props) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with this file. Instead of "privileges" can we use "permissions" or "authorization"? By keeping the terms consistent we can reduce mental overhead for people figuring out what the code is doing.

Copy link
Contributor Author

@sebelga sebelga Jul 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to align all the term to Elasticsearch API "privilege" naming. The provider is still the Authorization as explained above.

@sebelga
Copy link
Contributor Author

sebelga commented Jul 16, 2019

Thanks for the review @jen-huang and @cjcenizal I addressed all the feedback and made some comments. Could you have another look? Cheers!

@jen-huang I also updated the Server handler response to match the contract of the AuthorizationProvider.

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@pugnascotia
Copy link
Contributor

Just for interest, I thought I'd mention what Cloud has done for permissions. Our permissions map 1-to-1 with Swagger operation IDs, so for the UI we generate a TypeScript enum of all the possible permissions. Then, we get type completion when checking the user's permissions, as well as typo prevention.

@sebelga
Copy link
Contributor Author

sebelga commented Jul 22, 2019

Thanks for sharing @pugnascotia Although we don't have yet a centralized service for privileges. I'll keep it in mind if we decide to do so. cheers!

Copy link
Contributor

@jen-huang jen-huang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the changes Seb, LGTM!

I also tested locally to make sure there were no regressions with accessing the app with a fully privileged user and users of varying privileges.

Looking forward to trying this out in my new work for SR!

@sebelga
Copy link
Contributor Author

sebelga commented Jul 23, 2019

Cool, thanks for the review @jen-huang !

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@sebelga
Copy link
Contributor Author

sebelga commented Jul 23, 2019

@cjcenizal Do you still have concerns about naming? cheers

@cjcenizal
Copy link
Contributor

@sebelga I can re-review this today, thanks for the ping!

Copy link
Contributor

@cjcenizal cjcenizal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a grep for "permission" and found many references still in the code. Did you want to replace these with "privilege"? In a few places both terms refer to the same thing, which is a little confusing. Since this doesn't affect functionality I'm approving the PR to unblock it but I think it would be great if we could align on a single term.

const toArray = (value: string | string[]): string[] =>
Array.isArray(value) ? (value as string[]) : ([value] as string[]);

export const AuthPrivileges = ({ requiredPrivileges, children }: Props) => {
Copy link
Contributor

@cjcenizal cjcenizal Jul 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: AuthorizationPrivileges or simply Authorization would be more consistent with our other names, and removes any ambiguity around what auth stands for (authorization or authentication).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will use AuthorizationPrivileges.

"Authorization" and "Privileges" are 2 different things 😊 The "Authorization" is the mechanism in place that will handle a user "privileges". It could, for example, also manage "roles" and would still be called "Authorization".
Here, the component is handling "privileges" so I'll change it to AuthorizationPrivileges.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: I went back to my original <WithPrivileges> as it is what best describes this component. I just saw this convention used by the app search team.

<WithPrivileges privileges="index.*">
  {/* anything here requires the above privileges */}
</WithPrivileges>

cc @jen-huang

*/

import React, { createContext } from 'react';
import { useLoadPermissions } from '../services';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Would useLoadPrivileges be more consistent?

export const AuthorizationContext = createContext<Authorization>(initialValue);

interface Props {
permissionEndpoint: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: privilegesEndpoint?

}

export const AuthorizationProvider = ({ permissionEndpoint, children }: Props) => {
const { loading, error, data: permissionsData } = useLoadPermissions(permissionEndpoint);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: privilegesData?

* you may not use this file except in compliance with the Elastic License.
*/

export interface AppPermissions {
Copy link
Contributor

@cjcenizal cjcenizal Jul 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should this be AppPrivileges or perhaps AppAuthorization? I learn towards the latter if that's overarching concept we're dealing with, of which "privileges" are a part.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I'll change it to AppAuthorization.

@sebelga
Copy link
Contributor Author

sebelga commented Jul 29, 2019

Thanks for the review @cjcenizal I updated all references to use the word "privilege" instead of "permission".

permissionsResult.missingClusterPrivileges = extractMissingPrivileges(cluster || {});
permissionsResult.hasPermission = hasPermission;
privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(cluster);
privilegesResult.hasAllPrivileges = hasPermission;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you want to change this to privileges too? I did a ctrl-f for "permission" on the diff of this PR and there are also quite a few references to privileges in the localization keys.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow here. Yes I did want to change "permission" to "privileges" here. Isn't that what you also wanted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah you mean the "hasPermission" name? 😊

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah you mean the "hasPermission" name

Yes, you can do a ctrl-f and you'll see all of the instances I have in mind as candidates for renaming.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++ I just updated everything that had the word "permission".

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

non-issue Indicates to automation that a pull request should not appear in the release notes release_note:skip Skip the PR/issue when compiling release notes Team:Kibana Management Dev Tools, Index Management, Upgrade Assistant, ILM, Ingest Node Pipelines, and more t// v7.4.0 v8.0.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants