Add SubSection for System Console Instructions

Changelog

Feburary 16, 2021: Updated Backend portion regarding giving MANAGE_JOBS permission in the ancillary permissions.

WEBAPP PORTION

  1. Navigate to components/admin_console/system_roles/system_role/system_role_permissions.tsx

  2. Notice the variable sectionsList which holds all the sections. The sections and subsections are what map out to Privileges in admin_console/user_management/system_roles/{role_id}. For example, here is the user management section and subsection mapping to what you see in system console.

3. Navigate your assigned section (in this tutorial, I’ll be dealing with the experimental section) and add the corresponding subsections. Experimental has 3 sub sections so I will be adding 3 objects in the subsections array for those subsections. The subsection naming convention follows section_name_subsection_name.

 

 

4. Now you would want to scroll to the bottom of the file and add the translations for these subsections. Located your section and add the corresponding translations for subsections. The naming convention is admin.permissions.sysconsole_section_{section_name}_{subsection_name}. These will help us generate the correct translations when we run make i18n-extract in our terminal.

5. Navigate to your REDUX repo and go to the file src/constants/permissions_sysconsole.ts . Add your section keys to the RESOURCE_KEYS variable in the format

SECTION: { SUB_SECTION_1: 'section.subsection1', SUB_SECTION_2: 'section.subsection2', ... }

It should look something like this

Now locate your section in the ResourceToSysConsolePermissionsTable and break it down like the following. Note that each one of these sub-sections will have its own READ and WRITE permissions that we will later be implementing. Once you’ve broken it down as shown in the left photo, you can delete the section to permission mapping as seen on the left.

What it looked like before

What it looks like after breaking down the section into it’s subsections

6. Now you probably notice RED lines underneath the new permissions and that’s because we haven’t written those permissions yet. That is what we will do next. While we are still in the REDUX navigate to src/constants/permissions.ts . Scroll down and locate your sections permissions. In my case, the permissions I was looking for are here

You can go ahead and delete these and implement the subsection permissions we referenced earlier. This is what it should look like after the breakdown. Take notice that the red lines seen earlier in permissions_sysconsole.ts have gone away.

Make sure you copy all of the READ permissions to the values.SYSCONSOLE_READ_PERMISSIONS array and all the WRITE permissions to values.SYSCONSOLE_WRITE_PERMISSIONS.

7. We want to navigate back to our WEBAPP repo and go to components/admin_console/admin_definition.jsx . This file is what defines what we see in the system console. Find the section you are dealing with in the AdminDefinition variable.

8. We will be adding isHidden and isDisabled for each section, subsection, and contents of subsection. So I will start with the over-arching section (experimental section) and only add a isHidden function to it. This section should be hidden if we don’t have any READ permission to any of it’s subsection.

9. Now for every subsection, that particular subsection should be HIDDEN if we don’t have read permission to it. Otherwise, It’s contents should be disabled if we don’t have write permission to it. Notice how the Features sub-section is hidden if we don’t have READ permission and the contents within this subsection are disabled only if we don’t have WRITE permissions to that sub section.

Do this for all of the sub-sections for the section you are dealing with.

10. In your webapp directory, run make i18n-extract to get your translations. Now would also be a good idea to do a make fix-style to fix any linting errors.


BACKEND (SERVER) PORTION

  1. This part is a little bit tedious. You will want to navigate to model/permission.go. You’ll want to locate your section permission (in my case it’s PERMISSION_SYSCONSOLE_READ_EXPERIMENTAL and PERMISSION_SYSCONSOLE_WRITE_EXPERIMENTAL ) and underneath declare a new permission for each subsection. We won’t need to delete the section permission but rather will deprecated it.

2. You can find the assignment of the section permission and then underneath, create the assignment for all the subsection permissions. Note that the second and third parameters for the new permissions are ignored since they were once upon a time used for internationalization however that is no longer the case. It will look something like this:

Note: It may be a good idea here to add a comment above the section permission (PERMISSION_SYSCONSOLE_READ/WRITE_EXPERIMENTAL ) mentioning it’s been deprecated and remove it from:

1. SysconsoleReadPermissions

2. SysconsoleWritePermissions arrays

3. In model/role.go , ensure it’s removed from all the roles default permissions (SystemUserManagerDefaultPermissions , SystemReadOnlyAdminDefaultPermissions ,SystemManagerDefaultPermissions )

(done after screenshot below was taken so results are not reflected).

2B. Depending on which of the 3 (SystemUserManagerDefaultPermissions , SystemReadOnlyAdminDefaultPermissions ,SystemManagerDefaultPermissions ) arrays you removed your section permission, ensure you replace it with the new permissions. For example, in my case, SystemReadOnlyAdminDefaultPermissions had PERMISSION_SYSCONSOLE_READ_EXPERIMENTAL and it was replaced by the corresponding subsection read permissions. Ensure you do this for every one of the default permissions!

 

3. Take all the new permissions and add them to the respectively to SysconsoleReadPermissions and

SysconsoleWritePermissions. Don’t forget to add your now deprecated section permission to DeprecatedPermissions (not shown in picture).

4. Update Access Tags in model/config.go

When returning the config.json file to the Web application for SystemConsole Editing, only portions accessible to the given Role are returned. These will need updated as well. The underlying code will apply these at read/write as appropriate. So you only need to reference the base permission.

Change to:

5. With this done, you can start up both your mattertmost-server and mattermost-web and use the webapp to login into your system admin account. Since your system account doesn’t have these new permissions (which we will need to write a migration for later), you can go to system roles and and assign these new permissions to a role which didn’t have them before. In my case, system manager did not have experimental permissions at all (set to No Access) and I changed to Can Edit and assigned a test user.

After you’ve saved these settings, you can log out as your system admin account and login as your test account (test456 in my case).

6. Now when you log into your test account you should see those sections and sub sections since you will have the proper permissions.

Yeah! It means we’ve done everything correctly up to this point!!

Note: The next part may be time consuming depending on how big your section is.

Now we need to map out our ancillary permissions. That is to map out our system console permissions to more granular permissions that allow us to do certain actions within that sub section. Note that your sub sections may not need any ancillary permissions.

So you may be asking what are ancillary permissions? Basically within a system console sub section, you may be required to do some action (eg. remove a member from a team) which requires a certain granular permission. Ancillary permissions map our your permissions to a certain system console section to more granular permissions that allow you to do those actions

Why not just the system console permissions rather than having a dedicated granular permissions to do such actions? Well there are a couple reasons for this:

  1. System console permissions were originally implemented only for front end validation. They were never meant to be used for backend validation.

  2. It becomes a little hard to validate all the sections that need access to a certain endpoint. Say Section A, B, and C call POST api/v4/testEndpoint. In the method that handles that endpoint, we will need to do if hasPermission(subsection A) || hasPermission(subsection B ) || hasPermisison(subsection C) however if we made a granular permission called testEndpointPermission then in our ancillary we can map our those sub sections to this permission such that when a user gets WRITE access to those sections, they can make POST requests.

  3. Finally, if hypothetically a bot needs access to a certain endpoint and we use subsection permissions to validate permission access to that endpoint, it wouldn’t make sense to assign the bot those subsection permissions because we come back to our first point where they are meant to do front-end validation and not back-end validation

 

6. Now when we go through the Features and Feature Flags section, we notice it’s just a matter of setting values. There is no buttons or anything that would make any extra API calls.

However, once we get to the Bleve section, we notice there are two buttons. There are Index now and Purge Index buttons which we need to be cautious off.

We need to track what endpoints are called when those buttons are pressed and if any extra permissions are required for those endpoints.

When we click Index Now, we make a POST api/v4/jobs which if we track in our server code leads us to a function called createJob . Notice how this endpoint requires the permission MANAGE_JOBS . This means that we need to note down anytime we get SYSCONSOLE_WRITE_EXPERIMENTAL_WRITE, we should also get the permission MANAGE_JOBS which we will do via mapping through ancillary permissions.


Feburary 16, 2021 UPDATE

It’s important to make a note here about a potential security flaw that could have arised had we gone through with this. Thanks to Scott Bishel for pointing this out in my PR. The createJob endpoint allows us to create ANY job so it’s very important we are mindful of our permissions. Had we given the Bleve subsection PERMISSION_MANAGE_JOBS, that means we are allowed to trigger a job for any job type and not only for indexing bleve posts.

To remedy this, I created a new function SessionHasPermissionToJob(session model.Session, job *model.Job) (bool, *model.Permission) which takes the current session of a user and the job they are trying to create. It returns if they have permission and the permission required for that job. I then created a new permission for that particular job instead of using the generic PERMISSION_MANAGE_JOBS .

tdlr; be mindful of the permissions your granting per section and create new ones if the one you are granting is too over powered.


Similarly, when we press Purge Index , we will make a POST api/v4/bleve/purge_indexes which if we trace in our server code, we will see that we

Uh-Oh! You may notice these mistakes while you are implementing your sub section permissions. In this scenario, we want to define a new permission PERMISSION_PURGE_BLEVE_INDEXES in model/permission.go and add it to the SystemScopedPermissionsMinusSysconsole array and then map it to the Bleve subsection. (You must also be aware to update any tests that may be affected by these new permission changes)

After the new permission change we have

and after adding it to our ancillary permission

Since we were dealing with two endpoints that made POST requests, it makes sense to add it to the the WRITE permission as oppose to READ. Had we had any endpoints that did a GET request, we would have wanted to add those permissions to the READ permission of our subsection.

MIGRATIONS

Now that we have all of our permissions in place, it’s time to write the migrations so that people with the old section permission (EXPERIMENTAL in my case) can get these new permissions. Head over to app/permissions_migrations.go and scroll down to where you can see DoPermissionsMigrations function. In the PermissionsMigrations array , add a new object with a Key and Migration function.

You’ll notice that golang will complain that no key of that type or function of that type exist.

You can add your key to model/migration.go and the function can go right above theDoPermissionsMigrations.

Here is our migration plan:

  1. Give the permissions PERMISSION_SYSCONSOLE_READ_EXPERIMENTAL_BLEVE, PERMISSION_SYSCONSOLE_READ_EXPERIMENTAL_FEATURES , PERMISSION_SYSCONSOLE_READ_EXPERIMENTAL_FEATURE_FLAGS to anyone with PERMISSION_SYSCONSOLE_READ_EXPERIMENTAL and then remove that permission.

  2. Give the permissions PERMISSION_SYSCONSOLE_WRITE_EXPERIMENTAL_BLEVE, PERMISSION_SYSCONSOLE_WRITE_EXPERIMENTAL_FEATURES , PERMISSION_SYSCONSOLE_WRITE_EXPERIMENTAL_FEATURE_FLAGS to anyone with PERMISSION_SYSCONSOLE_WRITE_EXPERIMENTAL and then remove that permission.

  3. Give the permissions PERMISSION_POST_BLEVE_INDEXES and PERMISSION_PURGE_BLEVE_INDEXES to anyone with PERMISSION_SYSCONSOLE_WRITE_EXPERIMENTAL_BLEVE

Translating the above into code will look something like this

Once you’ve completed this, you can restart your server! After that’s complete, ensure both system admin and system manager users both have access to your section and can execute all actions in those subsections. Congratulations! You’re finished migrating your section to it’s individual sub sections!


TESTING

So we are onto the last section of this guide!! We are almost done, we just need to do a few more modifications.

  1. Within the webapp, navigate to e2e/cypress/support/api/role.js . Ensure that all the default permission you modified earlier are reflected in our default permission for the testing framework. For example, earlier I had to remove the READ experimental permission from system_read_only_admin and add the 3 corresponding READ subsection permissions.

Ensure we do the same for the system admin!! The following is a screenshot of sysadmin array. I’ve already removed the read_experimental and write_experimental permission and added my new permissions. Important not to forget all the permissions! In my case, it’s the purge_bleve_indexes and post_bleve_indexes

 

2. Now we want to navigate to e2e/cypress/fixtures/system-roles-console-access.json in the webapp and expand our section to it’s subsections and then remove the section like I’ve done in the following screenshot. The section names come from inspecting each subsection and copying it’s test ID.

 

3. Now we want to navigate to e2e/cypress/fixtures/console-example-inputs.json and expand our section into it’s subsections. The section comes from the same as above explanation (you can copy the section names here). path is the link to your section. selector is the id for any input field in that section (because we are just testing one input field rather than all of them since it would take way to long and we assume that if one of the fields is disabled then all of them should). This is what it looks like after I’ve made my changes

 

Since feature_flags only displays information and doesn’t have any input fields, I left the selector blank. The test ensures that the subsection appears in the right hand side of the side bar which is all we are concerned about for feature_flags .

If you’re wondering where the selector id comes from, you can navigate to your section and right click on any input field you want to use and inspect it. Ensure the field you choose doesn’t rely on any other field (ex. a field that becomes available if another field is true). You want to choose a field that is on by default.

In my case for the features section, I chose the Link Metadata Timeout as it’s on by default. The testid is what cypress will use to select that input field.

Note Since the test infrastructure has already been setup this way, we are using the data-testid. HOWEVER, the QA team has decided to move away from using id and data-testid to select component so for future reference if you’re writing your own test, do not use these properties to select DOM elements.

4. You may start your web and server locally as we will be testing things now. (that is to run make run-server and make run respectively in your terminal)

Once we are done that, navigate to your terminal and go to the e2e/ directory in the webapp repo. We want to run our test to ensure nothing in our limited console access test is broken (as it shouldn’t be). Once we are in the e2e directory, we execute npm i and then we execute npx cypress open to open cypress, our end-to-end testing framework.

 

After it’s open, you will want to search for t he limited_console_access_spec.js test. Click it and let cypress do its thing.

And then …. after a while …

OUR TESTS PASSED!!!! YAH!!!!

And that concludes our guide on migration section to subsection! If you like this tutorial, don’t forget to a drop a like and subscribe to my channel

The PR’s for the above code changes are here:

Server: https://github.com/mattermost/mattermost-server/pull/16887

Web: https://github.com/mattermost/mattermost-webapp/pull/7475

Redux: https://github.com/mattermost/mattermost-redux/pull/1356

 

Thank you to everyone who has helped revise and make this document better!

 

Notes/Reminders

N1: If you’ve made changes to your local redux repo but getting errors when trying to run your webapp telling you that something defined in redux isn’t defined in your webapp, you’ll need to run the following command in your terminal while in the root of your redux repo:

WEBAPP_DIR=~/go/src/github.com/mattermost/mattermost-webapp/ npm run dev:watch

This command will link your local redux-repo with the webapp so you get the changes your expecting.

 

N2: Make sure that you merge your redux PR first and then your Webapp PR. Once the redux PR has been merged, prior to merging the webapp PR, update the mattermost-redux in your package.json and package-lock.json with the latest hash shown in the SectionsToSubSections of the redux PR.

 

N3: Ensure you are pulling the latest changes from SectionsToSubSections daily for all 3 repo’s (server, redux, webapp) to ensure you are developing on the latest changes and that your branches are not behind.