Ostatnimi czasy w jednym z projektów pociągnąłem sobie aktualizacje bibliotek przez Nuget’a. A że byłem leniwy to pociągnąłem je jak leci, nie patrząc co aktualizuje o change logu poszczególnych bibliotek nie mówiąc.
Nie róbcie tego w domu!
Jedną z aktualizacji była nowa wersja mechanizmu ASP.Net Identity. Aktualizacja zmieniła encje wykorzystywane przez mechanizm, nie zmieniła jednak bazy danych (być może ze względu na fakt iż nie korzystam z mechanizmu migracji).
Fakt ten można dość łatwo przeoczyć, wystarczy, że podczas testowania ręcznego nie wykona się żadnej operacji związanej z autoryzacją. Ponieważ miałem aktywną sesję sprzed aktualizacji nie musiałem się ani logować, ani rejestrować, wyjątek został więc zgłoszony tuż po wylogowaniu.
Co robić? Jak żyć?
Aby wyłączyć zgłaszanie wyjątku należy w klasie dziedziczącej po IdentityDbContext, np. ApplicationDbContext, ustawić parametr konstruktora throwIfV1Schema na false.
public ApplicationDbContext() : base("CannectionStringName", throwIfV1Schema:false }
Nie należy tego jednak traktować jako rozwiązanie problemu, jest to jednak krok niezbędny do dalszej naprawy. Drugim krokiem jest włączenie mechanizmu migracji (pod warunkiem że nie jest on włączony) wpisując w Package Manager Console (View -> Other Windows -> Package Manager Console)
Powinna wygenerować się migracja zakładająca strukturę bazy dla pierwszej wersji mechanizmu Identity.
Następnie należy wygenerować migrację dostosowującą strukturę bazy do wersji drugiej. Wykonuje się to wpisując w Package Manager Console
Mechanizm migracji sam się zorientuje jakie Właściwości zostały dodane do DTO i wygeneruje w miarę poprawne operacje Up i Down. Następnie należy zastosować migrację wpisująć:
Na koniec należy usunąć ustawienie flagi throwIfV1Schema na false.
A co gdy się nie może albo nie chce użyć mechanizmu migracji?
Rozwiązaniem może być wykonanie powyższych kroków z tą różnicą, że w kroku nanoszącym zmiany na bazę danych uruchomia się w Package Manager Console
Spowoduje to wygenerowanie skryptu, który byłby uruchomiony podczas aktualizacji struktury bazy. Skrypt ten można dostosować do standardów panujących w danej organizacji, a następnie wycofać zmiany przy pomocy Git-a/SVN/CSV/wcześniejszej kopii. Aby oszczędzić innym tej pracy wrzucam skrypt który wygenerowałem w taki sposób.
Subiektywnie
W tej chwili można przeanalizować zmiany w strukturze bazy jakie wnosi druga wersja mechanizmu.
- Zmiana nazwy kolumny User_Id w tabeli AspNetUserClaims na UserId – oceniam ją pozytywnie sprawia że nazewnictwo kolumn jest spójne, jej wadą jest naruszenie wstecznej kompatybilności, więc jeżeli w kodzie występują zapytania SQL, albo baza zawiera procedury składowane używające tej kolumny należy je poprawić.
- Dodanie do tabeli Email, EmailConfirmed, PhoneNumber, PhoneNumberConfirmed, TwoFactorEnabled, LockoutEndDateUtc, LockoutEnabled, AccessFailedCount jak dla mnie wygląda mechanizm Identity próbuje za dużo narzucać programistom i architektom. Istniejąca bądź planowana struktura bazy może zakładać przechowywanie wielu adresów email użytkownika, albo brak komunikacji e-mail, to samo dotyczy numeru telefonu. Zliczanie nieudanych prób logowania jest może dobrą praktyką, ale nie konieczną, może być zastąpione innymi heurystykami.
- Ograniczenie długości kolumn Name w tabeli AspNetRoles do 256 znaków – szansa że ktoś nazwał grupę dłuższą nazwą jest mało-prawdopodobna, a nawet jeżeli to jedyną efektem ubocznym będzie obcięcie długości tej nazwy.
- Ograniczenie długości kolumn UserName w tabeli AspNetusers do 256 znaków – podobnie jak w powyższym przypadku szanse, że ktoś użytkownik miał nazwę-login dłuższy niż 256 znaków jest maloprawdopodobna. Jeżeli jednak z jakiegoś powodu taki się znalazł trzeba go poinformować o zaistniałej sytuacji.
- Nadanie wymagania unikalności na kolumnach wymienianych w poprzednich dwóch akapitów. Zmiana jak dla mnie logiczna, unikalność nazwy użytkownika i tak była walidowana na poziomie aplikacji, natomiast prawdopodobieństwo że nazwa roli nie była w jakimś systemie unikalna jest znikoma.
Usunięcie kolumny Discriminator z Tabeli AspNetUsers – każde usunięcie elemntu API, czy to kolumny czy to metody, czy właściwości jest złe i ten przypadek nie jest wyjątkiem. Zmusza on programistów do aktualizacji zapytań i procedur składowaZmiana nazwy kolumny User_Id w tabeli AspNetUserClaims na UserId – oceniam ją pozytywnie sprawia że nazewnictwo kolumn jest spójne, jej wadą jest naruszenie wstecznej kompatybilności, więc jeżeli w kodzie występują zapytania SQL, albo baza zawiera procedury składowane używające tej kolumny należy je poprawić.Dodanie do tabeli Email, EmailConfirmed, PhoneNumber, PhoneNumberConfirmed, TwoFactorEnabled, LockoutEndDateUtc, LockoutEnabled, AccessFailedCount jak dla mnie wygląda mechanizm Identity próbuje za dużo narzucać programistom i architektom. Istniejąca bądź planowana struktura bazy może zakładać przechowywanie wielu adresów email użytkownika, albo brak komunikacji e-mail, to samo dotyczy numeru telefonu. Zliczanie nieudanych prób logowania jest może dobrą praktyką, ale nie konieczną, może być zastąpione innymi heurystykami.Ograniczenie długości kolumn Name w tabeli AspNetRoles do 256 znaków – szansa że ktoś nazwał grupę dłuższą nazwą jest mało-prawdopodobna, a nawet jeżeli to jedyną efektem ubocznym będzie obcięcie długości tej nazwy.Ograniczenie długości kolumn UserName w tabeli AspNetusers do 256 znaków – podobnie jak w powyższym przypadku szanse, że ktoś użytkownik miał nazwę-login dłuższy niż 256 znaków jest maloprawdopodobna. Jeżeli jednak z jakiegoś powodu taki się znalazł trzeba go poinformować o zaistniałej sytuacji.Nadanie wymagania unikalności na kolumnach wymienianych w poprzednich dwóch akapitów. Zmiana jak dla mnie logiczna, unikalność nazwy użytkownika i tak była walidowana na poziomie aplikacji, natomiast prawdopodobieństwo że nazwa roli nie była w jakimś systemie unikalna jest znikoma.- Usunięcie kolumny Discriminator z Tabeli AspNetUsers – każde usunięcie elemntu API, czy to kolumny czy to metody, czy właściwości jest złe i ten przypadek nie jest wyjątkiem. Zmusza on programistów do aktualizacji zapytań i procedur składowanych z nich korzystających. Kolumna ta była bardziej ogólna niż kolumny związane z blokadą konta.
Podsumowując: o ile same zmiany w wersji drugiej mechanizmu Identity mogą pozytywnie wpłynąć na funkcjonalność i bezpieczeństwo aplikacji to nie podoba mi się wrzucenie ich jako wersji mechanizmu. Według mnie powinny być osobnymi pakietami Nugetowymi, które można zainstalować, jeżeli wystąpi taka potrzeba. Podobnie w przypadku integrowania mechanizmu OAuth2 z pierwszą wersją Identity.
Copy-paste spowodował podwojenie się wypunktowanej listy zmian. A poza tym się zgadzam z powyższym
Dziękuję za uwagę. Poprawka zastosowana 🙂