Skip to main content
All docs
V25.1
  • Authorization Logic — Reports

    • 11 minutes to read

    Use strategies outlined in this topic to implement authorization logic for both the DevExpress ASP.NET Web Forms Document Viewer and Report Designer (and address CWE-285-related security risks).

    Implement Authorization Logic

    Create a custom report storage derived from the ReportStorageWebExtension class to implement authorization logic for DevExpress Document Viewer and Report Designer components. As a starting point, you can use the following ReportStorageWithAccessRules class implementation and modify it based on your requirements:

    Show the ReportStorageWithAccessRules class
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Web;
    using DevExpress.XtraReports.UI;
    using DevExpress.XtraReports.Web.Extensions;
    
    namespace SecurityBestPractices.Authorization.Reports {
        public class ReportStorageWithAccessRules : ReportStorageWebExtension {
            private static readonly Dictionary<Type, string> reports = new Dictionary<Type, string> {
                {typeof(PublicReport), "Public Report"},
                {typeof(AdminReport), "Admin Report"},
                {typeof(JohnReport), "John Report"}
            };
            static string GetIdentityName() {
                return HttpContext.Current.User?.Identity?.Name;
            }
            static XtraReport CreateReportByDisplayName(string displayName) {
                Type type = reports.First(v => v.Value == displayName).Key;
    
                return (XtraReport)Activator.CreateInstance(type);
            }
            // Returns reports that the current user can view
            public static IEnumerable<string> GetViewableReportDisplayNamesForCurrentUser() {
                var identityName = GetIdentityName();
    
                var result = new List<string>(); 
    
                if (identityName == "Admin") {
                    result.AddRange(new[] { reports[typeof(AdminReport)], reports[typeof(JohnReport)] });
                } else if (identityName == "John") {
                    result.Add(reports[typeof(JohnReport)]);
                }
    
                result.Add(reports[typeof(PublicReport)]); // For unauthenticated users (i.e., public)
    
                return result;
            }
    
            // Returns reports that the current user can edit
            public static IEnumerable<string> GetEditableReportNamesForCurrentUser() {
                var identityName = GetIdentityName();
    
                if (identityName == "Admin") {
                    return new[] { reports[typeof(AdminReport)], reports[typeof(JohnReport)] };
                }
    
                if (identityName == "John") {
                    return new[] { reports[typeof(JohnReport)] };
                }
    
                return Array.Empty<string>();
            }
    
            // Overrides ReportStorageWebExtension
            public override bool CanSetData(string url) {
                var reportNames = GetEditableReportNamesForCurrentUser();
                return reportNames.Contains(url);
            }
            public override byte[] GetData(string url) {
                var reportNames = GetViewableReportDisplayNamesForCurrentUser();
                if(!reportNames.Contains(url))
                    throw new UnauthorizedAccessException();
    
                // Implement your logic to get bytes from DB
                XtraReport publicReport = CreateReportByDisplayName(url);
                using(MemoryStream ms = new MemoryStream()) {
                    publicReport.SaveLayoutToXml(ms);
                    return ms.GetBuffer();
                }
            }
            // Returns URLs and display names for all reports available for editing in the storage
            public override Dictionary<string, string> GetUrls() {
                var result = new Dictionary<string, string>();
                var reportNames = GetEditableReportNamesForCurrentUser();
                foreach(var reportName in reportNames) {
                    result.Add(reportName, reportName);
                }
                return result;
            }
            public override bool IsValidUrl(string url) {
                var reportNames = GetEditableReportNamesForCurrentUser();
                return reportNames.Contains(url);
            }
    
            public override void SetData(XtraReport report, string url) {
                // Implement your logic to save bytes to the database. Refer to the following topic for
                // more information and examples: https://docs.devexpress.com/XtraReports/17553
            }
            public override string SetNewData(XtraReport report, string defaultUrl) {
                // Implement your logic to save bytes to the database. Refer to the following topic for
                // more information and examples: https://docs.devexpress.com/XtraReports/17553
                return "New name";
            }
        }
    }
    

    Note the following implementation details:

    • The GetViewableReportDisplayNamesForCurrentUser method returns a list of reports available to the current user (view mode). Call this method from the overridden GetData method and other methods that interact with the report storage.

    • The GetEditableReportNamesForCurrentUser method returns a list of reports available to the current user (edit mode). Call this method from the overridden IsValidUrl method and other methods that write report data.

    Our ReportStorageWithAccessRules class implementation throws an UnauthorizedAccessException when users try to open reports they cannot access. To prevent errors, you can verify access rights in the PageLoad event and redirect unauthorized users to a public page:

    <dx:ASPxReportDesigner ID="ASPxReportDesigner1" runat="server">
    </dx:ASPxReportDesigner>
    
    protected void Page_Load(object sender, EventArgs e) {
        var name = Request.QueryString["name"];
        var reportNames = ReportStorageWithAccessRules.GetEditableReportNamesForCurrentUser();
        if(reportNames.Contains(name))
            ASPxReportDesigner1.OpenReport(name);
        else
            Response.Redirect("~/Authorization/Reports/ReportViewerPage.aspx");
    }
    

    Once you implement your custom report storage, register it in the Global.asax.cs or Global.asax.vb file:

    DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension.RegisterExtensionGlobal(
        new ReportStorageWithAccessRules()
    );
    

    Implement Operation Logger for Document Viewer

    The Document Viewer control maintains an open connection with the server to obtain additional document data when required (for instance, when a user switches pages or exports the document). This connection allows users to navigate through report pages even after they log out.

    Implement a custom operation logger to limit operations available to the current user. To implement access control rules, extend the WebDocumentViewerOperationLogger class and override its methods. Use the following OperationLogger class implementation as a starting point:

    Show the OperationLogger class

    Note

    For demonstration purposes, the OperationLogger class obtains user account data from a static property. In a real project, you should store authentication information in a data storage.

    using System;
    using System.Collections.Generic;
    using System.Web;
    using DevExpress.XtraPrinting;
    using DevExpress.XtraReports.UI;
    using DevExpress.XtraReports.Web.ClientControls;
    using DevExpress.XtraReports.Web.WebDocumentViewer;
    
    namespace SecurityBestPractices.Authorization.Reports {
        public class OperationLogger : WebDocumentViewerOperationLogger, IWebDocumentViewerAuthorizationService,
            IExportingAuthorizationService {
            const string ReportDictionaryName = "reports";
            const string DocumentDictionaryName = "documents";
            const string ExportedDocumentDictionaryName = "exportedDocuments";
    
            static readonly Dictionary<string, Dictionary<string, HashSet<string>>> authDictionary =
                new Dictionary<string, Dictionary<string, HashSet<string>>>();
    
            static OperationLogger() {
                authDictionary.Add("Public", new Dictionary<string, HashSet<string>> {
                    {ReportDictionaryName, new HashSet<string>()},
                    {DocumentDictionaryName, new HashSet<string>()},
                    {ExportedDocumentDictionaryName, new HashSet<string>()}
                });
                authDictionary.Add("Admin", new Dictionary<string, HashSet<string>> {
                    {ReportDictionaryName, new HashSet<string>()},
                    {DocumentDictionaryName, new HashSet<string>()},
                    {ExportedDocumentDictionaryName, new HashSet<string>()}
                });
                authDictionary.Add("John", new Dictionary<string, HashSet<string>> {
                    {ReportDictionaryName, new HashSet<string>()},
                    {DocumentDictionaryName, new HashSet<string>()},
                    {ExportedDocumentDictionaryName, new HashSet<string>()}
                });
            }
    
            public override void ReportOpening(string reportId, string documentId, XtraReport report) {
                if(report == null) {
                    var identityName = GetIdentityName();
                    if(string.IsNullOrEmpty(identityName))
                        identityName = "Public";
    
                    SaveUsedEntityId(ReportDictionaryName, identityName, reportId);
                    SaveUsedEntityId(DocumentDictionaryName, identityName, documentId);
                } else
                if(report is PublicReport) {
                    SaveUsedEntityId(ReportDictionaryName, "Public", reportId);
                    SaveUsedEntityId(DocumentDictionaryName, "Public", documentId);
                }
                else if(report is AdminReport) {
                    SaveUsedEntityId(ReportDictionaryName, "Admin", reportId);
                    SaveUsedEntityId(DocumentDictionaryName, "Admin", documentId);
                }
                else if(report is JohnReport) {
                    SaveUsedEntityId(ReportDictionaryName, "John", reportId);
                    SaveUsedEntityId(DocumentDictionaryName, "John", documentId);
    
                    SaveUsedEntityId(ReportDictionaryName, "Admin", reportId);
                    SaveUsedEntityId(DocumentDictionaryName, "Admin", documentId);
                }
            }
    
            public override void BuildStarted(string reportId, string documentId, ReportBuildProperties buildProperties) {
                if(IsEntityAuthorized("Public", ReportDictionaryName, reportId)) {
                    SaveUsedEntityId(DocumentDictionaryName, "Public", documentId);
                }
    
                if(IsEntityAuthorized("Admin", ReportDictionaryName, reportId)) {
                    SaveUsedEntityId(DocumentDictionaryName, "Admin", documentId);
                }
    
                if(IsEntityAuthorized("John", ReportDictionaryName, reportId)) {
                    SaveUsedEntityId(DocumentDictionaryName, "John", documentId);
                }
            }
    
            public override ExportedDocument ExportDocumentStarting(string documentId, string asyncExportOperationId,
                string format, ExportOptions options, PrintingSystemBase printingSystem,
                Func<ExportedDocument> doExportSynchronously) {
                if(!IsEntityAuthorizedForCurrentUser(DocumentDictionaryName, documentId))
                    throw new UnauthorizedAccessException();
    
                return base.ExportDocumentStarting(documentId, asyncExportOperationId, format, options, printingSystem,
                    doExportSynchronously);
            }
    
            public override void ReleaseDocument(string documentId) {
            }
    
            bool IWebDocumentViewerAuthorizationService.CanCreateDocument() {
                return true;
            }
    
            bool IWebDocumentViewerAuthorizationService.CanCreateReport() {
                return true;
            }
    
            bool IWebDocumentViewerAuthorizationService.CanReadDocument(string documentId) {
                return IsEntityAuthorizedForCurrentUser(DocumentDictionaryName, documentId);
            }
    
            bool IWebDocumentViewerAuthorizationService.CanReadReport(string reportId) {
                return IsEntityAuthorizedForCurrentUser(ReportDictionaryName, reportId);
            }
    
            bool IWebDocumentViewerAuthorizationService.CanReleaseDocument(string documentId) {
                return IsEntityAuthorizedForCurrentUser(DocumentDictionaryName, documentId);
            }
    
            bool IWebDocumentViewerAuthorizationService.CanReleaseReport(string reportId) {
                return IsEntityAuthorizedForCurrentUser(ReportDictionaryName, reportId);
            }
    
            static string GetIdentityName() {
                return HttpContext.Current.User?.Identity?.Name;
            }
    
            void SaveUsedEntityId(string dictionaryName, string user, string id) {
                if(string.IsNullOrEmpty(id))
                    return;
    
                lock(authDictionary)
                    authDictionary[user][dictionaryName].Add(id);
            }
    
            bool IsEntityAuthorizedForCurrentUser(string dictionaryName, string id) {
                return IsEntityAuthorized(GetIdentityName(), dictionaryName, id);
            }
    
            bool IsEntityAuthorized(string user, string dictionaryName, string id) {
                if(string.IsNullOrEmpty(id))
                    return false;
    
                lock(authDictionary)
                    return authDictionary["Public"][dictionaryName].Contains(id) || !string.IsNullOrEmpty(user) && authDictionary[user][dictionaryName].Contains(id);
            }
    
            public bool CanReadExportedDocument(string id) {
                // For DevExpress.Report.Preview.AsyncExportApproach = true;
                return IsEntityAuthorizedForCurrentUser(ExportedDocumentDictionaryName, id);
            }
        }
    }
    

    Register the operation logger in the Global.asax.cs or Global.asax.vb file:

    DefaultWebDocumentViewerContainer.Register<WebDocumentViewerOperationLogger, OperationLogger>();
    

    Restrict Access to Data Connections and Tables

    The Report Designer component allows users to browse available data connections/tables via its integrated Query Builder. Refer to the following topic to restrict access to these connections/tables: Authorization Logic — Query Builder.

    See Also