Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Multitenant: Add filtering by tenant_id #282

Merged
merged 47 commits into from
Jun 24, 2022
Merged

Conversation

mattbroussard
Copy link
Collaborator

@mattbroussard mattbroussard commented May 26, 2022

Depends on #259

See overall GOST integration plan: https://www.notion.so/usdr/GOST-ARPA-integration-technical-plan-cffc5800c0414660816a0920b959da82#b4dfb0449fc54674ae97f070a6b7b33a

This PR adds filtering and access checks based on the current user's tenant_id in (as far as I know) all places that need it. In most places the tenant ID to filter by comes from the logged in user, via a new req.session.user object populated by requireUser middleware that's used almost everywhere. In a few cases, functions that deal with other types of entities (mainly uploads) get a tenant ID from that object.

Most changes are serverside only and clients are generally not aware of the concept of tenants. My approach was to go through all the DB accessor functions in src/server/db and trace backward to the places that call them. This was a bit tricky in the absence of TypeScript since I had to rely on grep and we do some annoying things like renaming exports and imports.

One notable clientside change is that previously /api/application_settings was called before logging in and thus could not use requireUser to determine a tenant ID to filter by. I've changed this so that this data is not loaded until after login now, and the endpoint requires authentication. Accordingly, the navbar with the period dropdown (which draws from this data) is now hidden before login. There is a slight race condition after logging in since nothing explicitly waits for application_settings to be populated, but the only apparent issue in the gap before it loads is that the period selector is not populated. This doesn't seem like a huge deal, and that gap is very short.

Regarding dead code:

  • This PR removes routes/files and all code referencing UPLOAD_DIRECTORY. This seemed to be dead (posted about it on Slack here) and was problematic to keep around because it doesn't interact with the database and couldn't be easily tenant-scoped. Also, the implementation of the /api/files/delete endpoint was insecure and could have been used to arbitrarily delete any file on the machine!
  • I did not attempt for now to add filtering logic in db/subrecipients or routes/subrecipients since there is a concurrent PR Igor/recipients #260 that is removing/revamping these. I had previously made changes to several other now-deleted files that caused some issues when rebasing over Additional UI for upload tracking #249 edit: now that Igor/recipients #260 has merged, I did go back and add tenantId checks to code dealing with the revamped subrecipients model.

For DB seeds, the "dev" ones (which I think may also be used in prod deployments? Staging at least has data that looks like it came from them) still assume a single tenant with ID 0. Some of the "test" seeds add data for multiple tenants so that the tests can verify multitenant behaviors.

In this PR I also added a mechanism for writing API route tests using Supertest (this is conceptually similar to how API route tests work in GOST but imo a bit cleaner and something we might want to move GOST over to eventually). I added a few route tests verifying this works (health & sessions) and a few (exports) for multitenant, and I'd like to add some more, but I'm going to try to put those in a separate PR so this one stops growing and can land sooner.

…e login, and hide navbar on login since it uses some fields from applicationSettings

Note during rebase: originially I'd killed `loadApplicationSettings` in favor of `login` calling `doFetch('application_settings')`, but #249 added new logic to `loadApplicationSettings` so had to bring it back during rebase. We still do NOT call it before login in main.js anymore.
Rebase note: formerly these functions were in db/index
Base automatically changed from multitenant-migrations-1 to staging May 27, 2022 21:39
@mattbroussard mattbroussard changed the title [WIP] Multitenant: Add filtering by tenant_id Multitenant: Add filtering by tenant_id May 27, 2022
@mattbroussard mattbroussard marked this pull request as ready for review May 27, 2022 22:03
Copy link
Collaborator

@ajhyndman ajhyndman left a comment

Choose a reason for hiding this comment

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

Directionally, this looks great. Feel free to leave some of these TODOs in the code for @igor47 and I to resolve.

Here are my thoughts on how to adapt seeds:

We currently use db seed files primarily for unit tests and local execution. These shouldn't run in any production or staging environments. From that point of view, whatever changes we make to seed files should align to the goal of giving developers a basically usable configuration after running.

One approach could be to set initial users, reporting periods and application settings to the default tenant_id.

src/server/db/reporting-periods.js Outdated Show resolved Hide resolved
src/server/db/reporting-periods.js Outdated Show resolved Hide resolved
src/server/db/subrecipients.js Outdated Show resolved Hide resolved
src/server/db/uploads.js Outdated Show resolved Hide resolved
src/server/routes/exports.js Outdated Show resolved Hide resolved
@mattbroussard
Copy link
Collaborator Author

Github seems to have done some confusing thing with my stacked PRs... Since #259 was merged, it says:

Base automatically changed from multitenant-migrations-1 to staging 13 days ago

But then there are commits I've pushed to this branch past multitenant-migrations-1 that are not in staging yet, but they do not show up on the PR page. The changes from them do show up in "Files changed" though.

Specifically:

  • 6faa25d chore: rm TODO comment about missing user arg; the bad callsite was deleted
  • 9f4482b chore: add missing trns to a few getCurrentReportingPeriodID callsites

…ntually removing the default value from the column
Copy link
Contributor

@igor47 igor47 left a comment

Choose a reason for hiding this comment

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

left comments of varying pertinence throughout the diff here, but nothing is blocking. maybe just go through my comments and make sure nothing jumps out at you?

@@ -1,22 +1,27 @@
require('dotenv').config()

exports.seed = async function (knex) {
// NOTE(mbroussard): unclear to me if this seed will be used again, but I'm updating it to minimally
Copy link
Contributor

Choose a reason for hiding this comment

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

it probably won't get used again; we should maybe just drop this file, and also the migration script in scripts esp now that we have a blueprint

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

OK. I'm probably not going to do that in this PR, but if you'd like to kill it separately, go ahead 👍

src/components/Navigation.vue Outdated Show resolved Hide resolved
src/components/Navigation.vue Show resolved Hide resolved
Comment on lines -14 to -15
} else {
await store.dispatch('updateApplicationSettings')
Copy link
Contributor

Choose a reason for hiding this comment

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

why?

Copy link
Collaborator Author

@mattbroussard mattbroussard Jun 24, 2022

Choose a reason for hiding this comment

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

(Answered above and in PR description)

@@ -6,8 +6,13 @@ function baseQuery (trns) {
.select('*')
}

function agencies (trns = knex) {
function agencies (tenantId, trns = knex) {
if (tenantId === 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 do we explicitly test for undefined ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Because the default tenant ID is 0 which is falsey, so if (tenantId) is not enough.

src/server/services/validation-rules.js Outdated Show resolved Hide resolved
@@ -33,10 +34,19 @@ router.post('/', requireAdminUser, async function (req, res, next) {

try {
if (agencyInfo.id) {
const existingAgency = await agencyById(agencyInfo.id)
if (!existingAgency || existingAgency.tenant_id !== req.session.user.tenant_id) {
Copy link
Contributor

Choose a reason for hiding this comment

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

huh, how would the second condition ever happen?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Someone could do a request passing in an agency ID that is valid but refers to an agency of a different tenant.

@@ -4,11 +4,17 @@ const _ = require('lodash')

Copy link
Contributor

Choose a reason for hiding this comment

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

don't think the exports routes are in-use

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Well, the code exists still and it theoretically could be called, so it needs to obey multitenant rules. If you want to kill it after this PR merges, that's cool 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm, it does seem in use: the main "Download Treasury Report" button on the dashboard links to /api/exports

src/server/routes/configuration.js Outdated Show resolved Hide resolved
src/server/routes/uploads.js Show resolved Hide resolved
@mattbroussard mattbroussard merged commit a19ec28 into staging Jun 24, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants