SuperFinance Digital Banking SaaS: Step-1 – Setup Project, Models and Permissions

Introduction

As part of this tutorial, we’re building SuperFinance – a digital banking SaaS using ASPSecurityKit. In the previous step, we talked about the SuperFinance web application’s business features, security mechanisms, and the type of users. In this step, we’ll setup the initial project with ASPSecurityKit, build the data models, define permissions and learn how to assign them to different users. We’ll also highlight the best practices and recommendations behind the design choices you see so you can grasp the underlying concepts easily.

Prerequisites

This tutorial requires:

Setup the Initial Project

Follow the getting started walkthrough to setup the new SuperFinance project with the following options:

Data Models

Creating the data models as a first thing forces us to think about the system and its interactions holistically, which will then aid in rapid development of various components of the system.

In SuperFinance, we’re going to use EntityFramework Core with code first approach for data access and representation.

Security Models

The Premium source package has already added necessary data models for user, permission, and suspension management as source code to the project. This is so that we can modify them as needed.

These entities are:

  • User
  • UserMultiFactor
  • UserSession
  • UserPermitGroup
  • UserPermit
  • Permission
  • ImpliedPermission
  • SuspendedEntity
  • SuspensionExclusionRule
  • FirewallRule

To keep a physical difference between three major kinds of users within SuperFinance, we choose to add UserType property to the DbUser model as follows:

public enum UserType
{
	Admin,
	Staff,
	Customer
}

public class DbUser
{
    ...

    [Required]
    public UserType UserType { get; set; }
}

Domain Models

we need following data models to implement SF’s digital banking features. These are self-explanatory.

Bank
public class DbBank
{
	[Key]
	public Guid Id { get; set; }

	[MaxLength(60)]
	[Required]
	public string Name { get; set; }

	public string Address { get; set; }

	public bool FirewallEnabled { get; set; }

	public bool EnforceMFA { get; set; }

	public bool SkipMFAInsideNetwork { get; set; }

	public int? PasswordExpiresInDays { get; set; }

	[ForeignKey("User")]
	public Guid OwningUserId { get; set; }
	public DbUser OwningUser { get; set; }

	public IList<DbBranch> Branches { get; set; }

	public IList<DbAccountType> AccountTypes { get; set; }
}
Branch
public class DbBranch
{
	[Key]
	public Guid Id { get; set; }

	[MaxLength(60)]
	[Required]
	public string Name { get; set; }

	[MaxLength(16)]
	[Required]
	public string Code { get; set; }

	public string Address { get; set; }

	[ForeignKey("Bank")]
	public Guid BankId { get; set; }
	public DbBank Bank { get; set; }
}
AccountType
public class DbAccountType
{
	[Key]
	public Guid Id { get; set; }

	[MaxLength(30)]
	[Required]
	public string Name { get; set; }

	[Required]
	public double InterestRate { get; set; }

	[Required]
	public AccountKind Kind { get; set; }

	[ForeignKey("Bank")]
	public Guid BankId { get; set; }
	public DbBank Bank { get; set; }
}
Account
public class DbAccount
{
	[Key]
	public Guid Id { get; set; }

	[MaxLength(30)]
	[Required]
	public string Number { get; set; }

	[MaxLength(24)]
	[Required]
	public string IdentityNumber { get; set; }

	public AccountStatus Status { get; set; }

	public string Reason { get; set; }

	[ForeignKey("AccountType")]
	public Guid AccountTypeId { get; set; }
	public DbAccountType AccountType { get; set; }

	[ForeignKey("Branch")]
	public Guid BranchId { get; set; }
	public DbBranch Branch { get; set; }

	[ForeignKey("User")]
	public Guid OwningUserId { get; set; }
	public DbUser OwningUser { get; set; }

	public DateTime CreatedDate { get; set; }

	public IList<DbAccountNominee> Nominees { get; set; }
}
AccountNominee
public class DbAccountNominee
{
	[Key]
	public Guid Id { get; set; }

	[ForeignKey("Account")]
	public Guid AccountId { get; set; }
	public DbAccount Account { get; set; }

	[ForeignKey("User")]
	public Guid NomineeUserId { get; set; }
	public DbUser NomineeUser { get; set; }
}
Transaction
public class DbTransaction
{
	[Key]
	public Guid Id { get; set; }

	public DateTime Date { get; set; }

	public double Amount { get; set; }

	public TransactionType TransactionType { get; set; }

	public string Remarks { get; set; }

	[ForeignKey("Account")]
	public Guid AccountId { get; set; }
	public DbAccount Account { get; set; }
}
Transfer
public class DbTransfer
{
	[Key]
	public Guid Id { get; set; }

	[ForeignKey("Transaction")]
	public Guid DebitTransactionId { get; set; }
	public DbTransaction DebitTransaction { get; set; }

	[ForeignKey("Transaction")]
	public Guid CreditTransactionId { get; set; }
	public DbTransaction CreditTransaction { get; set; }

	public DateTime CreatedDate { get; set; }
}
UserInvitation
public class DbUserInvitation
{
	[Key]
	public Guid Id { get; set; }

	[MaxLength(100)]
	[Required]
	public string EmailAddress { get; set; }

	public DateTime Date { get; set; }

	[ForeignKey("Account")]
	public Guid AccountId { get; set; }
	public DbAccount Account { get; set; }

	[ForeignKey("User")]
	public Guid? UserId { get; set; }
	public DbUser User { get; set; }
}

View Raw File

Best Practices

  • If the relationship between a user and an entity is that of ownership, you should define user property physically in that entity. As done in case of OwningUserId in both DbBank and DbAccount.
    • However, as we’ll see below, actions even on such owned entities are also authorized via permits. For that, we assign a user the permit on the entity when it’s created.
  • On the other hand, when the relationship is temporal, for instance, because of one’s job in the organization, it’s better to just stick to assigning the permit on the entity to the user. As we’ll do with a user such as branch manager and branch staff. These users do not own their branches; they are merely serving their current roles in the bank. This way it’s easy to grant or revoke their permit effectively deleting the relationship with the entity, and also makes it easy to add multiple such relationships for the same entity.
  • However, there’s still one case the above rules aren’t exactly honored; and that is, the DbAccountNominee entity exists at the moment only to establish a relationship between a nominee user and an account. This can be achieved with just a permit – assigning Nominee permission on the AccountId – but the model is created intentionally because there could likely be more information we need to capture as part of nominee creation, such as the percentage of the amount a nominee is allocated as a beneficiary when multiple nominees are specified for an account.

Permissions

We strongly recommend that you read both the design and how-to ADA guides to understand the concept and power of ASPSecurityKit’s activity-based, data-aware authorization (ADA).

User Roles

Based on the unique concept of implied permissions, we can implement the roles for different kinds of users in SuperFinance as permissions. This is the recommended way because:

  • No extra effort is needed to develop/maintain role-related tables and UI.
  • In case you need to assign one permission to a user, you don’t have to create a new role, role-permission mapping, and such stuff; just assign both roles and direct permission using a unified construct of UserPermit.

These are the permissions representing user roles in SuperFinance:

Permission EntityType PermissionKind
BankOwner Bank Instance
BranchManager Branch Instance
BranchStaff Branch Instance
AccountHolder Account Instance
AccountNominee Account Instance
Customer User General

Notes:

  • EntityType represents the type of entity (bank/branch/etc) that this permission will be assigned against (EntityId)
  • PermissionKind represents whether the permission is a general or instance permission. A general permission indicates that there won’t be any EntityId required to grant this permission.

Activity Permissions

We now define permissions related to MVC actions (the activities) and associate them with user roles. Following the convention of activity definition, the PermissionCode for action is built by combining the controller and action names; you can override these conventions if needed.

Note:

  • For the sake of elaboration, we’re defining these permissions explicitly in the sample; you can automate this process and add/update permissions (on startup or as a migration seed data step) based on the same conventions.
  • Although the activity authorization approach recommends that you define a unique PermissionCode for every action, in the real-world sticking to the recommendation for all actions can lead to the problem of permissions bloat. In such cases, we recommend to be practical and define permissions at a bit higher-level for entities that do not need separate permission for every action on that entity.
    For example, the CRUD (create/read/update/delete) actions related to Branch and AccountType entities are only meant for Bank Owner and even if they’re delegated, they’ll be delegated in as a group; simply put, we do not see a need to assign these actions independently. So, ManageBranch and ManageAccountType permissions are suffice. in their respective controllers, we’ll override the conventions so ASK knows and authorize related actions using these permissions.
  • Some actions are anonymously available and hence they do not need permissions. For example, bank registration, customer sign up, login, etc.
Permission EntityType PermissionKind
ManageBank Bank Instance
ManageBranch Bank Instance
ManageAccountType Bank Instance
IndexCustomerAccount Account General
CreateCustomerAccount Account Instance
ChangeStatus Account Instance
CreateDeposit Account Instance
CreateWithdrawal Account Instance
OpenAccount Account General
IndexAccount Account General
CreateTransfer Account Instance
AddNominee Account Instance
DeleteNominee Account Instance
IndexNominee Account Instance
IndexAccountDetails Account Instance

Implied Permissions

Next, we can associate the above activity permissions with user roles via implied permissions construct:

Permission ImpliedPermission
AccountHolder AddNominee
AccountHolder CreateTransfer
AccountHolder DeleteNominee
AccountHolder IndexAccount
AccountHolder IndexAccountDetails
AccountHolder IndexNominee
AccountHolder OpenAccount
AccountNominee IndexAccount
AccountNominee IndexAccountDetails
BankOwner BranchManager
BankOwner ManageAccountType
BankOwner ManageBank
BankOwner ManageBranch
BankOwner ManageFirewall
BranchManager BranchStaff
BranchStaff ChangeStatus
BranchStaff CreateCustomerAccount
BranchStaff CreateDeposit
BranchStaff CreateWithdrawal
BranchStaff IndexAccountDetails
BranchStaff IndexCustomerAccount
BranchStaff IndexNominee
Customer OpenAccount

Migrations

We can now create an EntityFramework Core migration for the domain models and also include insert scripts for the permissions and implied permissions discussed above.

View Migration
using System;
using Microsoft.EntityFrameworkCore.Migrations;

namespace ASKSource.Migrations
{
    public partial class BankModels : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AddColumn<int>(
                name: "UserType",
                table: "User",
                nullable: false,
                defaultValue: 0);

            migrationBuilder.CreateTable(
                name: "Bank",
                columns: table => new
                {
                    Id = table.Column<Guid>(nullable: false),
                    Name = table.Column<string>(maxLength: 60, nullable: false),
                    Address = table.Column<string>(nullable: true),
                    FirewallEnabled = table.Column<bool>(nullable: false),
                    EnforceMFA = table.Column<bool>(nullable: false),
                    SkipMFAInsideNetwork = table.Column<bool>(nullable: false),
                    PasswordExpiresInDays = table.Column<int>(nullable: true),
                    OwningUserId = table.Column<Guid>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Bank", x => x.Id);
                    table.ForeignKey(
                        name: "FK_Bank_User_OwningUserId",
                        column: x => x.OwningUserId,
                        principalTable: "User",
                        principalColumn: "Id");
                });

            migrationBuilder.CreateTable(
                name: "AccountType",
                columns: table => new
                {
                    Id = table.Column<Guid>(nullable: false),
                    Name = table.Column<string>(maxLength: 30, nullable: false),
                    InterestRate = table.Column<double>(nullable: false),
                    Kind = table.Column<int>(nullable: false),
                    BankId = table.Column<Guid>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AccountType", x => x.Id);
                    table.ForeignKey(
                        name: "FK_AccountType_Bank_BankId",
                        column: x => x.BankId,
                        principalTable: "Bank",
                        principalColumn: "Id");
                });

            migrationBuilder.CreateTable(
                name: "Branch",
                columns: table => new
                {
                    Id = table.Column<Guid>(nullable: false),
                    Name = table.Column<string>(maxLength: 60, nullable: false),
                    Code = table.Column<string>(maxLength: 16, nullable: false),
                    Address = table.Column<string>(nullable: true),
                    BankId = table.Column<Guid>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Branch", x => x.Id);
                    table.ForeignKey(
                        name: "FK_Branch_Bank_BankId",
                        column: x => x.BankId,
                        principalTable: "Bank",
                        principalColumn: "Id");
                });

            migrationBuilder.CreateTable(
                name: "Account",
                columns: table => new
                {
                    Id = table.Column<Guid>(nullable: false),
                    Number = table.Column<string>(maxLength: 30, nullable: false),
                    IdentityNumber = table.Column<string>(maxLength: 24, nullable: false),
                    Status = table.Column<int>(nullable: false),
                    Reason = table.Column<string>(nullable: true),
                    AccountTypeId = table.Column<Guid>(nullable: false),
                    BranchId = table.Column<Guid>(nullable: false),
                    OwningUserId = table.Column<Guid>(nullable: false),
                    CreatedDate = table.Column<DateTime>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Account", x => x.Id);
                    table.ForeignKey(
                        name: "FK_Account_AccountType_AccountTypeId",
                        column: x => x.AccountTypeId,
                        principalTable: "AccountType",
                        principalColumn: "Id");
                    table.ForeignKey(
                        name: "FK_Account_Branch_BranchId",
                        column: x => x.BranchId,
                        principalTable: "Branch",
                        principalColumn: "Id");
                    table.ForeignKey(
                        name: "FK_Account_User_OwningUserId",
                        column: x => x.OwningUserId,
                        principalTable: "User",
                        principalColumn: "Id");
                });

            migrationBuilder.CreateTable(
                name: "AccountNominee",
                columns: table => new
                {
                    Id = table.Column<Guid>(nullable: false),
                    AccountId = table.Column<Guid>(nullable: false),
                    NomineeUserId = table.Column<Guid>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AccountNominee", x => x.Id);
                    table.ForeignKey(
                        name: "FK_AccountNominee_Account_AccountId",
                        column: x => x.AccountId,
                        principalTable: "Account",
                        principalColumn: "Id");
                    table.ForeignKey(
                        name: "FK_AccountNominee_User_NomineeUserId",
                        column: x => x.NomineeUserId,
                        principalTable: "User",
                        principalColumn: "Id");
                });

            migrationBuilder.CreateTable(
                name: "Transaction",
                columns: table => new
                {
                    Id = table.Column<Guid>(nullable: false),
                    Date = table.Column<DateTime>(nullable: false),
                    Amount = table.Column<double>(nullable: false),
                    TransactionType = table.Column<int>(nullable: false),
                    Remarks = table.Column<string>(nullable: true),
                    AccountId = table.Column<Guid>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Transaction", x => x.Id);
                    table.ForeignKey(
                        name: "FK_Transaction_Account_AccountId",
                        column: x => x.AccountId,
                        principalTable: "Account",
                        principalColumn: "Id");
                });

            migrationBuilder.CreateTable(
                name: "UserInvitation",
                columns: table => new
                {
                    Id = table.Column<Guid>(nullable: false),
                    EmailAddress = table.Column<string>(maxLength: 100, nullable: false),
                    Date = table.Column<DateTime>(nullable: false),
                    AccountId = table.Column<Guid>(nullable: false),
                    UserId = table.Column<Guid>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_UserInvitation", x => x.Id);
                    table.ForeignKey(
                        name: "FK_UserInvitation_Account_AccountId",
                        column: x => x.AccountId,
                        principalTable: "Account",
                        principalColumn: "Id");
                    table.ForeignKey(
                        name: "FK_UserInvitation_User_UserId",
                        column: x => x.UserId,
                        principalTable: "User",
                        principalColumn: "Id");
                });

            migrationBuilder.CreateTable(
                name: "Transfer",
                columns: table => new
                {
                    Id = table.Column<Guid>(nullable: false),
                    DebitTransactionId = table.Column<Guid>(nullable: false),
                    CreditTransactionId = table.Column<Guid>(nullable: false),
                    CreatedDate = table.Column<DateTime>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Transfer", x => x.Id);
                    table.ForeignKey(
                        name: "FK_Transfer_Transaction_CreditTransactionId",
                        column: x => x.CreditTransactionId,
                        principalTable: "Transaction",
                        principalColumn: "Id");
                    table.ForeignKey(
                        name: "FK_Transfer_Transaction_DebitTransactionId",
                        column: x => x.DebitTransactionId,
                        principalTable: "Transaction",
                        principalColumn: "Id");
                });

            migrationBuilder.CreateIndex(
                name: "IX_Account_AccountTypeId",
                table: "Account",
                column: "AccountTypeId");

            migrationBuilder.CreateIndex(
                name: "IX_Account_BranchId",
                table: "Account",
                column: "BranchId");

            migrationBuilder.CreateIndex(
                name: "IX_Account_OwningUserId",
                table: "Account",
                column: "OwningUserId");

            migrationBuilder.CreateIndex(
                name: "IX_AccountNominee_AccountId",
                table: "AccountNominee",
                column: "AccountId");

            migrationBuilder.CreateIndex(
                name: "IX_AccountNominee_NomineeUserId",
                table: "AccountNominee",
                column: "NomineeUserId");

            migrationBuilder.CreateIndex(
                name: "IX_AccountType_BankId",
                table: "AccountType",
                column: "BankId");

            migrationBuilder.CreateIndex(
                name: "IX_Bank_Name",
                table: "Bank",
                column: "Name",
                unique: true);

            migrationBuilder.CreateIndex(
                name: "IX_Bank_OwningUserId",
                table: "Bank",
                column: "OwningUserId");

            migrationBuilder.CreateIndex(
                name: "IX_Branch_BankId",
                table: "Branch",
                column: "BankId");

            migrationBuilder.CreateIndex(
                name: "IX_Branch_Code",
                table: "Branch",
                column: "Code",
                unique: true);

            migrationBuilder.CreateIndex(
                name: "IX_Transaction_AccountId",
                table: "Transaction",
                column: "AccountId");

            migrationBuilder.CreateIndex(
                name: "IX_Transfer_CreditTransactionId",
                table: "Transfer",
                column: "CreditTransactionId");

            migrationBuilder.CreateIndex(
                name: "IX_Transfer_DebitTransactionId",
                table: "Transfer",
                column: "DebitTransactionId");

            migrationBuilder.CreateIndex(
                name: "IX_UserInvitation_AccountId",
                table: "UserInvitation",
                column: "AccountId");

            migrationBuilder.CreateIndex(
                name: "IX_UserInvitation_UserId",
                table: "UserInvitation",
                column: "UserId");

            InsertActivityPermissions(migrationBuilder);
            InsertRolePermissions(migrationBuilder);
            InsertImpliedPermissions(migrationBuilder);
            InsertSuspensionRules(migrationBuilder);
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "AccountNominee");

            migrationBuilder.DropTable(
                name: "Transfer");

            migrationBuilder.DropTable(
                name: "UserInvitation");

            migrationBuilder.DropTable(
                name: "Transaction");

            migrationBuilder.DropTable(
                name: "Account");

            migrationBuilder.DropTable(
                name: "AccountType");

            migrationBuilder.DropTable(
                name: "Branch");

            migrationBuilder.DropTable(
                name: "Bank");

            migrationBuilder.DropColumn(
                name: "UserType",
                table: "User");
        }

        private void InsertActivityPermissions(MigrationBuilder migrationBuilder)
        {
            var permissions = new[]
            {
                "('ManageBank', 'BANK', 'Manage bank', 1)", // instance permission
				"('ManageBranch', 'BANK', 'Manage branches', 1)", // instance permission
				"('ManageAccountType', 'BANK', 'Manage account types', 1)", // instance permission

				"('IndexCustomerAccount', 'ACCOUNT', 'View account', 0)", // general permission
				"('CreateCustomerAccount', 'ACCOUNT', 'Add new accounts', 1)", // instance permission
				"('ChangeStatus', 'ACCOUNT', 'edit account', 1)", // instance permission
				"('CreateDeposit', 'ACCOUNT', 'Create deposit', 1)", // instance permission
				"('CreateWithdrawal', 'ACCOUNT', 'Create withdrawal', 1)", // instance permission

				"('OpenAccount', 'ACCOUNT', 'Add new accounts', 0)", // general permission
				"('IndexAccount', 'ACCOUNT', 'View account', 0)", // general permission
				"('CreateTransfer', 'ACCOUNT', 'Create transfer', 1)", // instance permission
				"('AddNominee', 'ACCOUNT', 'Create new nominee', 1)", // instance permission
				"('DeleteNominee', 'ACCOUNT', 'Delete nominee', 1)", // instance permission
				"('IndexNominee', 'ACCOUNT', 'Create new nominee', 1)", // instance permission

				"('IndexAccountDetails', 'ACCOUNT', 'View account details', 1)", // general permission
			};

            migrationBuilder.Sql(@"insert into [dbo].[Permission]
				(PermissionCode, EntityTypeCode, Description, Kind)
				values" + string.Join(",\r\n", permissions));
        }

        private void InsertRolePermissions(MigrationBuilder migrationBuilder)
        {
            var permissions = new[]
            {
                "('BankOwner', 'BANK', 'Bank admin permission', 1)", // instance permission
				"('BranchManager', 'BRANCH', 'Branch admin permission', 1)", // instance permission
				"('BranchStaff', 'BRANCH', 'Branch staff permission', 1)", // instance permission
				"('AccountHolder', 'ACCOUNT', 'Account holder permission', 1)", // instance permission
				"('AccountNominee', 'ACCOUNT', 'Account nominee permission', 1)", // instance permission
				"('Customer', 'USER', 'Customer permission', 0)", // instance permission
            };

            migrationBuilder.Sql(@"insert into [dbo].[Permission]
				(PermissionCode, EntityTypeCode, Description, Kind)
				values" + string.Join(",\r\n", permissions));
        }

        private void InsertImpliedPermissions(MigrationBuilder migrationBuilder)
        {
            var impliedPermissions = new[]
            {
                "('BankOwner', 'BranchManager')",
                "('BankOwner', 'ManageBranch')",
                "('BankOwner', 'ManageAccountType')",
                "('BankOwner', 'ManageFirewall')",
                "('BankOwner', 'ManageBank')",

                "('BranchManager', 'BranchStaff')",

                "('BranchStaff', 'IndexCustomerAccount')",
                "('BranchStaff', 'CreateCustomerAccount')",
                "('BranchStaff', 'ChangeStatus')",
                "('BranchStaff', 'CreateDeposit')",
                "('BranchStaff', 'CreateWithdrawal')",
                "('BranchStaff', 'IndexAccountDetails')",
                "('BranchStaff', 'IndexNominee')",

                "('AccountHolder', 'OpenAccount')",
                "('AccountHolder', 'IndexAccount')",
                "('AccountHolder', 'CreateTransfer')",
                "('AccountHolder', 'AddNominee')",
                "('AccountHolder', 'DeleteNominee')",
                "('AccountHolder', 'IndexNominee')",
                "('AccountHolder', 'IndexAccountDetails')",

                "('AccountNominee', 'IndexAccount')",
                "('AccountNominee', 'IndexAccountDetails')",

                "('Customer', 'OpenAccount')"
            };

            migrationBuilder.Sql(@"insert into [dbo].[ImpliedPermission]
				(PermissionCode, ImpliedPermissionCode)
				values" + string.Join(",\r\n", impliedPermissions));
        }

        private void InsertSuspensionRules(MigrationBuilder migrationBuilder)
        {
	        migrationBuilder.Sql(@"insert into SuspensionExclusionRule
				(Id, EntityTypePattern, SuspensionTypePattern, VerbPattern, OperationPattern, PossessesAnyOfThePermissions)
				 values
				 --Can view = all get operations permitted on suspended entities regardless of reason/user role. But not MVC view retrieval actions which are really the post ones.
				(newid(), 'Account', '.*', 'GET', '^(?:(?<!ChangeStatus|Deposit|Withdrawal|Transfer).)*$', null),
				 --list* operations are also get only but called with post verb by jtable etc.
				(newid(), 'Account', '.*', 'POST', 'List.*', null),
				 --deposit allowed to all on suspended entity regardless of reason/user role (except on close accounts). Note - only staff is allowed but that's handled by permit authorization (ADA).
				(newid(), 'Account', '^(?:(?<!close).)*$', '.*', 'Deposit', null),
				--transfer/withdrawal is not allowed on pendingApproval. so allowing only deposit.
		        (newid(), 'Account', 'PendingApproval', '.*', 'Deposit', null),
				 --changeStatus allowed to owner on any status other than close.
				(newid(), 'Account', '^(?:(?<!close).)*$', '.*', 'ChangeStatus', 'BankOwner'),
				 --changeStatus allowed to manager on dormant/pendingApproval.
				(newid(), 'Account', 'Dormant|PendingApproval', '.*', 'ChangeStatus', 'BranchManager'),
				 --changeStatus allowed to manager/staff on KYC required.
				(newid(), 'Account', 'KYCRequired', '.*', 'ChangeStatus', 'BranchManager|BranchStaff')");
        }
    }
}

View Raw File

Try Out the Live Demo

Visit https://superfinance.ASPSecurityKit.net to play with a live demo based on this sample.

Don't Miss Out!

Be the first to get notified when the new quality content related to web app security like the one you're reading is posted.