5

我正在尝试为 Elmah.Mvc 2.0 实现自定义身份验证。我知道我的 web.config 中有两个键(elmah.mvc.allowedRoles 和 elmah.mvc.allowedUsers),但这对我来说还不够。

我有一个自定义表单身份验证方法,它在 cookie 中添加了一些随机盐,所以我没有一个明确的用户名来输入 elmah.mvc.allowedUsers 值。此外,我没有实施任何角色。

有没有办法覆盖 ElmahController 或一些 Elmah 身份验证类/方法?

谢谢!

4

3 回答 3

4

正在进行的讨论在哪里 - https://github.com/alexanderbeletsky/elmah-mvc/pull/24

目前,这不是直接可能的,但在票证中您可以看到几种解决方案,包括自定义过滤器。我仍然不确定是否需要在Elmah.MVC包装本身中完成一些特殊的 tweeks。

于 2013-08-23T11:26:46.143 回答
3

您完全可以这样做并覆盖 ElmahController。事实上, Alexander Beletsky已经为 Elmah.Mvc 提供了一个nuget

一旦您创建了自己的 ElmahController,您就可以对其应用任何您喜欢的授权。在我的应用程序中,我有一个基本授权控制器来应用它。你只需要配置你的路线并返回一个 Elmah 结果,这一切都在他的网站上有很好的记录。

更新:自从我看到这个已经有一段时间了,但我有自己的控制器,受上面的启发但没有实际使用它。

[Authorize]
public class ElmahController : BaseAuthorizedController
{
    public ActionResult Index(string type)
    {
        return new ElmahResult(type);
    }
}

结果在哪里

using System;
using System.Web;
using System.Web.Mvc;

namespace Epic.Mvc.Mvc.ActionResults
{
    public class ElmahResult : ActionResult
    {
        private readonly string _resouceType;

        public ElmahResult(string resouceType)
        {
            _resouceType = resouceType;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            var factory = new Elmah.ErrorLogPageFactory();

            if (!string.IsNullOrEmpty(_resouceType))
            {
                var pathInfo = "/" + _resouceType;
                context.HttpContext.RewritePath(FilePath(context), pathInfo, context.HttpContext.Request.QueryString.ToString());
            }

            var currentApplication = (HttpApplication)context.HttpContext.GetService(typeof(HttpApplication));
            if (currentApplication == null) return;
            var currentContext = currentApplication.Context;

            var httpHandler = factory.GetHandler(currentContext, null, null, null);
            if (httpHandler is IHttpAsyncHandler)
            {
                var asyncHttpHandler = (IHttpAsyncHandler)httpHandler;
                asyncHttpHandler.BeginProcessRequest(currentContext, r => { }, null);
            }
            else
            {
                httpHandler.ProcessRequest(currentContext);
            }
        }

        private string FilePath(ControllerContext context)
        {
            return _resouceType != "stylesheet" ? context.HttpContext.Request.Path.Replace(String.Format("/{0}", _resouceType), string.Empty) : context.HttpContext.Request.Path;
        }
    }
}

我有两条路线(第二条非常可选)

routes.MapRoute("ElmahHandler", "elmah/{type}", new { action = "Index", controller = "Elmah", type = UrlParameter.Optional });
            routes.MapRoute("ElmahHandlerShortHand", "errors/{type}", new { action = "Index", controller = "Elmah", type = UrlParameter.Optional });
于 2013-08-20T12:46:21.077 回答
0

Elmah有一个内置的查看器暴露在~/Elmah.axdElmah MVC有一个内置的查看器暴露在~/Elmah,两者都有一些预设的配置选项。

正如dove 的回答概述的那样,如果您需要扩展内置配置,您可以将 Elmah 视图包装在您自己的控制器操作方法中,并根据需要授权访问。

一个可能的限制是,从 Elmah 通过 渲染视图Elmah.ErrorLogPageFactory()对于具有主布局页面的 MVC 应用程序不是超级友好。
*公平地说,这个限制也适用于任何开箱即用的实现。

自己动手

但是,由于您正在编写自己的自定义代码来路由和处理错误日志视图,因此编写视图组件并没有太多额外的工作,而不仅仅是包装提供的视图。到目前为止,这种方法不仅对谁可以查看,而且对他们可以查看的内容提供了最大的控制和粒度。

这是一个自定义ControllerActionsViews的快速而肮脏的实现,它公开了存储在Elmah.ErrorLog.GetDefault().GetErrors(0, 10, errors).

Controllers/ElmahController.cs

public class ElmahController : Controller
{
    [Authorize(Roles = "Admin")]
    public ActionResult Index(int pageNum = 1, int pageSize = 7)
    {
        var vm = new ElmahViewModel()
        {
            PageNum = pageNum,
            PageSize = pageSize
        };

        ErrorLog log = Elmah.ErrorLog.GetDefault(System.Web.HttpContext.Current);
        vm.TotalSize = log.GetErrors(vm.PageIndex, vm.PageSize, vm.Errors);

        return View(vm);
    }

    [Authorize(Roles = "Admin")]
    public ActionResult Details(string id)
    {
        ErrorLog log = Elmah.ErrorLog.GetDefault(System.Web.HttpContext.Current);
        ErrorLogEntry errDetail = log.GetError(id);

        return View(errDetail);
    }
}

public class ElmahViewModel
{
    public List<ErrorLogEntry> Errors { get; set; } = new List<ErrorLogEntry>();
    public int TotalSize { get; set; }
    public int PageSize { get; set; }
    public int PageNum { get; set; }
    public int PageIndex => Math.Max(0, PageNum - 1);
    public int TotalPages => (int)Math.Ceiling((double)TotalSize / PageSize);
}

这增加了两个动作。一个显示带有一些可选分页输入的错误列表,另一个将接收错误 ID 并仅返回该错误。然后我们也可以添加以下两个视图:

Views/Elmah/List.cshtml

@model CSHN.Controllers.ElmahViewModel

@{
    ViewBag.Title = "ELMAH (Error Logging Modules and Handlers)";
}

<table class="table table-condensed table-bordered table-striped table-accent">
    <thead>
        <tr>
            <th>
                Host Name
            </th>
            <th class="text-center" style="width: 85px">
                Status Code
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Errors.First().Error.Type)
            </th>
            <th style="min-width: 250px;">
                @Html.DisplayNameFor(model => model.Errors.First().Error.Message)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Errors.First().Error.User)
            </th>
            <th class="text-center" style="width: 160px">
                @Html.DisplayNameFor(model => model.Errors.First().Error.Time)
            </th>
            <th class="filter-false text-center" data-sorter="false" style="width: 75px">
                Actions
            </th>
        </tr>
    </thead>
    <tbody>
        @if (Model.Errors.Any())
        {
            foreach (var item in Model.Errors)
            {
                <tr>
                    <td>
                        @Html.DisplayFor(modelItem => item.Error.HostName)
                    </td>
                    <td class="text-center" style="width: 85px">
                        @Html.DisplayFor(modelItem => item.Error.StatusCode)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Error.Type)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Error.Message)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Error.User)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Error.Time)
                    </td>
                    <td class="disable-user-select hidden-print text-center" style="width: 75px">
                        <a href="@Url.Action("Details", "Elmah", new { id = item.Id})"
                           class="btn btn-xs btn-primary btn-muted">
                            <i class='fa fa-eye fa-fw'></i> Open
                        </a>
                    </td>
                </tr>

            }
        }
        else
        {
            <tr class="warning">
                <td colspan="7">There haven't been any errors since the last AppPool Restart.</td>
            </tr>
        }

    </tbody>
    @* We need a paginator if we have more records than currently returned *@
    @if (Model.TotalSize > Model.PageSize)
    {
        <tfoot>
            <tr>
                <th colspan="7" class="form-inline form-inline-xs">
                    <a href="@Url.Action("Index", new {pageNum = Model.PageNum - 1, pageSize = Model.PageSize})"
                       class="btn btn-default btn-sm prev @(Model.PageNum == 1?"disabled":"")">
                        <span class="fa fa-backward fa-fw"></span>
                    </a>
                    <span class="pagedisplay">
                        Page @Model.PageNum of @Model.TotalPages
                    </span>
                    <a href="@Url.Action("Index", new {pageNum = Model.PageNum + 1, pageSize = Model.PageSize})"
                       class="btn btn-default btn-sm next @(Model.PageNum == Model.TotalPages?"disabled":"")">
                        <span class="fa fa-forward fa-fw"></span>
                    </a>
                </th>
            </tr>
        </tfoot>
    }

</table>

<style>
    .table-accent thead tr {
        background: #0b6cce;
        color: white;
    }
    .pagedisplay {
        margin: 0 10px;
    }
</style>

Views/Elmah/Details.cshtml

@model Elmah.ErrorLogEntry

@{
    ViewBag.Title = $"Error Details on {Model.Error.Time}";
}

<a href="@Url.Action("Index", "Elmah")"
   class="btn btn-sm btn-default ">
    <i class='fa fa-th-list fa-fw'></i> Back to All Errors
</a>

<div class="form-horizontal">

    <h4 class="table-header"> General </h4>
    <table class="table table-condensed table-bordered table-striped table-fixed table-accent">
        <thead>
            <tr>
                <th>Name</th>
                <th>Value</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Message</td>
                <td>@Model.Error.Message</td>
            </tr>
            <tr>
                <td>Type</td>
                <td>@Model.Error.Type</td>
            </tr>
            <tr>
                <td>Time</td>
                <td>@Model.Error.Time</td>
            </tr>
            <tr>
                <td>Detail</td>
                <td><pre class="code-block">@Model.Error.Detail</pre></td>
            </tr>

        </tbody>
    </table>


    <h4 class="table-header"> Cookies </h4>
    <table class="table table-condensed table-bordered table-striped table-fixed table-accent">
        <thead>
            <tr>
                <th >Name</th>
                <th>Value</th>
            </tr>
        </thead>
        @foreach (var cookieKey in Model.Error.Cookies.AllKeys)
        {
            <tr>
                <td>@cookieKey</td>
                <td>@Model.Error.Cookies[cookieKey]</td>
            </tr>
        }
    </table>


    <h4 class="table-header"> Server Variables </h4>
    <table class="table table-condensed table-bordered table-striped table-fixed table-accent">
        <thead>
            <tr>
                <th >Name</th>
                <th>Value</th>
            </tr>
        </thead>
        @foreach (var servKey in Model.Error.ServerVariables.AllKeys)
        {
            if (!string.IsNullOrWhiteSpace(Model.Error.ServerVariables[servKey]))
            {
                <tr>
                    <td>@servKey</td>
                    <td>@Html.Raw(Html.Encode(Model.Error.ServerVariables[servKey]).Replace(";", ";<br />"))</td>
                </tr>
            }

        }
    </table>

</div>

<style>

    .table-header {
        background: #16168c;
        color: white;
        margin-bottom: 0;
        padding: 5px;
    }

    .table-fixed {
        table-layout: fixed;
    }

    .table-fixed td,
    .table-fixed th {
        word-break: break-all;
    }

    .table-accent thead tr {
        background: #0b6cce;
        color: white;
    }

    .table-accent thead tr th:first-child {
        width: 250px;
    }
    .table-accent td:first-child {
        font-weight: bold;
    }


    .code-block {
        overflow-x: scroll;
        white-space: pre;
        background: #ffffcc;
    }

</style>

这种方法的另一个好处是我们不需要set allowRemoteAccess="yes",它可以通过暴露 SessionId 暴露一些安全问题,比如会话劫持

如果这个实现不够健壮,你可以随时扩展和定制它,直到你满意为止。如果您想保留本地选项可用,您仍然可以公开它并通过将其隐藏在机器上为管理员提供友好链接HttpRequest.IsLocal

于 2018-02-28T22:13:39.120 回答