Skip to content
Prev Previous commit
Next Next commit
Handled Enable / Disable of a control from RBAC Control panel
  • Loading branch information
foyzulkarim committed Nov 11, 2020
commit e96bb0aa71448e84ef42565368bf5e550105b32b
1 change: 1 addition & 0 deletions client/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<title>React App</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="styles.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
Expand Down
4 changes: 4 additions & 0 deletions client/public/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.disable-control {
pointer-events: none;
color: gray;
}
23 changes: 13 additions & 10 deletions client/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { } from 'react';
import './App.css';
import { BrowserRouter as Router, Switch, Route, Link, Redirect, useHistory } from "react-router-dom";
import { useSelector, useDispatch } from 'react-redux';
import { PostCreate, Posts, Home } from "./components/Posts";
import { PostCreate, Posts, Home, PostDetail, PostEdit, PostDelete } from "./components/Posts";
import { Login } from "./components/Login";
import { Register } from "./components/Register";
import { Constants } from "./constants";
Expand Down Expand Up @@ -73,14 +73,17 @@ const App = () => {
//console.log('userContext', userContext);

let links = [
{ name: 'link-posts', url: '/posts', text: 'Posts', component: Posts },
{ name: 'link-post-create', url: '/post-create', text: 'Create post', component: PostCreate },
{ name: 'link-permission-create', url: '/permission-create', text: 'Create permission', component: PermissionCreate },
{ name: 'link-permission-list', url: '/permission-list', text: 'List permissions', component: PermissionList },
{ name: 'link-role-create', url: '/role-create', text: 'Create role', component: RoleCreate },
{ name: 'link-role-list', url: '/role-list', text: 'List role', component: RoleList },
{ name: 'link-resource-create', url: '/resource-create', text: 'Create resource', component: ResourceCreate },
{ name: 'link-resource-list', url: '/resource-list', text: 'List resource', component: ResourceList },
{ name: 'link-posts', url: '/posts', text: 'Posts', component: Posts, isRootMenu: true },
{ name: 'link-post-create', url: '/post-create', text: 'Create post', component: PostCreate, isRootMenu: true },
{ name: 'link-post-detail', url: '/post-detail/:id', text: 'Detail post', component: PostDetail },
{ name: 'link-post-edit', url: '/post-edit/:id', text: 'Edit post', component: PostEdit },
{ name: 'link-post-delete', url: '/post-delete/:id', text: 'Delete post', component: PostDelete },
{ name: 'link-permission-create', url: '/permission-create', text: 'Create permission', component: PermissionCreate, isRootMenu: true },
{ name: 'link-permission-list', url: '/permission-list', text: 'List permissions', component: PermissionList, isRootMenu: true },
{ name: 'link-role-create', url: '/role-create', text: 'Create role', component: RoleCreate, isRootMenu: true },
{ name: 'link-role-list', url: '/role-list', text: 'List role', component: RoleList, isRootMenu: true },
{ name: 'link-resource-create', url: '/resource-create', text: 'Create resource', component: ResourceCreate, isRootMenu: true },
{ name: 'link-resource-list', url: '/resource-list', text: 'List resource', component: ResourceList, isRootMenu: true },
];

// let routes = [
Expand All @@ -100,7 +103,7 @@ const App = () => {
<>
{
links.map((link, index) => {
return checkPermission(link.name, userContext) && <Link key={index} to={link.url} className="list-group-item list-group-item-action bg-light">{link.text}</Link>
return checkPermission(link.name, userContext) && link.isRootMenu && <Link key={index} to={link.url} className="list-group-item list-group-item-action bg-light">{link.text}</Link>
})
}
</>
Expand Down
7 changes: 7 additions & 0 deletions client/src/components/Permission.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export const PermissionCreate = () => {
<label><Field type="checkbox" name="isAllowed" /></label>
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="isAllowed" component="div" />
</div>
<div className="form-group row">
<label htmlFor="isDisabled" className="col-sm-2 col-form-label">Is disabled</label>
<label><Field type="checkbox" name="isDisabled" /></label>
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="isDisabled" component="div" />
</div>
<div className="form-group row">
<label htmlFor="name" className="col-sm-2 col-form-label"></label>
<button type="submit" disabled={isSubmitting}>Submit</button>
Expand Down Expand Up @@ -118,6 +123,7 @@ export const PermissionList = () => {
<th>Resource</th>
<th>Role</th>
<th>Is allowed</th>
<th>Is disabled</th>
</tr>
</thead>
<tbody>
Expand All @@ -129,6 +135,7 @@ export const PermissionList = () => {
<td>{resource.resourceName}</td>
<td>{resource.roleName}</td>
<td>{resource.isAllowed.toString()}</td>
<td>{resource.isDisabled.toString()}</td>
</tr>
)
})
Expand Down
16 changes: 11 additions & 5 deletions client/src/components/Posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { Switch, Route, Link, useRouteMatch, useParams, useHistory } from "react-router-dom";
import { useForm } from 'react-hook-form';
import { useSelector, useDispatch } from 'react-redux';
import { checkPermission } from "../utils/permissionManager.js";
import { checkPermission, checkIsDisabled } from "../utils/permissionManager.js";

export const SecuedLink = ({ resource, text, url }) => {

Expand All @@ -13,8 +13,9 @@ export const SecuedLink = ({ resource, text, url }) => {
console.log('SecuedLink ', resource, text, url);

const isAllowed = checkPermission(resource, userContext);
const isDisabled = checkIsDisabled(resource, userContext);

return (isAllowed && <Link to={() => url}>{text}</Link>)
return (isAllowed && <Link className={isDisabled ? "disable-control" : ""} to={() => url}>{text}</Link>)
}

export const Home = () => {
Expand Down Expand Up @@ -199,9 +200,8 @@ export const PostSummary = (post) => {
<h3>{post.title}</h3>
<img src={post.imgUrl} style={{ height: "50px", width: "50px" }} alt="post img" className="pull-left thumb margin10 img-thumbnail"></img>
<p>{post.emText}</p>
<Link to={() => `/post-detail/${post.id}`}>Detail</Link> &nbsp;
<Link to={() => `/post-edit/${post.id}`}>Edit</Link> &nbsp;
{/* <Link to={() => `/post-delete/${post.id}` style={{ pointerEvents: 'none' }}}>Delete</Link> &nbsp; */}
<SecuedLink resource='link-post-edit' url={`/post-detail/${post.id}`} text='Detail'></SecuedLink>&nbsp;
<SecuedLink resource='link-post-edit' url={`/post-edit/${post.id}`} text='Edit'></SecuedLink>&nbsp;
<SecuedLink resource='link-post-delete' url={`/post-delete/${post.id}`} text='Delete'></SecuedLink>
</div>
)
Expand Down Expand Up @@ -313,6 +313,7 @@ export const PostDetail = () => {

let fetchData = (id) => {
dispatch({ type: "FETCH_POST_DETAIL", payload: id });
dispatch({ type: "FETCH_COMMENTS", payload: id, });
}

useEffect(() => {
Expand All @@ -323,6 +324,10 @@ export const PostDetail = () => {
return state.posts.selectedPost;
});

const comments = useSelector(state => {
return state.posts.selectedComments;
});

let match = useRouteMatch();

return (
Expand All @@ -331,6 +336,7 @@ export const PostDetail = () => {
<h2>{post.title}</h2>
<img src={post.imgUrl} style={{ height: "50px", width: "50px" }} alt="post img" className="pull-left thumb margin10 img-thumbnail"></img>
<article><p>{post.articleText}</p></article>
<p>Total comments {comments.length}</p>
<a className="btn btn-blog pull-right marginBottom10" href={post.readMoreUrl}>READ MORE</a>
</div>
<div>
Expand Down
2 changes: 1 addition & 1 deletion client/src/sagas/api.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios from 'axios';

const BaseUrl = 'http://localhost:5005/api';
const BaseUrl = 'http://localhost:5002/api';
const AuthUrl = 'http://localhost:5000';


Expand Down
15 changes: 14 additions & 1 deletion client/src/utils/permissionManager.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
const getElement = (resource, userContext) => {
return userContext.resources
&& userContext.resources.length > 0
&& userContext.resources.find(element => element.name === resource);
}

export const checkPermission = (resource, userContext) => {
console.log('checkPermission', resource, userContext.resources);
return userContext.isAuthenticated && userContext.resources.includes(resource);
const element = getElement(resource, userContext);
return userContext.isAuthenticated && element != null && element.isAllowed;
}

export const checkIsDisabled = (resource, userContext) => {
console.log('isDisabled', resource, userContext.resources);
const element = getElement(resource, userContext);
return userContext.isAuthenticated && element != null && element.isDisabled;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<UserSecretsId>6b6ddae3-064a-423b-b950-8a4e0b0cb4cf</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public ApplicationPermissionViewModel(ApplicationPermission permission)
this.RoleName = permission.Role.Name;
this.ResourceName = permission.Resource.Name;
this.IsAllowed = permission.IsAllowed.ToString();
this.IsDisabled = permission.IsDisabled.ToString();
}

public string Id { get; set; }
Expand All @@ -29,5 +30,7 @@ public ApplicationPermissionViewModel(ApplicationPermission permission)
public string ResourceName { get; set; }

public string IsAllowed { get; set; }

public string IsDisabled { get; set; }
}
}
18 changes: 10 additions & 8 deletions server/AuthWebApplication/AuthWebApplication/Utilities/Tokens.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,28 @@ public static async Task<object> GenerateJwt(ClaimsIdentity identity, IJwtFactor
string id = identity.Claims.Single(c => c.Type == "id").Value;
var name = user.FirstName + " " + user.LastName;
string token = await jwtFactory.GenerateEncodedToken(user.UserName, identity);
List<string> resources = new List<string>();
if (roles!=null)

List<object> resources = new List<object>();
if (roles != null)
{
var roleIds = roles.Select(x => (string)x.Id).ToList();
var permissions = db.Permissions.Include(x => x.Resource).Where(x => roleIds.Contains(x.RoleId) && x.IsAllowed).Select(x => new { name = x.Resource.Name, isAllowed = x.IsAllowed, isDisabled = x.IsDisabled })
resources = db.Permissions.Include(x => x.Resource).Where(x => roleIds.Contains(x.RoleId) && x.IsAllowed).Select(x => (dynamic) new { name = x.Resource.Name, isAllowed = x.IsAllowed, isDisabled = x.IsDisabled })
.ToList();
resources = permissions.Select(x => x.name).ToList();
//resources = permissions.Select(x => x.name).ToList();
}
var response = new

dynamic response = new
{
id = id,
name = name,
userName = user.UserName,
resources = resources,
roles = roles,
access_token = token,
expires_in = (int)jwtOptions.ValidFor.TotalSeconds,
token_type = "bearer"
token_type = "bearer",
resources = resources
};

return response;
}
}
Expand Down
31 changes: 31 additions & 0 deletions server/Server/Server.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30621.155
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AuthWebApplication", "..\AuthWebApplication\AuthWebApplication\AuthWebApplication.csproj", "{F802ACFD-5C2F-42A3-A716-E0B89F9B5E11}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApplication2", "..\WebApplication2\WebApplication2\WebApplication2.csproj", "{601443BD-D8F7-4982-9AB7-E3D99D308477}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F802ACFD-5C2F-42A3-A716-E0B89F9B5E11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F802ACFD-5C2F-42A3-A716-E0B89F9B5E11}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F802ACFD-5C2F-42A3-A716-E0B89F9B5E11}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F802ACFD-5C2F-42A3-A716-E0B89F9B5E11}.Release|Any CPU.Build.0 = Release|Any CPU
{601443BD-D8F7-4982-9AB7-E3D99D308477}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{601443BD-D8F7-4982-9AB7-E3D99D308477}.Debug|Any CPU.Build.0 = Debug|Any CPU
{601443BD-D8F7-4982-9AB7-E3D99D308477}.Release|Any CPU.ActiveCfg = Release|Any CPU
{601443BD-D8F7-4982-9AB7-E3D99D308477}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {807813D6-90B8-41B5-9DBB-C4CF8B46DA98}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
Expand All @@ -8,6 +7,7 @@
"sslPort": 0
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
Expand All @@ -21,10 +21,10 @@
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5005",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"applicationUrl": "http://localhost:5002;https://localhost:5003"
},
"Watch": {
"commandName": "Watch",
Expand Down
16 changes: 12 additions & 4 deletions server/WebApplication2/WebApplication2/WebApplication2.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.9" />
<PackageReference Include="Google.Protobuf" Version="3.13.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.33.1" />
<PackageReference Include="Grpc.Tools" Version="2.33.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.10" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.8.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.4" />
<PackageReference Include="MongoDB.Driver" Version="2.11.4" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.7.0" />
</ItemGroup>

<ItemGroup>
<Folder Include="Protos\" />
</ItemGroup>


Expand Down