ASP.NET – role użytkowników

Wykonanie akcji w kontrolerze przez nie zalogowanego użytkownika można zabezpieczyć dodając atrybut: [Authorize]. A jak pozwolić wykonać akcję tylko użytkownikowi ze specjalnymi uprawnieniami?

Można zadeklarować własny atrybut, który dziedziczy po AuthorizeAttribute i w nim dodać logikę sprawdzenia, czy dany użytkownik może daną akcję wykonać.

Zacznijmy od tego, że użytkownik należy do jakiejś roli. Można tu wykorzystać role wbudowane lub dodać własny mechanizm ról. Ten drugi przypadek jest kuszący, jeśli przewidujemy, że wbudowane role mogą nam w przyszłości nie wystarczyć. Np., gdy będziemy chcieli przypisywać role w ramach czasowych.

Zdefiniowałem enum.:

public enum Role
{
    adminOrganization = 1,
    accountant = 2
}

Kod mojego atrybutu:

    public class AuthorizeCustomAttribute : AuthorizeAttribute
    {
        private readonly List<Role> _allowedroles;
        public AuthorizeCustomAttribute(params Role[] roles)
        {
            _allowedroles = roles.ToList();
        }

        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (httpContext.User == null)
                return false;

            return UserOrganizationRolesHelper.IsUserRole(httpContext.User.Identity.Name, _allowedroles);
        }

        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            filterContext.Result = new RedirectToRouteResult(
               new RouteValueDictionary
               {
                    { "controller", "Home" },
                    { "action", "UnAuthorized" }
               });
        }
    }

Z atrybutu korzystamy:

[AuthorizeCustom(Role.adminOrganization, Role.accountant)]
public class PlacesController : Controller

Logika sprawdzenia, czy użytkownik należy do roli jest związanaz moim rozwiązaniem trzymania przypisanych do użytkownika ról:

        public static bool IsUserRole(string userName, IList<Role> roles)
        {
            var userOrg = GetUserOrganization(userName);
            List<UserRole> userRoles = JsonConvert.DeserializeObject<List<UserRole>>(userOrg.Roles);
            return userRoles.Exists(x => roles.Contains(x.Role) && x.Active);
        }

Camp Manager – nie każdy może założyć konto?

We wczesnych wersjach aplikacji, każdy, kto znał adres strony, mógł założyć sobie konto i pooglądać dane. Ba, mógł je poedytować. Czyli mamy dwa problemy do rozwiązania:

  1. nie każdy powinien móc założyć konto
  2. uprawnienia dla użytkowników!

Tutaj zajmę się tym pierwszym.

Założenia:

  • pierwsze i najważniejsze, nie narobić się 🙂. Czyli nie wyrzucam ani nie wywracam do góry nogami istniejącego mechanizmu zakładania użytkownika. Dlatego:
  • dodaję stronę, gdzie użytkownik może wpisać maile, na które można zarejestrować nowego użytkownika
  • wpiąć się w istniejący mechanizm zakładania nowego użytkownika i nie pozwolić mu założyć konta, jeśli jego email nie został wcześniej zarejestrowany. Jest to standardowy mechanizm wygenerowany z szablonem projektu ASP.NET

W pierwszy kroku dodaję nowy model, który mógłby mieć tylko id i email. U mnie ma ciut więcej, a to już z myślą o uprawnieniach:

    public class UserEmailAllow
    {
        public int Id { get; set; }
        public string Email { get; set; }
        public string StartRoles { get; set; }
        public DateTime? DateExpire { get; set; }

        [Required]
        public Organization Organization { get; set; }
    }

I taką klasę dodałem do mojego DbContext.

Kontrolera i widoku nie pokazuję, to zwykły CRUD, czyli skorzystałem z generatora. Jedynie dodałem sprawdzenie przed zapisem:

            if (_db.Users.FirstOrDefault(x => x.Email == userEmailAllowViewModel.Email) != null)
            {
                ModelState.AddModelError("", "Email istnieje w zarejestrowanych użytkownikach");
                return View(userEmailAllowViewModel);
            }

            if (_db.UserEmailAllow.FirstOrDefault(x => x.Email == userEmailAllowViewModel.Email) != null)
            {
                ModelState.AddModelError("", "Email istnieje w oczekujących na rejestrację");
                return View(userEmailAllowViewModel);
            }

Pozostaje nam wpięcie w zakładanie nowego użytkownika. W kontrolerze: AccountController, jest metoda Register. Wystarczy dodać w niej poniższy kawałek kodu, tuż po sprawdzeniu czy model jest poprawny:

            ApplicationDbContext db = new ApplicationDbContext();
            var userAllow = db.UserEmailAllow.FirstOrDefault(x => x.Email == model.Email);
            if (userAllow == null)
            {
                AddErrors(new IdentityResult("Email nie zarejestrowany. Zgłoś się do administratora"));
                return View(model);
            }

CampManager podsumowanie…

Miało być podsumowanie pierwszego sezonu działania programu, a mineły trzy. Co do konkursu Daj się poznać to super inicjatywa. Ale o swoim udziale w nim nic nie napiszę. Niech już to tak zostanie.
Najważniejszy cel dla mnie, to było uruchomienie programu. Od strony użytkowników: zadowolenie, zrobił robotę, ale nie skończony.
Od mojej strony:
  • Ułatwił życie no i fajnie.
  • Wyliczenia (w końcu dość proste) dały radę 🙂
  • brak raportów
  • za póżno wdrożony… i tu może pojawi się osobny wpis
  • czas na ogarnięcie się i poprawki

Od strony technicznej

EF na plus, ale są „ale”
– niski koszt wejścia w technologię
– migracja bazy – pięknie, bo nie zastanawiam się, co będzie mi potrzebne jutro. Myślę o najbliższym zadaniu i nie staram się wyprzedzać, bo koszt pojedyńczej migracji jest pomijalny. I ten punkt przykrył wszystkie minusy.
– nie muszę pisać mapowań, juhuuu 🙂 przecież marzę o bazach obiektowych
– narzut niepotrzebnych zapytań (np. przy zapisie muszę w tym samym contex wczytać obiekty zależne, nie wystarczy foreign key)
– moim zdaniem (podkreślam: moim), wychodzi brak POCO we wcześniejszych wersjach (punkt powyższy) i teraz to smrodzi

FaUDD

Fast and Ugly Driven Development
Brzydki kod, brak testów, kontrolery przeładowane, zero testów i… działa :/ A nie powinno? Zrobiłem prototyp i wrzuciłem go na produkcję. Czy mam być z tego dumny? Chciałem sprawdzić, czy tak się da. I da się. Ale zaciągnąłem dług technologiczny i jestem tego świadom. Teraz trzeba refaktoryzować. Refaktoryzować i… moment, przecież nie mam testów. Uuu, łatwo nie będzie.

Analiza cz. 1

Robię soft dla firmy/organizacji zarządzającej bazą obozową. Poniżej spisane pierwsze podejście do analizy problemu.

 

Baza wynajmuje miejsca obozowe zorganizowanym obozom, zapewniając sprzęt obozowy i zaprowiantowanie. W pierwszym etapie zajmiemy się rozliczeniem kosztów wyżywienia z wyliczaniem stawki żywieniowej dziennej. Chcemy wyliczyć: ile wynosi stawka dzienna – koszt produktów, z których przygotowano posiłki w danym dniu na osobę.

Czytaj dalej Analiza cz. 1