Skip to content
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

Feature/permissions #1176

Merged
merged 103 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from 85 commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
f80aab1
update prisma and typescript
bryan-robitaille Sep 23, 2022
57208f8
wip to add CASL attribute based access list control
bryan-robitaille Oct 3, 2022
62f792f
Change to mongoAbility for conditions
bryan-robitaille Oct 3, 2022
338c960
update seed file
bryan-robitaille Oct 4, 2022
7e1847b
fix typescript error in _app.tsx
bryan-robitaille Oct 5, 2022
6784556
wip for users page and cache
bryan-robitaille Oct 5, 2022
d8a601a
interface to update permissions
bryan-robitaille Oct 5, 2022
be2800f
create privelages API
bryan-robitaille Oct 5, 2022
43b37ab
create a privelage
bryan-robitaille Oct 6, 2022
b3dbd1d
learned how to spell privelege
bryan-robitaille Oct 6, 2022
0a99236
still learning how to spell privilege
bryan-robitaille Oct 6, 2022
4c8aaa5
merge from develop
bryan-robitaille Oct 7, 2022
c797cb1
feat: remove hosted by CDS mention from the footer (#1116)
craigzour Oct 7, 2022
2ae3832
Fix spelling in imports
bryan-robitaille Oct 11, 2022
f7eb2bb
Merge branch 'develop' into feature/permissions
bryan-robitaille Oct 11, 2022
8a0998b
use lib prefix instead of path
bryan-robitaille Oct 11, 2022
7608d5f
fix session user object
bryan-robitaille Oct 11, 2022
356921d
fix legacy API + renaming files to privileges instead of privilages
craigzour Oct 11, 2022
0f6b0ab
Users are associated to Templates (#1131)
bryan-robitaille Oct 11, 2022
2968fdd
Feature/permission migration (#1133)
bryan-robitaille Oct 13, 2022
70e3a81
FormUser to ApiUser migration
bryan-robitaille Oct 13, 2022
aa66b8b
align tests with apiUser rename
bryan-robitaille Oct 13, 2022
fb34186
feat: add permission condition interpolation feature (#1135)
craigzour Oct 13, 2022
0c33d80
Merge branch 'feature/permissions' into feature/permission_api_user
bryan-robitaille Oct 13, 2022
2fe8fa0
fix: postgresql does not support gen_random_uuid
craigzour Oct 13, 2022
03df634
Merge branch 'feature/permissions' into feature/permission_api_user
bryan-robitaille Oct 13, 2022
b00fe0f
fix: default privileges definition clashes with the interpolation fun…
craigzour Oct 14, 2022
d246959
merge in develop
bryan-robitaille Oct 14, 2022
de9a060
merge in develop
bryan-robitaille Oct 17, 2022
d514368
fix yarn.lock
bryan-robitaille Oct 17, 2022
69e7efe
merge in base branch
bryan-robitaille Oct 17, 2022
68aec41
recreate yarn.lock
bryan-robitaille Oct 17, 2022
72a722f
updated checkPrivileges fn
bryan-robitaille Oct 17, 2022
fe8815d
Merge branch 'feature/permissions' into feature/permission_api_user
bryan-robitaille Oct 17, 2022
03cf764
wip
bryan-robitaille Oct 17, 2022
c394e74
wip
bryan-robitaille Oct 18, 2022
67a6b2f
tests for apiUsers
bryan-robitaille Oct 18, 2022
3998b90
remove unrequird export
bryan-robitaille Oct 18, 2022
5b16537
auth tests
bryan-robitaille Oct 18, 2022
fdafda8
Merge pull request #1150 from cds-snc/feature/permission_api_user
bryan-robitaille Oct 18, 2022
5f1d296
Merge branch 'feature/permissions' into feature/permissions_auth_and_…
bryan-robitaille Oct 18, 2022
8826ddc
wip
bryan-robitaille Oct 18, 2022
34684f7
Feature/protect crud functions with permissions (#1151)
craigzour Oct 18, 2022
bde41de
formatting error in flags test
bryan-robitaille Oct 18, 2022
d89dd85
Merge branch 'feature/permissions' into feature/permissions_auth_and_…
bryan-robitaille Oct 18, 2022
a705297
user tests modifications
bryan-robitaille Oct 19, 2022
3109fbf
fix bearer token api and acceptable use
bryan-robitaille Oct 19, 2022
1c938dc
Fix typing for middleware
bryan-robitaille Oct 19, 2022
5b18caa
Remove references to UserRole
bryan-robitaille Oct 19, 2022
5632634
small cleanup
bryan-robitaille Oct 19, 2022
f9ffb2f
fix casing error
bryan-robitaille Oct 19, 2022
25ee815
refactor prismaErrors placement
bryan-robitaille Oct 19, 2022
48743bc
Fix flags for Cypress
bryan-robitaille Oct 20, 2022
808dce1
enhance flags tests to use mocked redis
bryan-robitaille Oct 20, 2022
0b1bf1d
Merge pull request #1154 from cds-snc/feature/permissions_auth_and_flags
bryan-robitaille Oct 20, 2022
4cd1dd9
Ignore prisma cannot connect db when testing
bryan-robitaille Oct 20, 2022
dac3ef1
Removing unused experimentation file
bryan-robitaille Oct 20, 2022
cd57b07
Feature/add permission tests (#1155)
craigzour Oct 20, 2022
12712dd
fix NODE_ENV ref instead of APP_ENV
bryan-robitaille Oct 21, 2022
7a84f47
feat: enhance getAllTemplates API function to return either all or so…
craigzour Oct 24, 2022
8d5fb81
screens to manage user privileges
bryan-robitaille Oct 24, 2022
430167d
permissions should be a list
bryan-robitaille Oct 24, 2022
892ff41
Merge pull request #1174 from cds-snc/feature/permissions_user_settings
bryan-robitaille Oct 24, 2022
0708f54
disable Vault page
bryan-robitaille Oct 24, 2022
0aaa278
Disable Retrieval page until feature is ready
bryan-robitaille Oct 24, 2022
5c1d610
mark fn as internal only
bryan-robitaille Oct 24, 2022
f0ed075
Pages wrapped with privilege checks
bryan-robitaille Oct 24, 2022
b8eacac
Security enhancement acceptable-use
bryan-robitaille Oct 24, 2022
c9d20c5
refactor for ability to be first arg
bryan-robitaille Oct 24, 2022
2c1055a
hide create privilege button
bryan-robitaille Oct 24, 2022
e416454
nav links based on privileges
bryan-robitaille Oct 24, 2022
09846b9
reflect privilege change, call hook refresh
bryan-robitaille Oct 24, 2022
f319c9c
quick changes
bryan-robitaille Oct 24, 2022
7965716
Merge pull request #1175 from cds-snc/feature/permissions_page_access
bryan-robitaille Oct 25, 2022
54c93ed
Merge branch 'develop' into feature/permissions
bryan-robitaille Oct 25, 2022
3ed00e6
small tweaks
bryan-robitaille Oct 25, 2022
c5901fe
add priority to privileges
bryan-robitaille Oct 25, 2022
eea438f
return priority on all privileges
bryan-robitaille Oct 25, 2022
a38c20b
small tweaks on type and order
bryan-robitaille Oct 25, 2022
b8204ed
eslint unused vars
bryan-robitaille Oct 25, 2022
f5ac972
pinning aws types
bryan-robitaille Oct 25, 2022
04727e5
temp redirect to admin
bryan-robitaille Oct 25, 2022
5ada5d1
update change log
bryan-robitaille Oct 25, 2022
7ed9eb4
Merge branch 'develop' into feature/permissions
bryan-robitaille Oct 26, 2022
c03f2c3
adding declaration for typescript
bryan-robitaille Oct 26, 2022
8251496
refactored debug logging
bryan-robitaille Oct 27, 2022
d1994d7
redirect to cognito login
bryan-robitaille Oct 27, 2022
7281054
remove uneeded privileges on token
bryan-robitaille Oct 27, 2022
70fd260
remove outdated comment
bryan-robitaille Oct 27, 2022
9019f9e
moved _isForceTyping fn
bryan-robitaille Oct 27, 2022
5cc2f06
Handle prisma seed migrations in script
bryan-robitaille Oct 27, 2022
c468bde
remove lodash + add lodash.get
bryan-robitaille Oct 28, 2022
23f8f6f
refactor privilege types and files
bryan-robitaille Oct 28, 2022
6db8bf2
Fix session refetch time for inactive users
bryan-robitaille Oct 28, 2022
977414a
remove vault page
bryan-robitaille Oct 28, 2022
7c75b29
users small refactor
bryan-robitaille Oct 28, 2022
4c74ac1
prismaErrors refactor
bryan-robitaille Oct 28, 2022
5f56db9
cache refactoring
bryan-robitaille Oct 28, 2022
ea48d22
unused param
bryan-robitaille Oct 28, 2022
1cd962a
remove nested try catch block
bryan-robitaille Oct 28, 2022
5d5a4e9
update next-i18next + peer dependencies
bryan-robitaille Oct 28, 2022
924e497
error handling refactor on checkPrivileges
bryan-robitaille Oct 28, 2022
1c23482
Merge branch 'develop' into feature/permissions
bryan-robitaille Oct 28, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- Updated Terms and conditions page + text link in the footer [#863](https://github.com/cds-snc/platform-forms-client/issues/863)
- Modified Role Based to Asset Based Access Control [#1176](https://github.com/cds-snc/platform-forms-client/pull/1176)

### Fixed

Expand Down Expand Up @@ -129,7 +130,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Renamed `organisation` to `organization` which has an impact on the API access path
- Modified the middleware functionality and separation of scopes between middlewares
- A user now needs to have an enabled admin flag (user table) to access the Admin Pages
- An admin user can now add and remove administrative priveleges from other users.
- An admin user can now add and remove administrative privileges from other users.

### Fixed

Expand Down
30 changes: 25 additions & 5 deletions __tests__/api/acceptable-use.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,32 @@ import { createMocks } from "node-mocks-http";
import acceptableUse from "@pages/api/acceptableuse";
import { setAcceptableUse } from "@lib/acceptableUseCache";
import { getCsrfToken } from "next-auth/react";
import { unstable_getServerSession } from "next-auth/next";
import { Session } from "next-auth";

jest.mock("next-auth/next");
jest.mock("next-auth/react");
jest.mock("@lib/acceptableUseCache");
const mockedSetAcceptableUse = jest.mocked(setAcceptableUse, true);
const mockedGetCsrfToken = jest.mocked(getCsrfToken, true);
const mockedSetAcceptableUse = jest.mocked(setAcceptableUse, { shallow: true });
const mockedGetCsrfToken = jest.mocked(getCsrfToken, { shallow: true });
//Needed in the typescript version of the test so types are inferred correclty
const mockGetSession = jest.mocked(unstable_getServerSession, { shallow: true });

describe("Test acceptable use endpoint", () => {
beforeEach(() => {
const mockSession: Session = {
expires: "1",
user: {
id: "1",
email: "forms@cds.ca",
name: "forms user",
privileges: [],
},
};

mockGetSession.mockResolvedValue(mockSession);
});
afterEach(() => mockGetSession.mockReset());
mockedGetCsrfToken.mockResolvedValue("CsrfToken");
it("Should set acceptableuse value to true for userID 1 and return 200", async () => {
const { req, res } = createMocks({
Expand All @@ -30,7 +49,8 @@ describe("Test acceptable use endpoint", () => {
expect(res.statusCode).toBe(200);
});

it("Should return 404 for undefined userID", async () => {
it("Should return 401 for unauthenticated user", async () => {
mockGetSession.mockReset();
const { req, res } = createMocks({
method: "POST",
headers: {
Expand All @@ -43,8 +63,8 @@ describe("Test acceptable use endpoint", () => {
},
});
await acceptableUse(req, res);
expect(res.statusCode).toBe(404);
expect(JSON.parse(res._getData())).toEqual(expect.objectContaining({ error: "Bad request" }));
expect(res.statusCode).toBe(401);
expect(JSON.parse(res._getData())).toEqual(expect.objectContaining({ error: "Unauthorized" }));
});

it("Should throw an error and return 500 status code", async () => {
Expand Down
280 changes: 195 additions & 85 deletions __tests__/api/flags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,123 +7,233 @@ import enable from "@pages/api/flags/[key]/enable";
import disable from "@pages/api/flags/[key]/disable";
import check from "@pages/api/flags/[key]/check";
import checkAllFlags from "@pages/api/flags";
import defaultFlags from "../../flag_initialization/default_flag_settings.json";
import * as flags from "@lib/flags";
import { UserRole } from "@prisma/client";
jest.mock("next-auth/next");

//Needed in the typescript version of the test so types are inferred correclty
const mockGetSession = jest.mocked(unstable_getServerSession, true);
import { ViewApplicationSettings, ManageApplicationSettings } from "__utils__/permissions";
import Redis from "ioredis-mock";
const redis = new Redis();

jest.mock("@lib/flags");
jest.mock("@lib/integration/redisConnector", () => ({
getRedisInstance: jest.fn(() => redis),
}));

const mockedFlags = jest.mocked(flags, true);
jest.mock("next-auth/next");

const { checkOne, checkAll, enableFlag, disableFlag } = mockedFlags;
//Needed in the typescript version of the test so types are inferred correclty
const mockGetSession = jest.mocked(unstable_getServerSession, { shallow: true });

describe("Flags API endpoint", () => {
beforeAll(() => {
// Adding URL to process.env because we are mocking out Redis for these tests
process.env.REDIS_URL = "test_url";
});
afterAll(() => {
delete process.env.REDIS_URL;
});
describe("Authenticated", () => {
beforeEach(() => {
const mockSession = {
expires: "1",
user: {
email: "forms@cds.ca",
name: "forms user",
role: UserRole.ADMINISTRATOR,
userId: "1",
},
};
mockGetSession.mockResolvedValue(mockSession);
});
afterEach(() => {
mockGetSession.mockReset();
});

it("Enable a feature flag", async () => {
enableFlag.mockImplementation(jest.fn());
checkAll.mockResolvedValue(defaultFlags);

const { req, res } = createMocks({
method: "GET",
headers: {
"Content-Type": "application/json",
Origin: "http://localhost:3000",
},
url: "/api/flags/featureFlag/enable",
query: {
key: "featureFlag",
},
describe("ViewApplicationSettings", () => {
beforeEach(async () => {
const mockSession = {
expires: "1",
user: {
email: "forms@cds.ca",
name: "forms user",
id: "1",
privileges: ViewApplicationSettings,
},
};
mockGetSession.mockResolvedValue(mockSession);
// Intialize mockRedis with default flags
const testFlags = {
flag1: true,
flag2: false,
};
for (const [key, value] of Object.entries(testFlags)) {
await redis
.multi()
.sadd("flags", key)
.set(`flag:${key}`, value ? "1" : "0")
.exec();
}
});
afterEach(() => {
redis.flushall();
mockGetSession.mockReset();
});

await enable(req, res);
it("Enable a feature flag", async () => {
const { req, res } = createMocks({
method: "GET",
headers: {
"Content-Type": "application/json",
Origin: "http://localhost:3000",
},
url: "/api/flags/featureFlag/enable",
query: {
key: "featureFlag",
},
});

await enable(req, res);

expect(res.statusCode).toBe(403);
expect(res._getJSONData()).toMatchObject({ error: "Forbidden" });
});

expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toMatchObject(defaultFlags);
expect(enableFlag).toHaveBeenCalledWith("featureFlag");
it("Disable a feature flag", async () => {
const { req, res } = createMocks({
method: "GET",
headers: {
"Content-Type": "application/json",
Origin: "http://localhost:3000",
},
url: "/api/flags/featureFlag/disable",
query: {
key: "featureFlag",
},
});

await disable(req, res);

expect(res.statusCode).toBe(403);
expect(res._getJSONData()).toMatchObject({ error: "Forbidden" });
});
it("Gets a list of all feature flags", async () => {
const { req, res } = createMocks({
method: "GET",
headers: {
"Content-Type": "application/json",
Origin: "http://localhost:3000",
},
url: "/api/flags/",
});

await checkAllFlags(req, res);

expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toMatchObject({ flag1: true, flag2: false });
});
});

it("Disable a feature flag", async () => {
disableFlag.mockImplementation(jest.fn());
checkAll.mockResolvedValue(defaultFlags);

const { req, res } = createMocks({
method: "GET",
headers: {
"Content-Type": "application/json",
Origin: "http://localhost:3000",
},
url: "/api/flags/featureFlag/disable",
query: {
key: "featureFlag",
},
describe("ManageApplicationSettings", () => {
beforeEach(async () => {
const mockSession = {
expires: "1",
user: {
email: "forms@cds.ca",
name: "forms user",
id: "1",
privileges: ManageApplicationSettings,
},
};
mockGetSession.mockResolvedValue(mockSession);

// Intialize mockRedis with default flags
const testFlags = {
flag1: true,
flag2: false,
};
for (const [key, value] of Object.entries(testFlags)) {
await redis
.multi()
.sadd("flags", key)
.set(`flag:${key}`, value ? "1" : "0")
.exec();
}
});
afterEach(() => {
redis.flushall();
mockGetSession.mockReset();
});

await disable(req, res);
it("Enable a feature flag", async () => {
const { req, res } = createMocks({
method: "GET",
headers: {
"Content-Type": "application/json",
Origin: "http://localhost:3000",
},
url: "/api/flags/flag2/enable",
query: {
key: "flag2",
},
});

await enable(req, res);

expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toMatchObject({ flag2: true });
});

expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toMatchObject(defaultFlags);
expect(disableFlag).toHaveBeenCalledWith("featureFlag");
it("Disable a feature flag", async () => {
const { req, res } = createMocks({
method: "GET",
headers: {
"Content-Type": "application/json",
Origin: "http://localhost:3000",
},
url: "/api/flags/flag1/disable",
query: {
key: "flag1",
},
});

await disable(req, res);

expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toMatchObject({ flag1: false });
});
it("Gets a list of all feature flags", async () => {
const { req, res } = createMocks({
method: "GET",
headers: {
"Content-Type": "application/json",
Origin: "http://localhost:3000",
},
url: "/api/flags/",
});

await checkAllFlags(req, res);

expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toMatchObject({ flag1: true, flag2: false });
});
});
});

describe("Unauthenticated", () => {
it("Check a feature flag", async () => {
(checkOne as jest.Mock).mockReturnValue(true);
beforeAll(async () => {
// Intialize mockRedis with default flags
const testFlags = {
flag1: true,
flag2: false,
};
for (const [key, value] of Object.entries(testFlags)) {
await redis
.multi()
.sadd("flags", key)
.set(`flag:${key}`, value ? "1" : "0")
.exec();
}
});
afterAll(() => {
redis.flushall();
});

it("Check a feature flag", async () => {
const { req, res } = createMocks({
method: "GET",
headers: {
"Content-Type": "application/json",
Origin: "http://localhost:3000",
},
url: "/api/flags/featureFlag/check",
url: "/api/flags/flag1/check",
query: {
key: "featureFlag",
key: "flag1",
},
});

await check(req, res);

expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toMatchObject({ status: true });
expect(checkOne).toHaveBeenCalledWith("featureFlag");
});
it("Gets a list of all feature flags", async () => {
checkAll.mockResolvedValue(defaultFlags);

const { req, res } = createMocks({
method: "GET",
headers: {
"Content-Type": "application/json",
Origin: "http://localhost:3000",
},
url: "/api/flags/",
});

await checkAllFlags(req, res);

expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toMatchObject(defaultFlags);
});
});
});