26/04/2011

Problem z magazynem certyfikatów

Home

Jakiś czas temu pracowałem nad aplikacją WWW, która między innymi zajmowała się wysyłaniem SMS'ów. SMS'y wysyłałem przy pomocy Web Service'u udostępnionego przez operatora. Aby wysłać taki SMS musiałem uwierzytelnić się korzystając z infrastruktury klucza publicznego - dokładniej mówiąc aby wysłać wiadomość musiałem przedstawić certyfikat podpisany przez operatora. Jak to zazwyczaj bywa testy przy użyciu serwera WWW wbudowanego w Visual Studio powiodły się. Niestety po zainstalowaniu aplikacji na serwerze IIS 7.0 aplikacja nie potrafiła znaleźć certyfikatu w magazynie kluczy i próba wysłania SMS'a kończyła się wyjątkiem:

System.Security.Cryptography.CryptographicException: Zestaw kluczy nie istnieje.

Początkowo myślałem, że to błąd konfiguracji czyli, że szukam certyfikatu o błędnej nazwie. Szybko okazało się jednak, że konfiguracja jest poprawna. W tym momencie przypomniałem sobie o kwestii uprawnień. Przecież tożsamość puli aplikacyjnej w jakiej działa aplikacja musi mieć odpowiednie uprawnienia aby uzyskać dostęp do magazynu certyfikatów.

Problem rozwiązałem bardzo szybko zmieniając tożsamość puli aplikacyjnej na System Lokalny (Local System). Rozwiązanie to nie spodobało mi się jednak ponieważ w ten sposób pula aplikacyjna dostała dużo, dużo więcej uprawnień niż tylko prawo dostępu do magazynu certyfikatów. Krótkie poszukiwania naprowadziły mnie na poprawnego rozwiązanie. Otóż należy nadać puli aplikacyjnej (tożsamości puli aplikacyjnej) uprawnienia TYLKO do wybranych certyfikatów w magazynie.

W tym celu uruchamiamy konsolę zarządzania (mmc.exe) i dodajemy do niej przystawkę zarządzania certyfikatami (pisałem o tym w poście Błąd przy dodawaniu przystawki do konsoli zarządzania). Następnie w kategorii Osobisty (My) znajdujemy interesujący nas certyfikat i z menu kontekstowego wybieramy Wszystkie zadania -> Zarządzaj kluczami prywatnymi.... W okienku jakie się pojawi nadajemy użytkownikowi z jakiego korzysta pula prawa do odczytu. Zalecane jest konto wbudowane Usługa sieciowa (Network Service).

17/04/2011

Błąd przy dodawaniu przystawki do konsoli zarządzania

Home

Przystawka certyfikatów (ang. Certificates Snap-in) pozwala na przeglądanie magazynu certyfikatów dla użytkownika, usługi czy też komputera. Aby uruchomić to narzędzie wystarczy w wierszu poleceń lub do okienka Uruchom wpisać polecenie certmgr.msc. Problem polega na tym, że tak uruchomiona przystawka certyfikatów pokaże nam tylko magazyn dla bieżącego użytkownika.

Jeśli chcemy zobaczyć magazyn certyfikatów komputera, tak było w moim przypadku, czeka nas trochę więcej pracy. Zaczynamy od uruchomienia konsoli zarządzania (ang. Microsoft Management Console) wpisując w wierszu poleceń lub do okienka Uruchom komendę mmc.exe. Następnie wybieramy Plik -> Dodaj/Usuń przystawkę. Przystawka certyfikatów znajduje się na początku listy i po jej wybraniu zostaniemy poproszeni o zaznaczenie jakimi certyfikatami chcemy zarządzać: użytkownika, usługi czy komputera. Na koniec klikamy Ok.

Tak to wygląda w teorii, w praktyce po naciśnięciu przycisku Ok konsola zarządzania raportowała błąd (pokazany poniżej) i kończyła pracę, bez względu na to jaka przystawkę wybrałem.
  Nazwa zdarzenia problemu: APPCRASH
  Nazwa aplikacji: mmc.exe
  Wersja aplikacji: 6.0.6002.18005
  Sygnatura czasowa aplikacji: 49e02760
  Nazwa modułu z błędem: StackHash_7ae8
  Wersja modułu z błędem: 6.0.6002.18327
  Sygnatura czasowa modułu z błędem: 4cb74dd3
  Kod wyjątku: c0000374
  Przesunięcie wyjątku: 00000000000aca57
  Wersja systemu operacyjnego: 6.0.6002.2.2.0.256.6
  Identyfikator ustawień regionalnych: 1045
  Dodatkowe informacje 1: 7ae8
  Dodatkowe informacje 2: fab1f7793b8a08e05290bb8ef1ca5c9e
  Dodatkowe informacje 3: 1607
  Dodatkowe informacje 4: 3b4ea5c6cc4724ebe1b8e0ae80fae1cf
Pan Google nie był zbyt pomocny. Radził aby zainstalować SP2 dla Visty, który już mam zainstalowany. Na innej stronie ktoś twierdził, że problem pojawia się jeśli na jednej maszynie zainstalowane są dwie wersje MSSQL i że trzeba jedną z nich odinstalować. Ja mam akurat zainstalowane dwie wersje MSSQL ale nie miałem najmniejszej ochoty usuwać z dysku żadnej z nich. Trzecia osoba radziła aby na komputerze o zbliżonej do problematycznego konfiguracji, na którym mmc.exe działa, wyeksportować klucze rejestru dotyczące konsoli i zaimportować je na komputerze, na którym występuje problem. Ta rada też nie przypadła mi do gustu ponieważ nie miałem takiego komputera pod ręką. Nie wiedziałem także, na ile ta konfiguracja powinna być "zbliżona" aby było dobrze.

Postanowiłem jednak pójść tropem zawartości rejestru i konfliktu pomiędzy różnymi wersjami MSSQL. Na początek zauważyłem, że na liście dostępnych przystawek znajdują się dwie przystawki o takiej samej nazwie SQL Server Configuration Manager. Zapewne dedykowane dla różnych wersji MSSQL. Następnie postanowiłem zajrzeć do rejestru do klucza, który przechowuje listę wszystkich dostępnych przystawek:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MMC\SnapIns\

Jest ich tam kilkadziesiąt i mają niewiele mówiące nazwy np.: d52e5f54-75d9-4a93-91b7-2215ea5cbed2 ale szybko udało mi się znaleźć klucze odpowiadające przystawce SQL Server Configuration Manager. Pomyślałem sobie "raz kozie śmierć", zobaczę co się stanie jak usunę jeden z nich. Oczywiście najpierw wyeksportowałem kopię klucza, a dopiero potem go usunąłem. Okazało się to strzałem w dziesiątkę. Po tej operacji dodanie nowej przystawki w końcu zadziałało. Co ciekawe nie ma znaczenia czy usuniemy klucz przystawki dla MSSQL 2005, czy dla MSSQL 2008.

Reasumując. Jeśli operacja dodania nowej przystawki konsoli zarządzania kończy sie błędem, a masz na komputerze zainstalowane dwie (lub więcej) wersji MSSQL to z dużym prawdopodobieństwem problem spowodowany jest konfliktem pomiędzy przystawkami SQL Server Configuration Manager dla różnych wersji MSSQL. Można go rozwiązać usuwając odpowiedni wpis z rejestru. Nie jest to idealne rozwiązanie, ale z braku laku dobry kit.

14/04/2011

CURRENT_USER , SYSTEM_USER...

Home

W poście Problem z domyślnym schematem wspomniałem, że w celu wyjaśnienia problemu posłużyłem się funkcją CURRENT_USER, która zwraca nazwę bieżącego użytkownika. Czym jednak funkcja ta różni się od funkcji SYSTEM_USER, SESSION_USER czy innych o podobnych nazwach. Sprawa jest prosta. Część z tych funkcji zwraca login, który służy do uwierzytelnienie się względem serwera, a część nazwę użytkownika, która określa co możemy zrobić w ramach poszczególnych baz danych (autoryzacja). Każdy login jest skojarzony (zmapowany) z jednym użytkownikiem dla danej instancji bazy danych. Nie jest to nic skomplikowanego ale można się zgubić w gąszczu nazw. Dlatego ku pamięci, głównie dla siebie, zebrałem te informacje w postaci poniższej tabelki.

CURRENT_USERZwracają nazwę użytkownika
USER_NAME
SESSION_USER
USER

SYSTEM_USERZwracają login
SUSER_SNAME

Więcej szczegółów np.: parametry wywołania, wyjaśnienie skąd wzięło się tyle funkcji robiących to samo, można znaleźć tutaj.

13/04/2011

Problem z domyślnym schematem

Home

Kilka dni temu chciałem zmodyfikowałem domyślny schemat dla jednego z użytkowników, niech nazywa się SomeUser. W tym celu otworzyłem Management Studio, wybrałem interesującą mnie bazę danych i przeszedłem do listy użytkowników. Dalej wybrałem interesującego mnie użytkownika i wyświetliłem dla niego okno właściwość i w polu Default schema: wpisałem nazwę schematu, dla ustalenia uwagi niech nazywa się testSchema.

Żeby być pewnym, że wszystko jest w porządku nawiązałem połączenie z serwerem korzystając z wspomnianego użytkownika i spróbowałem wykonać trywialne zapytanie pobierające dane z tabeli znajdującej się w schemacie testSchema:
select *
from TestTable
Ku swojemu zdziwieniu otrzymałem komunikat o treści Invalid object name 'TestTable'. Sprawdziłem, więc zapytanie zawierające pełną nazwę tabeli (razem ze schematem) i zadziałało:
select *
from testSchema.TestTable
Aby upewnić się, że pracuję w kontekście właściwego użytkownika posłużyłem się poleceniem select current_user, które ponownie ku mojemu zdziwieniu wypisało na ekran dbo zamiast SomeUser. No cóż może się pomyliłem. Ponownie połączyłem się z serwerem upewniając się, że korzystam z dobrego użytkownika ale nic się nie zmieniło.

Problem pomógł mi rozwiązać kolega, który zwrócił uwagę, że SomeUser ma przypisaną rolę sysadmin, a więc w rzeczywistości był widziany jako użytkownik dbo (dla którego domyślny schemat to dbo). Po zabraniu użytkownikowi roli sysadmin, która nie była mu zresztą potrzebna, wszystko zaczęło działać jak trzeba.

01/04/2011

Zabawy z pozycjonowaniem okna w WPF

Home

Czasami zachodzi potrzeba "ręcznego" pozycjonowania okna w aplikacji WPF. Sprawa jest prosta i sprowadza się do odpowiedniego ustawienia właściwości Left oraz Top. Jeśli chcemy aby pozycja okna dziecka zależała od położenia rodzica to możemy odwołać się do rodzica przez właściwość Owner i napisać taki kod jak poniżej. Kod ten powoduje przesunięcie okno dziecka o 50 jednostek w prawo względem lewej krawędzi okna rodzica.
...
myWindow.Left = myWindow.Owner.Left + 50;
...
Nic prostrzego. Jest tu jednak mały haczyk. Jeśli okno rodzica jest zmaksymalizowane to właściwość Left i Top dla rodzica będą miały wartość wskazującą położenie okna przed zmaksymalizowaniem. Poniżej kod, który to uwzględnia:
...
myWindow.Left = (myWindow.Owner.WindowState == WindowState.Maximized ? 0 : myWindow.Owner.Left) + 50;
...
Nic trudnego ale trzeba o tym pamiętać. Sprawa robi się jeszcze ciekawsza kiedy pracujemy z wieloma monitorami. W takim wypadku powyższy kod będzie działał tylko dla głównego monitora. Jeśli okno rodzica będzie zmaksymalizowane i będzie znajdować się na innym monitorze niż główny to wykonanie tego kodu spowoduje wyświetlenie okna dziecka na głównym monitorze! Poniżej rozwiązanie tego problemu:

if (myWindow.Owner.WindowState == WindowState.Maximized)
{
  Screen s = Screen.FromHandle(new WindowInteropHelper(myWindow.Owner).Handle);                      
  myWindow.Left = s.WorkingArea.Left + 50;
}
else
{
  myWindow.Left = myWindow.Owner.Left + 50;
}
                    
Korzystam tutaj ze starej poczciwej klasy Screen, przy pomocy której znajduję monitor, na którym wyświetlane jest okno rodzica. Następnie odczytuję położenie lewej krawędzi tego monitora. Wartość ta będzie różna w zależności od tego, z którym monitorem mamy do czynienia.