-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Decentralized authorization app (#240)
* Decentralized authorization app * Fixed the readme.md * Added role and user tables * Added role and user tables * Updated the route to check if a user is allowed an action * Fixed review comments from Amaury * Updated README.md --------- Co-authored-by: Yagnesh Setti <[email protected]>
- Loading branch information
1 parent
8c021e9
commit 1a999c0
Showing
27 changed files
with
1,392 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Decentralized AuthZ application | ||
|
||
This is the _CCF Decentralized AuthZ app - sample_ in typescript. | ||
|
||
## Overview | ||
|
||
The CCF network will be used to host a decentralized RBAC application where a consortium of members from different organizations would manage the roles, allowed action for a role and users. A user would have a specific role that would determine the allowed action. | ||
|
||
A service could use the decentralized RBAC application to determine if an action is allowed for a logged-in user. | ||
|
||
## Architecture | ||
|
||
The application consists of two parts: Role and User Management, Authorization. | ||
|
||
- Role and User Management | ||
- API Endpoint: allow members to add a role and action allowed for a role. | ||
- API Endpoint: allow members to add a user and their role. | ||
- Authorization | ||
- Check if a user exist and an action is allowed. | ||
|
||
### Repository Layout | ||
|
||
```text | ||
📂 | ||
└── src Application source code | ||
| └── auth Member and User cert Authentication | ||
│ └── endpoints Application endpoints | ||
│ └── repositories Data repositories | ||
│ └── services Domain services | ||
│ └── utils utility classes | ||
``` | ||
|
||
## Getting Started | ||
|
||
To get started and run the application locally, start with setting up the environment. | ||
|
||
```bash | ||
# setup the environment | ||
git clone https://github.com/microsoft/ccf-app-samples # Clone the samples repository | ||
code ccf-app-samples # open samples repository in Visual studio code | ||
|
||
# In the VScode terminal window | ||
cd decentralized-authz-app # Navigate to app folder | ||
npm run build # Build and create the application deployment bundle | ||
``` | ||
|
||
## Local Deployment |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"presets": [ | ||
[ | ||
"@babel/preset-env", | ||
{ | ||
"targets": { | ||
"node": "current" | ||
} | ||
} | ||
], | ||
"@babel/preset-typescript" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { readdirSync, statSync, readFileSync, writeFileSync } from "fs"; | ||
import { join, posix, sep } from "path"; | ||
|
||
const args = process.argv.slice(2); | ||
|
||
const getAllFiles = function (dirPath, arrayOfFiles) { | ||
arrayOfFiles = arrayOfFiles || []; | ||
|
||
const files = readdirSync(dirPath); | ||
for (const file of files) { | ||
const filePath = join(dirPath, file); | ||
if (statSync(filePath).isDirectory()) { | ||
arrayOfFiles = getAllFiles(filePath, arrayOfFiles); | ||
} else { | ||
arrayOfFiles.push(filePath); | ||
} | ||
} | ||
|
||
return arrayOfFiles; | ||
}; | ||
|
||
const removePrefix = function (s, prefix) { | ||
return s.substr(prefix.length).split(sep).join(posix.sep); | ||
}; | ||
|
||
const rootDir = args[0]; | ||
|
||
const metadataPath = join(rootDir, "app.json"); | ||
const metadata = JSON.parse(readFileSync(metadataPath, "utf-8")); | ||
|
||
const srcDir = join(rootDir, "src"); | ||
const allFiles = getAllFiles(srcDir); | ||
|
||
// The trailing / is included so that it is trimmed in removePrefix. | ||
// This produces "foo/bar.js" rather than "/foo/bar.js" | ||
const toTrim = srcDir + "/"; | ||
|
||
const modules = allFiles.map(function (filePath) { | ||
return { | ||
name: removePrefix(filePath, toTrim), | ||
module: readFileSync(filePath, "utf-8"), | ||
}; | ||
}); | ||
|
||
const bundlePath = join(args[0], "bundle.json"); | ||
const appRegPath = join(args[0], "set_js_app.json"); | ||
const bundle = { | ||
metadata: metadata, | ||
modules: modules, | ||
}; | ||
const app_reg = { | ||
actions: [ | ||
{ | ||
name: "set_js_app", | ||
args: { | ||
bundle: bundle, | ||
disable_bytecode_cache: false, | ||
}, | ||
}, | ||
], | ||
}; | ||
|
||
console.log( | ||
`Writing bundle containing ${modules.length} modules to ${bundlePath}` | ||
); | ||
writeFileSync(bundlePath, JSON.stringify(bundle)); | ||
writeFileSync(appRegPath, JSON.stringify(app_reg)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
* For a detailed explanation regarding each configuration property and type check, visit: | ||
* https://jestjs.io/docs/configuration | ||
* https://jestjs.io/docs/ecmascript-modules | ||
* https://microsoft.github.io/CCF/main/js/ccf-app/modules/polyfill.html | ||
* To Run: Add to package.json > "scripts": {"unit-test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"} | ||
*/ | ||
|
||
export default { | ||
// Indicates whether the coverage information should be collected while executing the test | ||
collectCoverage: true, | ||
|
||
// An array of glob patterns indicating a set of files for which coverage information should be collected | ||
collectCoverageFrom: ["./src/**/*.ts"], | ||
|
||
// The directory where Jest should output its coverage files | ||
coverageDirectory: "test/coverage", | ||
|
||
// An array of file extensions your modules use | ||
moduleFileExtensions: [ | ||
"js", | ||
"mjs", | ||
"cjs", | ||
"jsx", | ||
"ts", | ||
"tsx", | ||
"json", | ||
"node", | ||
], | ||
|
||
// A preset that is used as a base for Jest's configuration | ||
// preset: undefined, | ||
// preset: 'ts-jest', | ||
|
||
// A list of paths to directories that Jest should use to search for files in | ||
roots: ["./"], | ||
|
||
// The test environment that will be used for testing | ||
testEnvironment: "node", | ||
|
||
testMatch: ["**/test/unit-test/**/*.test.(ts|js|mjs)"], | ||
|
||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped | ||
testPathIgnorePatterns: ["/node_modules/", "/lib/"], | ||
|
||
// This option allows use of a custom test runner | ||
// testRunner: "jest-circus/runner", | ||
|
||
// A map from regular expressions to paths to transformers | ||
// transform: undefined, | ||
transform: { "^.+\\.[t|j]sx?$": "babel-jest" }, | ||
|
||
extensionsToTreatAsEsm: [".ts"], | ||
|
||
// Indicates whether each individual test should be reported during the run | ||
verbose: true, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
{ | ||
"private": true, | ||
"scripts": { | ||
"build": "del-cli -f dist/ && rollup --config && cp src/endpoints/app.json dist/ && node build_bundle.js dist/", | ||
"bundle": "node build_bundle.js dist", | ||
"unit-test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", | ||
"create-jwt-config": "ts-node --esm ./test/utils/jwt-config-generator.ts", | ||
"e2e-test": "ts-node --esm ./test/e2e-test/src/index.ts" | ||
}, | ||
"type": "module", | ||
"engines": { | ||
"node": ">=14" | ||
}, | ||
"dependencies": { | ||
"@microsoft/ccf-app": "^4.0.7", | ||
"axios": "^1.2.4", | ||
"crypto-js": "^3.1.9-1", | ||
"inquirer": "9.1.4", | ||
"js-base64": "^3.5.2", | ||
"jsonwebtoken": "^9.0.0", | ||
"jsrsasign": "^10.0.4", | ||
"jsrsasign-util": "^1.0.2", | ||
"jwt-decode": "^3.0.0", | ||
"lodash-es": "^4.17.15", | ||
"node-forge": "^1.3.1", | ||
"protobufjs": "^7.2.4" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.20.5", | ||
"@babel/preset-env": "^7.20.2", | ||
"@babel/preset-typescript": "^7.18.6", | ||
"@jest/globals": "^29.3.1", | ||
"@rollup/plugin-commonjs": "^17.1.0", | ||
"@rollup/plugin-node-resolve": "^11.2.0", | ||
"@rollup/plugin-typescript": "^8.2.0", | ||
"@types/inquirer": "^9.0.3", | ||
"@types/jasmine": "^4.3.0", | ||
"@types/jest": "^29.2.4", | ||
"@types/jsrsasign": "^8.0.7", | ||
"@types/lodash-es": "^4.17.3", | ||
"@types/mocha": "^10.0.0", | ||
"@types/node": "^18.11.9", | ||
"axios": "^1.2.2", | ||
"babel-jest": "^29.3.1", | ||
"del-cli": "^3.0.1", | ||
"http-server": "^0.13.0", | ||
"jest": "^29.3.1", | ||
"rollup": "^2.41.0", | ||
"ts-jest": "^29.0.3", | ||
"ts-node": "^10.9.1", | ||
"tslib": "^2.0.1", | ||
"typescript": "^4.9.4", | ||
"@typescript-eslint/eslint-plugin": "^5.48.1", | ||
"@typescript-eslint/parser": "^5.48.1", | ||
"eslint": "^8.28.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-config-standard-with-typescript": "^4.0.0", | ||
"eslint-plugin-import": "^2.27.4", | ||
"eslint-plugin-n": "^15.5.1", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"eslint-plugin-promise": "^6.1.1", | ||
"eslint-plugin-unused-imports": "^2.0.0", | ||
"prettier": "^2.8.0", | ||
"rimraf": "^3.0.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { nodeResolve } from "@rollup/plugin-node-resolve"; | ||
import commonjs from "@rollup/plugin-commonjs"; | ||
import typescript from "@rollup/plugin-typescript"; | ||
|
||
export default { | ||
input: "src/endpoints/all.ts", | ||
output: { | ||
dir: "dist/src", | ||
format: "es", | ||
preserveModules: true, | ||
preserveModulesRoot: "src", | ||
}, | ||
plugins: [nodeResolve(), typescript(), commonjs()], | ||
}; |
67 changes: 67 additions & 0 deletions
67
decentralize-rbac-app/src/auth/validator/certificate/member-cert-validation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import * as ccfapp from "@microsoft/ccf-app"; | ||
import { ccf } from "@microsoft/ccf-app/global"; | ||
import { ServiceResult } from "../../../utils/service-result"; | ||
import { IValidatorService } from "../validation-service"; | ||
import { UserMemberAuthnIdentity } from "./user-cert-validation"; | ||
|
||
/** | ||
* CCF member information | ||
* https://microsoft.github.io/CCF/main/audit/builtin_maps.html#members-info | ||
*/ | ||
enum MemberStatus{ | ||
ACCEPTED = "Accepted", | ||
ACTIVE = "Active" | ||
} | ||
|
||
interface CCFMember { | ||
status: MemberStatus; | ||
} | ||
|
||
export class MemberCertValidator implements IValidatorService { | ||
validate(request: ccfapp.Request<any>): ServiceResult<string> { | ||
const memberCaller = request.caller as unknown as UserMemberAuthnIdentity; | ||
const identityId = memberCaller.id; | ||
const isValid = this.isActiveMember(identityId); | ||
if (isValid.success && isValid.content) { | ||
return ServiceResult.Succeeded(identityId); | ||
} | ||
return ServiceResult.Failed({ | ||
errorMessage: "Error: invalid caller identity", | ||
errorType: "AuthenticationError", | ||
}); | ||
} | ||
|
||
/** | ||
* Checks if a member exists and active | ||
* @see https://microsoft.github.io/CCF/main/audit/builtin_maps.html#members-info | ||
* @param {string} memberId memberId to check if it exists and active | ||
* @returns {ServiceResult<boolean>} | ||
*/ | ||
public isActiveMember(memberId: string): ServiceResult<boolean> { | ||
const membersCerts = ccfapp.typedKv( | ||
"public:ccf.gov.members.certs", | ||
ccfapp.string, | ||
ccfapp.arrayBuffer | ||
); | ||
|
||
const isMember = membersCerts.has(memberId); | ||
|
||
const membersInfo = ccfapp.typedKv( | ||
"public:ccf.gov.members.info", | ||
ccfapp.string, | ||
ccfapp.json<CCFMember>() | ||
); | ||
|
||
const memberInfo = membersInfo.get(memberId); | ||
|
||
const isActiveMember = memberInfo && memberInfo.status === MemberStatus.ACTIVE; | ||
|
||
return ServiceResult.Succeeded(isActiveMember && isMember); | ||
} | ||
} | ||
|
||
/** | ||
* Export the member cert validator | ||
*/ | ||
const memberCertValidator: IValidatorService = new MemberCertValidator(); | ||
export default memberCertValidator; |
Oops, something went wrong.