Autoryzacja - gdzie?

1

Powiedzmy, że mam aplikację, w której użytkownik może pobierać (tylko swoje) pliki. Gdzie powinno być sprawdzenie tego, czy użytkownik faktycznie jest właścicielem tego pliku? Pomyślałem, żeby zrobić policy-based authorization, i dostałem coś takiego:

public class DownloadFileAuthorizationHandler : AuthorizationHandler<DownloadFilePermission>
    {
        private readonly ApplicationDbContext dbContext;
        private readonly IAuthContext authContext;
        
        public DownloadFileAuthorizationHandler(ApplicationDbContext dbContext, IAuthContext authContext)
        {
            this.dbContext = dbContext;
            this.authContext = authContext;
        }

        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, DownloadFilePermission permission)
        {
            if (context.Resource is AuthorizationFilterContext mvcContext)
            {
                var currentUserId = authContext.GetCurrentUserId();
                
                var fileId = int.Parse(mvcContext.RouteData.Values["fileId"] as string);

                var file = await dbContext.Files.FindAsync(fileId);

                if (file?.CreatedById == currentUserId)
                {
                    context.Succeed(permission);
                }                
            }
        }
    }

Tak to powinno wyglądać?

0

Może ktoś podzieli się tym, jak to wygląda u niego? ;)

EDIT
Nieważne, wyrzuciłem kluczową część logiki do innej klasy i z handlerów zaczęło się dość przyjemnie korzystać. Mam np. coś takiego:

public class FileAuthContext
    {
        private readonly IAuthContext authContext;
        private readonly ApplicationDbContext dbContext;

        public FileAuthContext(IAuthContext authContext, ApplicationDbContext dbContext)
        {
            this.authContext = authContext;
            this.dbContext = dbContext;
        }

        public async Task<bool> IsFileOwner(int fileId)
        {
            var file = await dbContext.Files.FindAsync(fileId);
            // todo co gdy file == null?
            return file.CreatedById == authContext.GetCurrentUserId();
        }

        public async Task<bool> HasPermissionToDownloadFile(int fileId)
        {
            // kod
        }
        
        // inne metody
    }

W handlerach po prostu wstrzykuję tę klasę i wywołuje poszczególne metody w zależności od tego, jakie wymagania muszą być spełnione.

0

Chociaż dalej autoryzacja w ten sposób niezbyt mi się podoba.

  1. Za każdym razem, gdy trzeba coś sprawdzić w bazie w celu autoryzacji, trzeba tworzyć DoXXXPermission i DoXXXAuthorizationHandler. Nie jest to przykład przeinżynierowania? Skoro i tak używam MediatR i jedno żądanie ma dokładnie jedną klasa obsługującą to żądanie, to prościej byłoby po prostu utworzyć w tej klasie metodę pomocniczą z kilkoma ifami.
  2. Co, gdy zasób, do którego się odwołujemy, nie istnieje? Przy tym podejściu, jeśli chcemy zwrócić 404, należałoby przyjąć, że użytkownik ma uprawnienia do wykonania akcji, a potem w handlerze żądania zwrócić informację, że zasobu nie ma. Tylko że to oznacza dwa zapytania do bazy danych, bo chyba EFC nie cachuje zapytań, które nic nie zwracają (prawda?).
  3. Czy w ogóle w filtrach (ten kod zostanie wywołany przez filter Authorize) powinno się wykonywać zapytania do bazy? Czy to dobra praktyka?
    Nie chciałem nikogo przywoływać, ale już jestem zdesperowany. @somekind ? @Aventus ? @neves ? Ktokolwiek?
1

Czy w ogóle w filtrach (ten kod zostanie wywołany przez filter Authorize) powinno się wykonywać zapytania do bazy? Czy to dobra praktyka?

A dlaczego nie? jak bez zapytania do bazy chcesz sprawdzić uprawnienia?

Jak sprawdzić czy ktoś ma uprawnienia / jest w roli administratora bez zapytania bazy?

Jak sprawdzić czy user ma jakieś niestandardowe uprawnienia typu jest autorem pliku lub jest na liście osób, które mogą go edytować?

  1. Co, gdy zasób, do którego się odwołujemy, nie istnieje? Przy tym podejściu, jeśli chcemy zwrócić 404, należałoby przyjąć, że użytkownik ma uprawnienia do wykonania akcji, a potem w handlerze żądania zwrócić informację, że zasobu nie ma. Tylko że to oznacza dwa zapytania do bazy danych, bo chyba EFC nie cachuje zapytań, które nic nie zwracają (prawda?).

Nie wiem jak bardzo rozbite są u ciebie uprawnienia w aplikacji, ale taka kolejność wydaje się dość sensowna

  • Uprawnienia do akcji

  • Czy zasób istnieje

  • Uprawnienia do zasobu

Chociaż tak naprawdę czy zasób istnieje i jego uprawnienia możesz sprawdzić w swojej IsFileOwner ale musiałbyś poza boolem zwrócić jakiś output message.

Według mnie, to ten twój FileAuthContext to dobre podejście, ale się nie znam :P

3
nobody01 napisał(a):

Chociaż dalej autoryzacja w ten sposób niezbyt mi się podoba.

  1. Za każdym razem, gdy trzeba coś sprawdzić w bazie w celu autoryzacji, trzeba tworzyć DoXXXPermission i DoXXXAuthorizationHandler. Nie jest to przykład przeinżynierowania?

Najprościej to stworzyć atrybut do trzymania wymaganego uprawnienia dla danej akcji i w metodzie autoryzujacej sprawdzać to.

Skoro i tak używam MediatR i jedno żądanie ma dokładnie jedną klasa obsługującą to żądanie, to prościej byłoby po prostu utworzyć w tej klasie metodę pomocniczą z kilkoma ifami.

Skoro używasz MediatR to udekoruj swoje komunikaty atrybutami z odpowiednimi uprawnieniami i wczep się w pipeline, tam wywołując swoją logikę.

  1. Co, gdy zasób, do którego się odwołujemy, nie istnieje? Przy tym podejściu, jeśli chcemy zwrócić 404, należałoby przyjąć, że użytkownik ma uprawnienia do wykonania akcji, a potem w handlerze żądania zwrócić informację, że zasobu nie ma. Tylko że to oznacza dwa zapytania do bazy danych, bo chyba EFC nie cachuje zapytań, które nic nie zwracają (prawda?).

Zależy jaką logikę obierzesz. Przykład jeśli user chce ściągnąć plik /dupa/cycki/foo, gdzie foo nie istnieje, ale nawet gdyby istniało to nie miałby dostępu do ścieżki to wypada zwrócić tak czy siak forbidden.

  1. Czy w ogóle w filtrach (ten kod zostanie wywołany przez filter Authorize) powinno się wykonywać zapytania do bazy? Czy to dobra praktyka?

Na pewno nie bezpośrednio, wtedy masz związany element frameworka do prezentacji wraz z biblioteczką do bazy danych.
Polecałbym w ogóle nie robić takich rzeczy jak logika dostępu do zasobów na poziomie frameworka do prezentacji..

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.