DDD CQRS - autoryzacja na zapytanie i na polecenie

15

streszczenie

Czy autoryzacja w CQRS / DDD powinna być realizowana na polecenie / zapytanie, czy nie?

Po raz pierwszy opracowuję aplikację online, wykorzystującą mniej więcej ściśle wzorzec DDD CQRS. Wpadłem na jakiś problem, którego tak naprawdę nie mogę rozwiązać.

Aplikacja, którą tworzę, jest aplikacją do tworzenia ksiąg rachunkowych, umożliwiającą tworzenie ksiąg rachunkowych, a także umożliwiającą ich przeglądanie / edytowanie / usuwanie, np. Pracownikom. Twórca księgi powinien mieć możliwość edytowania praw dostępu do księgi, którą utworzył. Może nawet zmienić właściciela. Domena ma dwa agregaty TLedger i TUser .

Przeczytałem wiele postów ze słowem kluczowym DDD / CQRS dotyczących bezpieczeństwa, autoryzacji itp. Większość z nich stwierdziła, że ​​autoryzacja jest poddomeną ogólną , chyba że budowana jest aplikacja zabezpieczająca.

W tym przypadku domeną podstawową jest z pewnością domena księgowa zainteresowana transakcjami, bilansowaniem i rachunkami. Wymagana jest także funkcjonalność umożliwiająca zarządzanie drobnym dostępem do ksiąg rachunkowych. Zastanawiam się, jak zaprojektować to w kategoriach DDD / CQRS.

W samouczkach DDD w całym miejscu stwierdzono, że polecenia są częścią wszechobecnego języka. Mają znaczenie. Są to konkretne działania, które reprezentują „rzeczywistość”.

Ponieważ wszystkie te polecenia i zapytania są faktycznymi działaniami, które użytkownicy wykonaliby w „prawdziwym życiu”, czy wdrożenie autoryzacji powinno być połączone ze wszystkimi tymi „poleceniami” i „zapytaniami”? Użytkownik miałby uprawnienia do wykonania TLedger.addTransaction (), ale nie na przykład TLedger.removeTransaction (). Lub użytkownik może wykonać zapytanie „getSummaries ()”, ale nie „getTransactions ()”.

Trójwymiarowe mapowanie istniałoby w formie polecenia-księgi użytkownika lub zapytania księgi użytkownika w celu ustalenia praw dostępu.

Lub w odsprzężony sposób nazwane „uprawnienia” zostaną zarejestrowane dla użytkownika. Uprawnienia, które byłyby następnie mapowane dla określonych poleceń. Na przykład pozwolenie „ManageTransactions” pozwoliłoby użytkownikowi na wykonanie polecenia „AddTransaction ()”, „RemoveTransaction ()” itp.

  1. Użytkownik mapujący uprawnienia -> księga główna -> polecenie / zapytanie

  2. Użytkownik mapujący uprawnienia -> księga główna -> pozwolenie -> polecenie / zapytanie

To pierwsza część pytania. Czy w skrócie, czy autoryzacja w CQRS / DDD powinna być realizowana dla polecenia lub zapytania? Czy też należy oddzielić autoryzację od poleceń?

Po drugie, dotyczy autoryzacji na podstawie uprawnień. Użytkownik powinien mieć możliwość zarządzania uprawnieniami do swoich ksiąg rachunkowych lub ksiąg, którymi może zarządzać.

  1. Komendy zarządzania autoryzacją występują w księdze głównej

Myślałem o dodaniu zdarzeń / poleceń / procedur obsługi do agregacji Księgi głównej, takich jak grantPermission (), revokePermission () itp. W takim przypadku wymuszenie tych reguł miałoby miejsce w procedurach obsługi komend. Wymagałoby to jednak, aby wszystkie polecenia zawierały identyfikator użytkownika, który je wydał. Następnie sprawdziłbym TLedger, czy istnieje uprawnienie dla tego użytkownika do wykonania tego polecenia.

Na przykład :

class TLedger{ 
    function addTransactionCmdHandler(cmd){
        if (!this.permissions.exist(user, 'addTransaction')
            throw new Error('Not Authorized');
    }
}
  1. Komendy zarządzania autoryzacją w Użytkowniku

Odwrotnym rozwiązaniem byłoby włączenie uprawnień do TUser. TUser miałby zestaw uprawnień. Następnie w modułach obsługi poleceń TLedger pobierałbym użytkownika i sprawdzał, czy ma on uprawnienia do wykonania polecenia. Wymagałoby to jednak pobrania agregatu TUser dla każdego polecenia TLedger.

class TAddTransactionCmdHandler(cmd) {
    this.userRepository.find(cmd.userId)
    .then(function(user){
        if (!user.can(cmd)){
            throw new Error('Not authorized');
        }
        return this.ledgerRepository.find(cmd.ledgerId);
    })
    .then(function(ledger){
        ledger.addTransaction(cmd);
    })

}
  1. Kolejna domena z usługą

Inną możliwością byłoby całkowite modelowanie innej domeny autoryzacji. Ta domena byłaby zainteresowana prawami dostępu, autoryzacją itp. Subdomena księgowości użyłaby następnie usługi, aby uzyskać dostęp do tej domeny autoryzacji w formie AuthorizationService.isAuthorized(user, command).

class TAddTransactionCmdHandler(cmd) {
    authService.isAuthorized(cmd)
    .then(function(authorized){
        if (!authorized) throw new Error('Not authorized');
        return this.ledgerRepository.find(cmd.ledgerId)
    })
    .then(function(){
        ledger.addTransaction(cmd);
    })

}

Jaka decyzja byłaby najbardziej „DDD / CQRS”?

Ludovic C.
źródło
1
Świetne pytanie - starałem się rozwiązywać podobne problemy i żadna literatura nie wydaje się dotyczyć bezpośrednio. Byłem trochę zdezorientowany drugą połową twojego pytania. Brzmiało to tak, jakbyś zastanawiał się, gdzie umieścić zarządzanie uprawnieniami (dodawanie lub usuwanie uprawnień), ale przedstawione przykłady dotyczą dodawania transakcji, więc wygląda na to, że druga połowa pyta „jak zapytać o uprawnienia”. Czy możesz wyjaśnić tę część?
emragins
Każda transakcja może mieć zasady wykonywania. Każdy użytkownik powinien należeć do jednej lub więcej grup, każda grupa miałaby profil dostępu określający, które transakcje są dozwolone. W czasie wykonywania, przed wykonaniem transakcji, polityka jest sprawdzana względem zagregowanych profili dla wykonującego użytkownika. Oczywiście łatwiej to powiedzieć niż zrobić.
NoChance

Odpowiedzi:

5

W przypadku pierwszego pytania walczyłem z czymś podobnym. Coraz bardziej skłaniam się ku trójfazowemu schematowi autoryzacji:

1) Autoryzacja na poziomie polecenia / zapytania „czy ten użytkownik ma kiedykolwiek uprawnienia do wykonania tego polecenia?” W aplikacji MVC można to prawdopodobnie obsłużyć na poziomie kontrolera, ale wybieram ogólny moduł obsługi wstępnej, który zapyta o magazyn uprawnień na podstawie bieżącego użytkownika i wykonującego polecenia.

2) Autoryzacja w ramach usługi aplikacji „czy ten użytkownik” kiedykolwiek * ma uprawnienia dostępu do tego podmiotu? ”W moim przypadku prawdopodobnie będzie to kontrola niejawna po prostu za pomocą filtrów w repozytorium - w mojej domenie jest to w zasadzie TenantId z nieco większą szczegółowością OrganizationId.

3) Autoryzacja oparta na przejściowych właściwościach twoich podmiotów (takich jak Status) będzie obsługiwana wewnątrz domeny. (Np. „Tylko niektóre osoby mogą modyfikować zamkniętą księgę.”) Zdecydowałem się umieścić to w domenie, ponieważ w dużej mierze opiera się ona na domenie i logice biznesowej i nie bardzo czuję się dobrze, ujawniając to w innych miejscach.

Chciałbym usłyszeć reakcje innych osób na ten pomysł - jeśli chcesz, rozerwij go na strzępy (jeśli to możliwe, podaj kilka alternatyw :))

emragins
źródło
Myślę, że masz pewne ważne punkty dotyczące różnych „warstw” autoryzacji. System, nad którym pracowałem, miał różne typy użytkowników - zarejestrowanych użytkowników i członków personelu. Uprawnienia modułu obsługi poleceń / zapytań wykonały podstawowe sprawdzenie typu użytkownika. Jeśli był to personel, to zawsze się sprawdzał. Jeśli był to zarejestrowany użytkownik, było to dozwolone tylko pod warunkiem spełnienia określonych warunków (np. Uprawnień dla agregatu).
magnus
0

Wdrożyłbym autoryzację jako część Twojej autoryzacji BC, ale wdrożyłbym ją jako filtr akcji w systemie Ledger. W ten sposób można je logicznie oddzielić od siebie - kod księgi głównej nie powinien wywoływać kodu autoryzacji - ale nadal uzyskujesz wysoką wydajność autoryzacji w trakcie procesu dla każdego przychodzącego żądania.

pnschofield
źródło