Authorization Logic — Dashboard
- 3 minutes to read
Use the strategies outlined in this topic to introduce authorization logic for your DevExpress ASP.NET MVC-powered Dashboard app (and address CWE-285-related security risks).
#Apply Authorization Attributes
The Authorize attribute specifies authorization rules for application pages. To protect your application, apply the Authorize attribute to the controller class. Use the AllowAnonymous attribute to allow anonymous access to public actions:
namespace SecurityBestPractices.Mvc.Controllers {
[Authorize]
public partial class AuthorizationController : Controller {
[AllowAnonymous]
public ActionResult PublicDashboard() {
return View("Dashboards/PublicDashboard");
}
public ActionResult Dashboard() {
return View("Dashboards/Dashboard");
}
}
// ...
}
#Implement Authorization Logic
To implement authorization in the DevExpress Dashboard extension, create a custom dashboard storage that specifies appropriate access rules and implements the IEditableDashboardStorage interface. Use the following class implementation as a starting point and modify it based on your requirements:
using System;
using System.Collections.Generic;
using System.Data;
using System.Web;
using System.Xml.Linq;
using DevExpress.DashboardWeb;
namespace SecurityBestPractices.Authorization.Dashboards {
public class DashboardStorageWithAccessRules : IEditableDashboardStorage {
readonly DataSet dashboards = new DataSet();
const string dashboardLayoutColumn = "DashboardXml";
const string nameColumn = "DashboardName";
const string dashboardIDColumn = "DashboardID";
readonly Dictionary<string, HashSet<string>> authDictionary = new Dictionary<string, HashSet<string>>();
public DashboardStorageWithAccessRules() {
DataTable table = new DataTable("Dashboards");
DataColumn idColumn = new DataColumn(dashboardIDColumn, typeof(int)) {
AutoIncrement = true,
AutoIncrementSeed = 1,
Unique = true,
AllowDBNull = false
};
table.Columns.Add(idColumn);
table.Columns.Add(dashboardLayoutColumn, typeof(string));
table.Columns.Add(nameColumn, typeof(string));
table.PrimaryKey = new[] { idColumn };
dashboards.Tables.Add(table);
// Your logic to get dashboard layouts from the database
var adminId = AddDashboardCore(XDocument.Load(HttpContext.Current.Server.MapPath(@"~/App_Data/AdminDashboard.xml")), "Admin Dashboard");
var johnId = AddDashboardCore(XDocument.Load(HttpContext.Current.Server.MapPath(@"~/App_Data/JohnDashboard.xml")), "John Dashboard");
authDictionary.Add("Admin", new HashSet<string>(new [] { adminId, johnId })); // Admin can view/edit all dashboards
authDictionary.Add("John", new HashSet<string>(new[] { johnId })); // John can view/edit only his dashboard
}
string AddDashboardCore(XDocument dashboard, string dashboardName) {
DataRow newRow = dashboards.Tables[0].NewRow();
newRow[nameColumn] = dashboardName;
newRow[dashboardLayoutColumn] = dashboard;
dashboards.Tables[0].Rows.Add(newRow);
return newRow[dashboardIDColumn].ToString();
}
public bool IsAuthorized(string dashboardId) {
var identityName = GetIdentityName();
if(!string.IsNullOrEmpty(identityName)) {
return authDictionary.ContainsKey(identityName) && authDictionary[identityName].Contains(dashboardId);
}
return false;
}
static string GetIdentityName() {
return HttpContext.Current.User?.Identity?.Name;
}
// Storage implementation
XDocument IDashboardStorage.LoadDashboard(string dashboardId) {
if (!IsAuthorized(dashboardId))
return null;
// Your logic to get dashboard bytes from the database by <dashboardId>
DataRow currentRow = dashboards.Tables[0].Rows.Find(dashboardId);
if (currentRow == null)
return null;
XDocument dashboardXml = XDocument.Parse(currentRow[dashboardLayoutColumn].ToString());
return dashboardXml;
}
IEnumerable<DashboardInfo> IDashboardStorage.GetAvailableDashboardsInfo() {
List<DashboardInfo> dashboardInfos = new List<DashboardInfo>();
foreach (DataRow row in dashboards.Tables[0].Rows) {
var dashboardId = row[dashboardIDColumn].ToString();
if (IsAuthorized(dashboardId)) {
DashboardInfo dashboardInfo = new DashboardInfo {
ID = row[dashboardIDColumn].ToString(),
Name = row[nameColumn].ToString()
};
dashboardInfos.Add(dashboardInfo);
}
}
return dashboardInfos;
}
void IDashboardStorage.SaveDashboard(string dashboardId, XDocument dashboard) {
if (!IsAuthorized(dashboardId))
return;
// Your logic to save dashboard bytes to the database by <dashboardId>
DataRow currentRow = dashboards.Tables[0].Rows.Find(dashboardId);
if (currentRow == null)
return;
currentRow[dashboardLayoutColumn] = dashboard;
}
string IEditableDashboardStorage.AddDashboard(XDocument dashboard, string dashboardName) {
var identityName = GetIdentityName();
if(string.IsNullOrEmpty(identityName))
throw new UnauthorizedAccessException();
if(!authDictionary.ContainsKey(identityName)) {
authDictionary.Add(identityName, new HashSet<string>());
}
var id = AddDashboardCore(dashboard, dashboardName);
authDictionary[identityName].Add(id);
return id;
}
}
}
Register your custom dashboard storage in the Global.asax.cs file as follows:
DashboardConfigurator.Default.SetDashboardStorage(new DashboardStorageWithAccessRules());
#Restrict Access to Data Connections and Data Tables
The DevExpress Dashboard extension allows users to browse available data connections/tables when using its integrated Query Builder. Refer to the following topic to restrict access to these connections/tables: Authorization Logic — Query Builder.