Потребителски разрешения само за четене в приложението MVC3 с минимални промени

Имам задача да създам потребител само за четене за нашето приложение ASP.Net MVC3. т.е. те могат да влизат, да преглеждат всички данни, но не могат да актуализират никакви данни.

Прочетох много статии/рамки за удостоверяване, като тази: Внедрете защитени ASP.NET MVC приложения или Fluent Security Configuration или Създаване на филтри за действие в ASP.Net MVC (и няколко други, към които вече загубих връзки).

Проблемът с повечето от подходите е, че изискват драстични промени в домейна/приложението. И имам само един ден за прилагане на функцията.

Имаме около сто контролера със средно 4 действия на контролер (предимно CRUD операции) и преминаването през всеки един от тях е изключено. Също така би било лесно да забравите да поставите атрибути в новия код - въвеждане на грешки.

Досега измислих глобален филтър, който отрича всички базирани на POST действия и действия на контролера, наречени „Създаване“ за потребител само за четене:

public class ReadOnlyFilter : IActionFilter 
{

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var currentUser = HttpContext.Current.User;
        if (currentUser == null || !currentUser.Identity.IsAuthenticated)
            return; // user is not logged in yet


        if (!currentUser.IsInRole("Readonly")) 
            return; // user is not read-only. Nothing to see here, move on!

        // Presume User is read-only from now on.


        // if action is of type post - deny
        if (filterContext.HttpContext.Request.HttpMethod.ToUpper() == "POST")
        {
            filterContext.HttpContext.Response.Redirect("~/ReadOnlyAccess");
        }

        // if action is "Create" - deny access
        if (filterContext.ActionDescriptor.ActionName == "Create")
        {
            filterContext.HttpContext.Response.Redirect("~/ReadOnlyAccess");
        }

        // if action is edit - check if Details action exits -> redirect to it.
        //TODO get this done ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

        return;
    }


    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // blah! have to have this here for IActionFilter
    }
}

Следващото нещо, което планирам да създам атрибут [AllowReadOnlyUser] за действия по публикуване, като промяна на парола/имейл, и във филтъра да позволя това действие да премине.

Чудя се дали има по-добри начини да се направят подобни неща?

Актуализация: Приложението не е за обществено ползване. Използва се в корпоративния свят за проследяване на хора, активи и други скучни данни.

Актуализация 2: Изглежда съм приключил с тази задача. Направи го като контролер, както започна. Пълен код и малко обяснение, което можете да видите в моя блог.


person trailmax    schedule 18.09.2012    source източник
comment
Това ще позволи публикации за невлезли потребители. Също така не съм сигурен, че POST е гарантирано, че е с главни букви.   -  person usr    schedule 18.09.2012
comment
Нашето приложение се използва само от влезли потребители. Никакви контролери освен LoginController не са достъпни за света. Така че това е добре. Справедливо мнение за POST. Ще го поправя - благодаря!   -  person trailmax    schedule 18.09.2012
comment
Наскоро трябваше да преобразувам по-старо приложение за уеб формуляри на asp.net в режим само за четене за много кратко време. Моето решение беше да използвам javascript/jquery, за да деактивирам всички контроли за въвеждане и ненавигационни хипервръзки, и да премахна целия функционален код за щракване/изпращане, за да предотвратя обратно изпращане.   -  person asawyer    schedule 18.09.2012
comment
@asawyer Да, това ще свърши работа. Докато не получите потребител, който знае как да използва инструменти за отстраняване на грешки в браузъра и малко jquery.   -  person trailmax    schedule 18.09.2012
comment
@trailmax Няма да отричам, че е възможно, но не успях да се върна и да поправя страницата до степен, в която успя да публикува успешно, дори и с интимни познания за приложението и уеб формулярите. Освен това приложението ми беше само вътрешно и беше само за държавни одитори, които едва ли биха опитали нещо подобно. Направих го само за няколко часа, затова го предложих. Ако искате, мога да публикувам скрипта, за да го разгледате и тествате.   -  person asawyer    schedule 18.09.2012
comment
@asawyer Да, това звучи интересно. Имате ли нещо против да публикувате като отговор? така че получавате своя дял от гласовете за -)   -  person trailmax    schedule 18.09.2012
comment
@trailmax Публикувано е. Кажете ми, ако успеете да поправите и публикувате формуляр, след като се изпълнява.   -  person asawyer    schedule 18.09.2012


Отговори (2)


можете да използвате System.Web.Mvc.AuthorizeAttribute за вашата цел. Създайте клас, който произлиза от AuthorizeAttribute и заменете методите AuthorizeCore и HandleUnauthorizedRequest. В AuthorizeCore вие определяте дали даден потребител има право да изпълни действие, в HandleUnauthorizedRequest вие определяте какво да се покаже, ако не му е позволено (например, покажете "NotAllowed"-View).

След като създадете вашия персонализиран атрибут за оторизация, трябва да добавите атрибута към всички действия на контролера, които трябва да бъдат защитени от вашето персонализирано оторизиране. Например всички POST-методи. Но ако има POST-метод, който трябва да бъде разрешен за всички потребители, просто не добавяйте атрибута към това действие на контролера.

person Dirk Trilsbeek    schedule 18.09.2012
comment
Разглеждахме тази опция преди и трябваше да я отхвърлим. Имаме МНОГО контролери и разхвърлянето на атрибут [ReadOnly] не е най-добрият вариант и е магнит за грешки за нови контролери. - person trailmax; 18.09.2012
comment
все още можете да използвате AuthorizeAttribute на ниво контролер или дори да извлечете вашите контролери от потребителски контролер с атрибута на място. Ако даден метод има свой собствен AuthorizeAttribute, не правете нищо на ниво контролер. Ще трябва да замените метода OnAuthorization, за да проверите за атрибути на ниво метод. Все пак би означавало да модифицирате всичките си контролери. Но не съм сигурен дали общото улавяне на методи по тяхното име и тип заявка е добра идея, тъй като разчита на конвенции, прилагани на организационно ниво (ние използваме Create само за създаване на методи) също толкова. - person Dirk Trilsbeek; 18.09.2012
comment
+1. Сега това изглежда като много по-добра идея. Ще го разгледам. - person trailmax; 18.09.2012
comment
ето един пример: stackoverflow.com/questions/780436/ . Един от (по-късните) отговори също съдържа код за оторизация на ниво контролер/метод, вижте тук: gist.github.com /948822 - person Dirk Trilsbeek; 18.09.2012

Ще трябва да промените това малко и преди някой да ми каже, аз съм на 100% наясно, че това е ужасен хак. Но също така е доста ефективен и се прилага много бързо, което беше основната грижа по онова време. Разбира се, ще искате да пуснете това през обфикатор.

Има и някои неща в панела за актуализация, които трябва да бъдат премахнати или променени на jQuery ajax крайни куки за отговор или нещо подобно, ако е необходимо.

О, има и това, за да контролирате стартирането му само за потребители само за четене:

if (isReadonly && !Page.ClientScript.IsClientScriptBlockRegistered("ReadonlyScriptController"))
{
this.Page.ClientScript.RegisterStartupScript(this.GetType(), 
  "ReadonlyScriptController", "<script>RunReadonlyScript();</script>");
}

Скрипт:

<script type="text/javascript" src="<%# Page.ResolveUrl("~/ClientScripts/jquery-1.4.2.min.js") %>"></script>
<script type="text/javascript">
    function RunReadonlyScript() {
        //Extend jquery selections to add some new functionality
        //namely, the ability to select elements based on the
        //text of the element.
        $.expr[':'].textEquals = function (a, i, m) {
            var match = $(a).text().match("^" + m[3] + "$");
            return match && match.length > 0;
        };
        //this function does all the readonly work
        var disableStuff = function () {

            //select all controls that accept input, save changes, open a popup, or change form state
            // ** customize this with your own elements **
            $('button, input:not(:hidden), textarea, select,
              a:textEquals("Clear Selection"), 
              a:textEquals("Add Message"), 
              a:textEquals("CCC EVAL"),
              a[id$="availLink"], 
              a[id$="lbtnDelete"], 
              a[id$="lbtnEdit"]')
                //disable input controls
                .attr('disabled', 'disabled')
                //remove onclick javascript
                .removeAttr('onclick')
                //remove all bound click events
                .unbind("click")
                //add a new click event that does nothing
                //this stops <a> links from working
                .click(function (e) {
                    e.preventDefault(); 
                    return false;
                });

            //zap some images with click events that we don't want enabled
            $('img[id$="imgCalendar1"], img[id$="imgDelete"]').hide();
        }
        //if we are loading the home page, don't disable things
        //or the user won't be able to use the search controls
        var urlParts = document.URL.split('/');
        var last2 = urlParts[urlParts.length - 2] + '/' + urlParts[urlParts.length - 1];
        if (last2 !== 'home/view') {
            //disable everything initially
            disableStuff();
            //setup a timer to re-disable everything once per second
            //some tab panels need this to stay disabled
            setInterval(disableStuff, 1000);
            //some pages re-enable controls after update panels complete
            //make sure to keep them disabled!
            Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(disableStuff);
            Sys.WebForms.PageRequestManager.getInstance().add_endRequest(disableStuff);
        }
    }
</script>
person asawyer    schedule 18.09.2012
comment
Благодаря за скрипта, полезен е. Използвам това като допълнение към това, което вече имам от страна на сървъра: Ако изгледът предоставя входни елементи и те не са деактивирани на сървъра по някаква причина, вашият jquery се включва. След като свърша, ще направя блог-пост по темата и ще сложа линк за въпроса. - person trailmax; 19.09.2012
comment
Освен това току-що си поиграх с конзолата на Chrome и успях да изпратя формуляра чрез скрипт: $('button, input:not(:hidden), textarea, select').removeAttr("disabled"); $("#Comment").val("Hello, i'm from jquery console");$("form").submit(); Така че това е хубаво допълнение към потребителския интерфейс и трябва да бъде подкрепено от мерки от страна на сървъра (ако тази функция е критична). - person trailmax; 19.09.2012
comment
@trailmax Предполагам, че е по-лесно да публикувате и MVC, отколкото да се опитвате да накарате обратната връзка в уеб формуляр да работи отново. Всъщност никога не съм го пробвал на MVC. Все пак съм резервирал, маркирал съм този въпрос и в случай, че имам нужда от това за някое от по-новите ми приложения! - person asawyer; 19.09.2012