aspnet-405155-security-considerations-authorization-dashboard.md
Use the strategies outlined in this topic to introduce authorization logic for your DevExpress ASP.NET WebForms-powered Dashboard app (and address CWE-285-related security risks).
The DevExpress ASP.NET WebForms Dashboard can use both standard Web Forms APIs or DashboardConfigurator APIs to exchange data with the server. The UseDashboardConfigurator property specifies desired data exchange mode and defines the authorization mechanism used.
If the UseDashboardConfigurator property is set to false (default), you must use standard ASP.NET access restriction logic to configure authorized access:
<location path="Authorization/Dashboards">
<authorization>
<deny users="?" />
</authorization>
</location>
Standard ASP.NET authorization logic cannot be used if the UseDashboardConfigurator property is enabled. If UseDashboardConfigurator is set to true, you must 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:
Show the DashboardStorageWithAccessRules class
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;
}
}
}
Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Web
Imports System.Xml.Linq
Imports DevExpress.DashboardWeb
Namespace SecurityBestPractices.Authorization.Dashboards
Public Class DashboardStorageWithAccessRules Inherits IEditableDashboardStorage
ReadOnly dashboards As DataSet = New DataSet()
Const dashboardLayoutColumn As String = "DashboardXml"
Const nameColumn As String = "DashboardName"
Const dashboardIDColumn As String = "DashboardID"
ReadOnly authDictionary As Dictionary(Of String, HashSet(Of String)) = New Dictionary(Of String, HashSet(Of String))()
Public Sub New()
Dim table As DataTable = New DataTable("Dashboards")
Dim idColumn As DataColumn = New DataColumn(dashboardIDColumn, GetType(Integer)) With {
.AutoIncrement = True,
.AutoIncrementSeed = 1,
.Unique = True,
.AllowDBNull = False
}
table.Columns.Add(idColumn)
table.Columns.Add(dashboardLayoutColumn, GetType(String))
table.Columns.Add(nameColumn, GetType(String))
table.PrimaryKey = {idColumn}
dashboards.Tables.Add(table)
' Your logic to get dashboard layouts from the database
Dim adminId = AddDashboardCore(XDocument.Load(HttpContext.Current.Server.MapPath("~/App_Data/AdminDashboard.xml")), "Admin Dashboard")
Dim johnId = AddDashboardCore(XDocument.Load(HttpContext.Current.Server.MapPath("~/App_Data/JohnDashboard.xml")), "John Dashboard")
authDictionary.Add("Admin", New HashSet(Of String)({adminId, johnId})) ' Admin can view/edit all dashboards
authDictionary.Add("John", New HashSet(Of String)({johnId})) ' John can view/edit only his dashboard
End Sub
Private Function AddDashboardCore(ByVal dashboard As XDocument, ByVal dashboardName As String) As String
Dim newRow As DataRow = dashboards.Tables(0).NewRow()
newRow(nameColumn) = dashboardName
newRow(dashboardLayoutColumn) = dashboard
dashboards.Tables(0).Rows.Add(newRow)
Return newRow(dashboardIDColumn).ToString()
End Function
Public Function IsAuthorized(ByVal dashboardId As String) As Boolean
Dim identityName = GetIdentityName()
If Not String.IsNullOrEmpty(identityName) Then
Return authDictionary.ContainsKey(identityName) AndAlso authDictionary(identityName).Contains(dashboardId)
End If
Return False
End Function
Private Shared Function GetIdentityName() As String
Return HttpContext.Current.User?.Identity?.Name
End Function
' Storage implementation
Private Function LoadDashboard(ByVal dashboardId As String) As XDocument
If Not IsAuthorized(dashboardId)
Then Return Nothing
' Your logic to get dashboard bytes from the database by <dashboardId>
Dim currentRow As DataRow = dashboards.Tables(0).Rows.Find(dashboardId)
If currentRow Is Nothing
Then Return Nothing
Dim dashboardXml As XDocument = XDocument.Parse(currentRow(dashboardLayoutColumn).ToString())
Return dashboardXml
End Function
Private Function GetAvailableDashboardsInfo() As IEnumerable(Of DashboardInfo)
Dim dashboardInfos As List(Of DashboardInfo) = New List(Of DashboardInfo)()
For Each row As DataRow In dashboards.Tables(0).Rows
Dim dashboardId = row(dashboardIDColumn).ToString()
If IsAuthorized(dashboardId) Then
Dim dashboardInfo As DashboardInfo = New DashboardInfo With {
.ID = row(dashboardIDColumn).ToString(),
.Name = row(nameColumn).ToString()
}
dashboardInfos.Add(dashboardInfo)
End If
Next
Return dashboardInfos
End Function
Private Sub SaveDashboard(ByVal dashboardId As String, ByVal dashboard As XDocument)
If Not IsAuthorized(dashboardId)
Then Return
' Your logic to save dashboard bytes to the database by <dashboardId>
Dim currentRow As DataRow = dashboards.Tables(0).Rows.Find(dashboardId)
If currentRow Is Nothing
Then Return
currentRow(dashboardLayoutColumn) = dashboard
End Sub
Private Function AddDashboard(ByVal dashboard As XDocument, ByVal dashboardName As String) As String
Dim identityName = GetIdentityName()
If String.IsNullOrEmpty(identityName)
Then Throw New UnauthorizedAccessException()
If Not authDictionary.ContainsKey(identityName) Then
authDictionary.Add(identityName, New HashSet(Of String)())
End If
Dim id = AddDashboardCore(dashboard, dashboardName)
authDictionary(identityName).Add(id)
Return id
End Function
End Class
End Namespace
Register your custom dashboard storage in the Global.asax.cs or Global.asax.vb file as follows:
DashboardConfigurator.Default.SetDashboardStorage(new DashboardStorageWithAccessRules());
DashboardConfigurator.Default.CustomParameters += (o, args) => {
if (!new DashboardStorageWithAccessRules().IsAuthorized(args.DashboardId))
throw new UnauthorizedAccessException();
};
DashboardConfigurator.[Default].SetDashboardStorage(New DashboardStorageWithAccessRules())
DashboardConfigurator.[Default].CustomParameters += Sub(o, args)
If Not New DashboardStorageWithAccessRules().IsAuthorized(args.DashboardId)
Then Throw New UnauthorizedAccessException()
End Sub
The DevExpress Dashboard control 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.