Skip to content

Commit

Permalink
Merge pull request #9 from getlarge/8-add-support-for-logical-operato…
Browse files Browse the repository at this point in the history
…rs-and-or-in-permission-checks

feat: add support for logical operators and or in permission checks
  • Loading branch information
getlarge authored Apr 3, 2024
2 parents 7bdcdf5 + 18ff575 commit cedcdc4
Show file tree
Hide file tree
Showing 21 changed files with 424 additions and 131 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ on:
env:
CI: true

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-CI

jobs:
main:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -41,7 +44,7 @@ jobs:

- uses: nrwl/nx-set-shas@v4

- uses: 8BitJonny/gh-get-current-pr@2.2.0
- uses: 8BitJonny/gh-get-current-pr@3.0.0
id: current-pr

- if: steps.current-pr.outputs.number != 'null' && github.ref_name != 'main'
Expand Down
12 changes: 7 additions & 5 deletions nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,10 @@
"changelog": {
"projectChangelogs": {
"createRelease": "github"
},
"workspaceChangelog": {
"createRelease": "github"
}
},
"git": {
"commit": true,
"push": true,
"pushRemote": "origin",
"tag": true
}
},
Expand Down Expand Up @@ -57,6 +52,13 @@
"codeCoverage": true
}
}
},
"nx-release-publish": {
"options": {
"packageRoot": "dist/{projectRoot}",
"registry": "https://registry.npmjs.org/"
},
"dependsOn": ["build"]
}
},
"nxCloudAccessToken": "ZDU0ZWUwNDktM2YzZC00ODgzLWFiNTktYzQzNDVmOWZlNTE0fHJlYWQtd3JpdGU="
Expand Down
33 changes: 8 additions & 25 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"@nestjs/platform-express": "^10.0.2",
"@ory/client": "^1.4.9",
"defekt": "^9.3.1",
"lodash.get": "^4.4.2",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"tslib": "^2.3.0"
Expand All @@ -28,7 +27,6 @@
"@swc-node/register": "~1.6.7",
"@swc/core": "~1.3.85",
"@types/jest": "^29.4.0",
"@types/lodash.get": "^4.4.9",
"@types/node": "18.16.9",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "^6.9.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/keto-client-wrapper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"@getlarge/keto-relations-parser": "0.0.3",
"tslib": "^2.3.0",
"defekt": "^9.3.1",
"lodash.get": "^4.4.2"
"rxjs": "^7.8.0"
},
"peerDependencies": {
"axios": "1.6.5",
Expand Down
7 changes: 1 addition & 6 deletions packages/keto-client-wrapper/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,7 @@
"dependsOn": ["build"]
},
"nx-release-publish": {
"executor": "@nx/js:release-publish",
"options": {
"packageRoot": "dist/packages/keto-client-wrapper",
"registry": "https://registry.npmjs.org/"
},
"dependsOn": ["build"]
"executor": "@nx/js:release-publish"
},
"lint": {
"executor": "@nx/eslint:lint",
Expand Down
150 changes: 150 additions & 0 deletions packages/keto-client-wrapper/src/lib/ory-authorization.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { OryBaseService } from '@getlarge/base-client-wrapper';
import { ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test, TestingModule } from '@nestjs/testing';
import { AxiosHeaders } from 'axios';

import {
IAuthorizationGuard,
OryAuthorizationGuard,
} from './ory-authorization.guard';
import { EnhancedRelationTupleFactory } from './ory-permission-checks.decorator';
import {
OryPermissionsModuleOptions,
OryPermissionsService,
} from './ory-permissions';

const mockAxiosResponse = <D = object>(data: D) => {
return {
status: 200,
statusText: 'OK',
headers: {},
config: {
url: 'http://localhost',
headers: new AxiosHeaders(),
},
data,
};
};

describe('OryAuthorizationGuard', () => {
let oryPermissionsService: OryPermissionsService;
let reflector: Reflector;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
OryPermissionsService,
{
provide: OryPermissionsModuleOptions,
useValue: { basePath: 'http://localhost' },
},
{
provide: OryBaseService,
useValue: {
axios: {
request: jest.fn(),
},
},
},
],
}).compile();

oryPermissionsService = module.get(OryPermissionsService);
reflector = module.get(Reflector);
});

describe('evaluateConditions', () => {
let oryAuthorizationGuard: IAuthorizationGuard;
let context: ExecutionContext;

beforeEach(() => {
context = {} as ExecutionContext; // Mock execution context
const guardFactory = OryAuthorizationGuard({});
oryAuthorizationGuard = new guardFactory(
reflector,
oryPermissionsService
);
});

it('should allow access for a single permitted relation tuple', async () => {
const factory = () => 'user:123#access@resource:456';
jest
.spyOn(oryPermissionsService, 'checkPermission')
.mockResolvedValue(mockAxiosResponse({ allowed: true }));

const result = await oryAuthorizationGuard.evaluateConditions(
factory,
context
);
expect(result.allowed).toBe(true);
});

it('should deny access if one relation in AND condition is not permitted', async () => {
const factory = {
type: 'AND',
conditions: [
() => 'user:123#access@resource:456',
() => 'user:123#access@resource:789',
],
} satisfies EnhancedRelationTupleFactory;
jest
.spyOn(oryPermissionsService, 'checkPermission')
.mockResolvedValueOnce(mockAxiosResponse({ allowed: true }))
.mockResolvedValueOnce(mockAxiosResponse({ allowed: false }));

const result = await oryAuthorizationGuard.evaluateConditions(
factory,
context
);
expect(result.allowed).toBe(false);
});

it('should allow access if one relation in OR condition is permitted', async () => {
const factory = {
type: 'OR',
conditions: [
() => 'user:123#access@resource:456',
() => 'user:123#access@resource:789',
],
} satisfies EnhancedRelationTupleFactory;
jest
.spyOn(oryPermissionsService, 'checkPermission')
.mockResolvedValueOnce(mockAxiosResponse({ allowed: false }))
.mockResolvedValueOnce(mockAxiosResponse({ allowed: true }));

const result = await oryAuthorizationGuard.evaluateConditions(
factory,
context
);
expect(result.allowed).toBe(true);
});

it('should correctly evaluate nested AND/OR conditions', async () => {
const factory = {
type: 'AND',
conditions: [
{
type: 'OR',
conditions: [
() => 'user:123#access@resource:456',
() => 'user:123#access@resource:789',
],
},
() => 'user:123#access@resource:101',
],
} satisfies EnhancedRelationTupleFactory;
jest
.spyOn(oryPermissionsService, 'checkPermission')
.mockResolvedValueOnce(mockAxiosResponse({ allowed: true }))
.mockResolvedValueOnce(mockAxiosResponse({ allowed: true }))
.mockResolvedValueOnce(mockAxiosResponse({ allowed: true }));

const result = await oryAuthorizationGuard.evaluateConditions(
factory,
context
);
expect(result.allowed).toBe(true);
});
});
});
Loading

0 comments on commit cedcdc4

Please sign in to comment.