-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #377 from amplication/feat/8533-dotnet-auth-plugin
Feat(dontet-auth): Add DOTNet authentication plugin
- Loading branch information
Showing
20 changed files
with
1,309 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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,6 @@ | ||
{ | ||
"settings": {}, | ||
"systemSettings": { | ||
"requireAuthenticationEntity": "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,8 @@ | ||
{ | ||
"extends": [ | ||
"../../.eslintrc.json" | ||
], | ||
"ignorePatterns": [ | ||
"!**/*" | ||
] | ||
} |
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,2 @@ | ||
.prettierignore | ||
.gitignore |
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 @@ | ||
dist |
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 @@ | ||
# @amplication/plugin-dotnet-auth-core-identity | ||
|
||
[![NPM Downloads](https://img.shields.io/npm/dt/@amplication/plugin-dotnet-auth-core-identity)](https://www.npmjs.com/package/@amplication/plugin-dotnet-auth-core-identity) | ||
|
||
Use authentication in the service generated by Amplication. | ||
|
||
## Purpose | ||
|
||
This plugin adds the core required code to use authentication in the service generated by Amplication. | ||
It updates the following parts: | ||
|
||
- Adds the Authorize attribute in the base controllers by the roles configurations | ||
- Add the required configurations for the auth entity | ||
- Add the required configurations in the program.cs file |
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,37 @@ | ||
{ | ||
"name": "@amplication/plugin-dotnet-auth-core-identity", | ||
"version": "0.0.1", | ||
"description": "Add Authentication and Authorization to your .NET Services", | ||
"main": "dist/index.js", | ||
"nx": {}, | ||
"scripts": { | ||
"dev": "webpack --watch", | ||
"build": "webpack", | ||
"prebuild": "rimraf dist" | ||
}, | ||
"author": "Mor Hagbi", | ||
"license": "Apache-2.0", | ||
"devDependencies": { | ||
"@amplication/code-gen-types": "2.0.33-beta.23", | ||
"@amplication/code-gen-utils": "^0.0.9", | ||
"@amplication/csharp-ast": "0.0.3-beta.2", | ||
"@babel/parser": "^7.18.11", | ||
"@babel/types": "^7.18.10", | ||
"@types/lodash": "^4.14.182", | ||
"@types/normalize-path": "^3.0.0", | ||
"@typescript-eslint/eslint-plugin": "^5.33.0", | ||
"@typescript-eslint/parser": "^5.33.0", | ||
"copy-webpack-plugin": "^12.0.2", | ||
"eslint": "^8.21.0", | ||
"jest-mock-extended": "^2.0.7", | ||
"lodash": "^4.17.21", | ||
"pascal-case": "^3.1.2", | ||
"prettier": "^2.6.2", | ||
"rimraf": "^4.4.1", | ||
"ts-loader": "^9.4.2", | ||
"typescript": "^4.9.3", | ||
"webpack": "^5.75.0", | ||
"webpack-cli": "^5.0.1", | ||
"@types/pluralize": "^0.0.29" | ||
} | ||
} |
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,6 @@ | ||
{ | ||
"targets": { | ||
"lint": {}, | ||
"npm:publish": {} | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
plugins/dotnet-auth-core-identity/src/core/create-app-services.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,24 @@ | ||
import { CodeBlock } from "@amplication/csharp-ast"; | ||
|
||
export function createAppServices(builderServicesBlocks: CodeBlock[]): void { | ||
builderServicesBlocks.push( | ||
new CodeBlock({ | ||
code: `using (var scope = app.Services.CreateScope()) | ||
{ | ||
var services = scope.ServiceProvider; | ||
await RolesManager.SyncRoles(services, app.Configuration); | ||
}`, | ||
}) | ||
); | ||
|
||
builderServicesBlocks.push( | ||
new CodeBlock({ | ||
code: ` | ||
using (var scope = app.Services.CreateScope()) | ||
{ | ||
var services = scope.ServiceProvider; | ||
await SeedDevelopmentData.SeedDevUser(services, app.Configuration); | ||
}`, | ||
}) | ||
); | ||
} |
33 changes: 33 additions & 0 deletions
33
plugins/dotnet-auth-core-identity/src/core/create-builders-services.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,33 @@ | ||
import { CodeBlock, CsharpSupport } from "@amplication/csharp-ast"; | ||
|
||
export function createBuildersServices( | ||
resourceName: string, | ||
builderServicesBlocks: CodeBlock[] | ||
): void { | ||
builderServicesBlocks.push( | ||
new CodeBlock({ | ||
code: `builder.Services.AddApiAuthentication();`, | ||
}) | ||
); | ||
|
||
const swaggerBuilderIndex = builderServicesBlocks.findIndex((b) => | ||
b.toString().includes("AddSwaggerGen") | ||
); | ||
|
||
if (swaggerBuilderIndex === -1) return; | ||
|
||
builderServicesBlocks[swaggerBuilderIndex] = new CodeBlock({ | ||
references: [ | ||
CsharpSupport.classReference({ | ||
namespace: `${resourceName}.APIs`, | ||
name: resourceName, | ||
}), | ||
], | ||
code: `builder.Services.AddSwaggerGen(options => | ||
{ | ||
options.UseOpenApiAuthentication(); | ||
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; | ||
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); | ||
});`, | ||
}); | ||
} |
40 changes: 40 additions & 0 deletions
40
plugins/dotnet-auth-core-identity/src/core/create-method-authorize-annotation.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,40 @@ | ||
import { Entity, EnumEntityAction } from "@amplication/code-gen-types"; | ||
import { CsharpSupport, Method } from "@amplication/csharp-ast/src"; | ||
import { getEntityRoleMap } from "./get-entity-roles-map"; | ||
|
||
export function createMethodAuthorizeAnnotation( | ||
method: Method, | ||
roles: string | ||
): void { | ||
roles && | ||
method.annotations?.push( | ||
CsharpSupport.annotation({ | ||
reference: CsharpSupport.classReference({ | ||
name: "Authorize", | ||
namespace: "Microsoft.AspNetCore.Authorization", | ||
}), | ||
argument: `Roles = "${roles}"`, | ||
}) | ||
); | ||
} | ||
|
||
export function createRelatedMethodAuthorizeAnnotation( | ||
entity: Entity, | ||
entities: Entity[], | ||
fieldPermanentId: string, | ||
method: Method, | ||
methodType: EnumEntityAction, | ||
roles?: string | ||
): void { | ||
const field = entity.fields.find( | ||
(field) => field.permanentId === fieldPermanentId | ||
); | ||
|
||
const relatedEntity = entities.find( | ||
(entity) => entity.id === field?.properties?.relatedEntityId | ||
); | ||
if (relatedEntity) { | ||
const rolesMapping = getEntityRoleMap(relatedEntity, roles); | ||
createMethodAuthorizeAnnotation(method, rolesMapping[methodType].roles); | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
plugins/dotnet-auth-core-identity/src/core/create-seed-development-data.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,99 @@ | ||
import { Entity, EnumDataType } from "@amplication/code-gen-types"; | ||
import { CodeBlock, CsharpSupport } from "@amplication/csharp-ast"; | ||
import { camelCase } from "lodash"; | ||
import { pascalCase } from "pascal-case"; | ||
|
||
export function CreateSeedDevelopmentDataBody( | ||
resourceName: string, | ||
authEntity: Entity, | ||
entities: Entity[] | ||
): CodeBlock { | ||
const { name, pluralName } = authEntity; | ||
const entityNameToCamelCase = camelCase(name); | ||
const entityNamePluralize = pascalCase(pluralName); | ||
const entityFirstLetter = entityNameToCamelCase.slice(0, 1); | ||
return new CodeBlock({ | ||
references: [ | ||
CsharpSupport.classReference({ | ||
name: "Identity", | ||
namespace: "Microsoft.AspNetCore.Identity", | ||
}), | ||
CsharpSupport.classReference({ | ||
name: "EntityFrameworkCore", | ||
namespace: "Microsoft.AspNetCore.Identity.EntityFrameworkCore", | ||
}), | ||
CsharpSupport.classReference({ | ||
name: resourceName, | ||
namespace: `${resourceName}.Infrastructure`, | ||
}), | ||
], | ||
code: `var context = serviceProvider.GetRequiredService<${resourceName}DbContext>(); | ||
var amplicationRoles = configuration | ||
.GetSection("AmplicationRoles") | ||
.AsEnumerable() | ||
.Where(x => x.Value != null) | ||
.Select(x => x.Value.ToString()) | ||
.ToArray(); | ||
${authEntityDto(authEntity, entities)} | ||
if (!context.${entityNamePluralize}.Any(${entityFirstLetter} => ${entityFirstLetter}.UserName == ${entityNameToCamelCase}.UserName)) | ||
{ | ||
var password = new PasswordHasher<${name}>(); | ||
var hashed = password.HashPassword(${entityNameToCamelCase}, "password"); | ||
${entityNameToCamelCase}.PasswordHash = hashed; | ||
var userStore = new UserStore<${name}>(context); | ||
await userStore.CreateAsync(${entityNameToCamelCase}); | ||
var _roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>(); | ||
foreach (var role in amplicationRoles) | ||
{ | ||
await userStore.AddToRoleAsync(${entityNameToCamelCase}, _roleManager.NormalizeKey(role)); | ||
} | ||
} | ||
await context.SaveChangesAsync();`, | ||
}); | ||
} | ||
|
||
const authEntityDto = (authEntity: Entity, entities: Entity[]): string => { | ||
const { fields } = authEntity; | ||
let codeBlock = ""; | ||
|
||
for (const field of fields) { | ||
const fieldNamePascalCase = pascalCase(field.name); | ||
|
||
if (field.dataType == EnumDataType.Lookup) { | ||
const relatedEntity = entities.find( | ||
(entity) => entity.id === field.properties?.relatedEntityId | ||
); | ||
|
||
const relatedEntityFieldName = pascalCase(field.name); | ||
|
||
if (field.properties?.allowMultipleSelection) { | ||
// the "many" side of the relation | ||
codeBlock = | ||
codeBlock + | ||
`${fieldNamePascalCase} = model.${relatedEntityFieldName}.Select(x => new ${relatedEntity?.name}IdDto {Id = x.Id}).ToList(),\n`; | ||
} else { | ||
if (field.properties.fkHolderName === authEntity.name) { | ||
break; | ||
} else { | ||
// the "one" side of the relation | ||
codeBlock = | ||
codeBlock + | ||
`${fieldNamePascalCase} = new ${relatedEntity?.name}IdDto { Id = model.${fieldNamePascalCase}Id},\n`; | ||
} | ||
} | ||
} else { | ||
codeBlock = | ||
codeBlock + `${fieldNamePascalCase} = model.${fieldNamePascalCase},\n`; | ||
} | ||
} | ||
|
||
return `var ${camelCase(authEntity.name)} = new ${authEntity.name} | ||
{ | ||
${codeBlock} | ||
};`; | ||
}; |
29 changes: 29 additions & 0 deletions
29
plugins/dotnet-auth-core-identity/src/core/create-static-file-map.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,29 @@ | ||
import { ClassReference, CodeBlock } from "@amplication/csharp-ast"; | ||
import { FileMap, IFile, dotnetTypes } from "@amplication/code-gen-types"; | ||
import { readFile } from "fs/promises"; | ||
import { pascalCase } from "pascal-case"; | ||
|
||
export async function createStaticFileFileMap( | ||
destPath: string, | ||
filePath: string, | ||
context: dotnetTypes.DsgContext, | ||
classReference?: ClassReference | ||
): Promise<FileMap<CodeBlock>> { | ||
const fileMap = new FileMap<CodeBlock>(context.logger); | ||
|
||
if (!context.resourceInfo) return fileMap; | ||
const resourceName = pascalCase(context.resourceInfo.name); | ||
let fileContent = await readFile(filePath, "utf-8"); | ||
fileContent = fileContent.replace("ServiceName", resourceName); | ||
|
||
const file: IFile<CodeBlock> = { | ||
path: destPath, | ||
code: new CodeBlock({ | ||
code: fileContent, | ||
references: classReference && [classReference], | ||
}), | ||
}; | ||
|
||
fileMap.set(file); | ||
return fileMap; | ||
} |
57 changes: 57 additions & 0 deletions
57
plugins/dotnet-auth-core-identity/src/core/get-entity-roles-map.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,57 @@ | ||
import { | ||
Entity, | ||
EnumEntityAction, | ||
EnumEntityPermissionType, | ||
} from "@amplication/code-gen-types"; | ||
|
||
export function getRelatedFieldRolesMap( | ||
entity: Entity, | ||
entities: Entity[], | ||
fieldPermanentId: string, | ||
roleNames?: string | ||
): Record< | ||
EnumEntityAction, | ||
{ | ||
roles: string; | ||
} | ||
> | null { | ||
const field = entity.fields.find( | ||
(field) => field.permanentId === fieldPermanentId | ||
); | ||
|
||
const relatedEntity = entities.find( | ||
(entity) => entity.id === field?.properties?.relatedEntityId | ||
); | ||
if (relatedEntity) { | ||
return getEntityRoleMap(relatedEntity, roleNames); | ||
} | ||
return null; | ||
} | ||
|
||
export function getEntityRoleMap( | ||
entity: Entity, | ||
roleNames?: string | ||
): Record< | ||
EnumEntityAction, | ||
{ | ||
roles: string; | ||
} | ||
> { | ||
return Object.fromEntries( | ||
entity.permissions.map((permission) => { | ||
return [ | ||
permission.action, | ||
{ | ||
roles: | ||
permission.type === EnumEntityPermissionType.AllRoles | ||
? roleNames | ||
: permission.type === EnumEntityPermissionType.Granular | ||
? permission.permissionRoles | ||
.map((role) => role.resourceRole.name) | ||
.join(",") | ||
: null, | ||
}, | ||
]; | ||
}) | ||
) as unknown as Record<EnumEntityAction, { roles: string }>; | ||
} |
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,12 @@ | ||
export { | ||
createMethodAuthorizeAnnotation, | ||
createRelatedMethodAuthorizeAnnotation, | ||
} from "./create-method-authorize-annotation"; | ||
export { | ||
getEntityRoleMap, | ||
getRelatedFieldRolesMap, | ||
} from "./get-entity-roles-map"; | ||
export { createStaticFileFileMap } from "./create-static-file-map"; | ||
export { createBuildersServices } from "./create-builders-services"; | ||
export { createAppServices } from "./create-app-services"; | ||
export { CreateSeedDevelopmentDataBody } from "./create-seed-development-data"; |
Oops, something went wrong.