Strona główna--artykuly--ws-security_epuap

Poradnik integratora czyli, Jak dogadać się z portalem ePUAP przy pomocy protokołu SOAP zabezpieczonego podpisem elektronicznym według specyfikacji ws-security

  1. 1. Wstep
  2. 2. Co to jest ws-security
  3. 3. Co to jest podpis elektroniczny dokumentu XML
  4. 4. Certyfikat wymagany przez ePUAP
  5. 5. Konfiguracja platformy EPUAP i konsoli DRACO na potrzeby Komunikacji przez ws-security
  6. 6. Podpisywanie koperty SOAP według specyfikacji ws-security
  7. 7. Odpytanie ePUAP o uprawnienia, czyli wywołanie usługi decisionQuery
  8. 8. Odpytanie ePUAP o dane zalogowanego użytkownika czyli wywołanie uslugi getUserInfo
  9. 9. Załączniki
  10. 10. Materiały

1. Wstep

W pierwszym artykule z tej serii http://www.extern.pl/artykuly/sso_epuap/ opisałem w jaki sposób wykonać tzw. SSO czyli jak się autentykować w swoim systemie informatycznym za pośrednictwem portalu ePUAP (ePUAP jest więc tzw. IDP "identity provider"). Portal ePUAP potrafi zapewnić większość funkcjonalności do zarządzania użytkownikami co zwalnia nas z konieczności samodzielnego pisania takiej funkcjonalności, przy okazji mamy również z głowy problem zaufanego certyfikatu. Niestety sam proces autentykacji musi być załatwiony przy pomocy protokołu SAML 2.0 co w wykonaniu portalu ePUAP nie jest niestety tak oczywiste jakby się mogło wydawać. Opisałem ten proces w poprzedniej części artykułu i mam nadzieję że zrobiłem to wystarczająco przystępnie. Niestety lektura poprzedniego artykułu jest wielce pożądana przed rozpoczęciem czytania tej części.

Celem procesu autentykacji opisanego w pierwszej części było uzyskanie identyfikatora sesji tzw. TGSID. Identyfikator ten był nam potrzebny aby utrzymywać sesję autentykacyjną z serwerem IDP, jednak to nie koniec zadań tego identyfikatora, jest on również bardzo przydatny a właściwie to niezbędny w dalszej komunikacji z API WebService portalu ePUAP.

Okazuje się że portal ePUAP udostępnia zaufanym (zalogowanym) użytkownikom (tym którzy mają identyfikator TGSID) cały szereg usług WebService których opis znajduje się w dokumentacji portalu (sekcja "integratorzy"). Dla mnie najważniejszymi funkcjonalnościami z tego portalu było odpytanie o uprawnienia zalogowanego użytkownika, odpytanie portalu ePUAP o szczegółowe dane osobowe użytkownika, podpisanie dokumentu XML profilem zaufanym użytkownika.

Niestety okazuje się że o ile początkowa komunikacja z API portalu ePUAP może się odbywać przy pomocy niepodpisanych kopert protokołu SOAP to niestety już dostęp do usług które są najbardziej interesujące wymaga aby sekcja body kopert SOAP była podpisana odpowiednim certyfikatem typu "X.509"

A mówiąc bardziej po naszemu, w kopercie SOAP musi znajdować się nagłówek "header" zawierający podpis elektroniczny elementu "body" koperty SOAP, w którym to elemencie jest już ten konkretny dokument xml który przesyłamy do portalu ePUAP. Dokument ten zwykle jest po prostu XMLowym zapytaniem (nazwa metody i parametry) jakie przekazujemy do portalu ePUAP w nadzieii na właściwą odpowiedź lub akcję.

Dokument ten utworzony ściśle według dokumentu WSDL opisującego wywoływaną przez nas usługę musi być podpisany przy pomocy klucza prywatnego którego odpowiednik publiczny (certyfikat) zna portal ePUAP. Tylko komunikaty podpisane właściwym podpisem są przyjmowane przez portal ePUAP inne są od razu odrzucane i zamiast oczekiwanej treści dostajemy komunikat fault o treści "WSDoAllReceiver: security processing failed " czyli zupełnie zgodny z już poznaną przezemnie ePUAPową tradycją którą można podsumować w trzech wszystko wyjaśniających słowach "Coś nie działa" :-).

2. Co to jest ws-security

Hmm, tak po naszemu to można to przedstawić że jest to załatanie zupełnego braku jakichkolwiek zabezpieczeń w protokole SOAP przez wepchnięcie do sekcji "head" koperty SOAP dodatkowych elementów XML opisujących to co się znajduje w sekcji "body" koperty SOAP. Tak dla zasady muszę wspomnieć że ws-security zajmuje się jakby trzema podstawowymi kwestiami bezpieczeństwa usług WebService czyli:

Przy czym nas w kontekście ePUAPu interesuje głównie podpis (signature). Autentykacja według specyfikacji ws-security jest typu "Basic authentication" co oznacza przekazywanie loginu i hasła otwartym tekstem więc twórcy ePUAPu słusznie doszli do wniosku że lepiej jest do tego użyć protokołu SAML 2.0. Natomiast szyfrowanie jest zapewnione przez protokół niższej warstwy czyli ssl więc nie ma po co męczyć samej warstwy aplikacyjnej tym zadaniem.

Fizycznie podpis przez ws-security wygląda tak że w sekcji header koperty SOAP umieszczany jest element o nazwie <Security> w którym to umieszczane są elementy

3. Co to jest podpis elektroniczny dokumentu XML

Podpisanie dokumentu XML w formacie XAdES to nic innego jak wepchnięcie do oryginalnego dokumentu xml dodatkowego elementu <Signature> w którym to elemencie są zawarte kolejne elementy opisujące jakie sekcje dokumentu zostały podpisane <Reference url=""> (bo nie musimy podpisywać całośći tylko mogą to być podpisywane wybrane elementy dokumentu xml identyfikowane na podstawie ich unikatowych atrybutów ID, jakich przekształceń użyto podczas generacji treści skrótu <Transform Algorithm=""> i jakich algorytmów użyto do generaci skrótów z podpisywanego elementu <DigestMethod Algorithm="">. Wreszcie wartości samego podpisu zawartego w elemencie <SignatureValue>

Sam podpis XAdES nie musi być koniecznie zawarty wewnątrz dokumentu podpisywanego a to jest akurat tylko opcja wymagana przez ws-security używane na portalu ePUAP. Podpis XAdES może być również umieszczony nawet w oddzielnym pliku czy nawet pod innym adresem url

Co nam ten cały podpis elektroniczny koperty SOAP w przypadku portalu ePUAP daje. Przede wszystkm uwierzytelnienie naszego systemu. Ponieważ w trakcie konfiguracji naszej aplikacji (opisane w sekcji "Konfiguracja platformy ePUAP") deklarujemy jakiego certyfikatu będziemy używali do komunikacji przez ws-security dlatego możemy mieć pewność że ePUAP nie pozwoli komuś podszyć się pod nasz system. Dodatkowo otrzymujemy niezaprzeczalność czyli pewność że nikt nie przejmie naszej komunikacji i modyfikując tylko fragmencik przekazu wyśle polecenie przez nas nie autoryzowane, serwer bezpieczeństwa wykryje taki przypadek ponieważ podpis nie będzie poprawnie zweryfikowany z przesłaną zawartością, więc cały komunikat zostanie odrzucony jako błąd bezpieczeństwa.

Cała specyfikacja formatu XAdES jest na tyle rozbudowana że nie pokuszę się tutaj o przedstawianie jej szczegółowo. Zainteresowanych niuansami odsyłam do właściwej dokumentacji, a my po prostu "damy ePUAPowi co ePUAP wymaga" o właśnie.

4. Certyfikat wymagany przez ePUAP

Zgodnie z dokumentacją dostępną na stronie dla integratorów na portalu ePUAP do komunikacji z API portalu jest wymagany certyfikat niekwalifikowany serwerowy z rozszerzeniem "uwierzytelnianie klienta" i niestety dla celów produkcyjnych trzeba sobie taki certyfikat wykupić u jakiegoś znanego wystawcy certyfikatów. Jednak dla celów testowych i związanych ze wstępnymi procesami integracyjnymi na proźbę integratora systemu zewnętrznego (czyli nas) pracownicy portalu ePUAP mogą wygenerować dla nas właściwy certyfikat. Oczywiście trzeba napisać do nich odpowiedniego maila przekazując im zarówno swoją uzasadnioną prośbę jak i dane wymagane do wystawienia takiego certyfikatu (zestaw tych danych jest opisany na portalu ePUAP w podsekcji "Specyfikacja interfejsów WSDL" sekcji "integratorzy"). Niestety kombinatorów (takich jak ja) muszę zmartwić ponieważ certyfikat wygenerowany samodzielnie nie chce działać, prawdopodobnie wymagany jest niestety właściwy podpis na tym certyfikacie czego własne centrum certyfikacyjne postawione przed chwilą na Linuxie nie może zapewnić.

Choć na stronie z dokumentacją ePUAPU sekcja "integratorzy" podsekcja "Specyfikacja interfejsów WSDL" można ściągnąć certyfikaty ePUAPowe opisane jako "Certyfikaty wykorzystane przy zabezpieczeniu wiadomości SOAP systemu ePUAP:" jednak są to tylko certyfikaty z kluczem publicznym używane przez ePUAP w przypadku gdy to nasz system wystawia jakieś usługi które mają być zabezpieczone przez ws-security. Mówiąc inaczej jeśli ePUAP będzie wywoływał jakieś usługi z naszego serwisu (np. w przypadku wysyłania do nas zawartości skrytek typu "PUSH") to wtedy podpisze swoją komunikację przy pomocy kluczy prywatnych powiązanych z tymi certyfikatami a my po stronie metod web naszego serwisu musimy zweryfikować poprawność tych podpisów właśnie dzięki temu że znamy te publiczne certyfikaty wystawiane przez ePUAP.

Jeśli dostaniemy już ten testowy certyfikat, to dostaniemy go prawdopodobnie w zabezpieczonym hasłem pliku zapisanym w formacie PFX (przynajmniej ja tak dostałem). Jest to format używany przez firmę Microsoft (chyba) do składowania certyfikatów na serwerze IIS. Dla nas istotne jest jednak to że w tym pliku jest zawarty zarówno interesująćy nas klucz prywatny jak i klucz publiczny w formie certyfikatu. Do naszych celów potrzebujemy jednak tych elementów w formacie X.509 czyli elementów opisanych przez "-----BEGIN CERTIFICATE-----" ... "-----END CERTIFICATE-----" dla certyfikatu (klucz publiczny i dane odnośnie zabezpieczanej instytucji) i "-----BEGIN RSA PRIVATE KEY-----" ... "-----END RSA PRIVATE KEY-----" dla klucza prywatnego. Na szczęście możemy w prosty sposób przekonwertować format pfx do formatu X.509 (plik "pem", "cer", "crt", "der" jak zwał tak zwał) wystarczy mieć openssl i wywołać komendę openssl pkcs12 -in certificate.pfx -out certificate.cer -nodes i zaraz po wpisaniu hasełka do pliku pfx (powinniśmy je dostać od osoby która generowała nam klucz) jako plik "certyficate.cer" dostaniemy piękny plik tekstowy w którym będą interesujące nas sekcję certyfikatu i klucza prywatnego.

5. Konfiguracja platformy EPUAP i konsoli DRACO na potrzeby Komunikacji przez ws-security

Podobnie jak w przypadku procesu autentykacji opertej na SAML (uzyskanie TGSID) nie uda się nam dogadać z ePUAPem przez SOAP chroniony przez ws-security jeśli wcześniej nie skonfigurujemy konsoli Draco (ePUAPowy serwer bezpieczeństwa) tak aby mogła nastąpić wymiana komunikatów zabezpieczonych właściwym podpisem elektronicznym. A podpis elektroniczny jak wiemy jest ściśle związany z certyfikatem bezpieczeństwa czyli kluczem publicznym, kluczem prywatnym i z danymi o właścicielu tych kluczy podpisanymi przez zaufane centrum certyfikacyjne

Łącząc się z serwerem ePUAP a właściwie to z serwerem o nazwie hetmantest (produkcyjny serwer tego typu po prostu nazywa się hetman) musimy wcześniej wykonać konfigurację tzw. konsoli Draco znajdującej się pod adresem https://konsolahetmantest.epuap.gov.pl/DracoConsole Oczywiscie nie dostaniemy się do niej jeśli nie mamy konta na ePUAP, Konieczne jest również posiadanie uprawnienia "Administrator aplikacji" inaczej nie będziemy mogli właściwie skonfigurować interesujących nas ustawień. Niestety nie dostajemy tego uprawnienia automatyczne i musimy o nie poprosić administratorów ePUAP (trzeba wysłąć grzecznego maila z prośbą o to uprawnienie). Dopiero z tym uprawnieniem mamy możliwość dodania w konsoli Draco bytu który będzie reprezentował nasz system zewnętrzny. Byt ten nazywa się "Aplikacja". Opis jak dodać aplikację znajduje się w poprzednim artykule czyli tu,http://www.extern.pl/artykuly/sso_epuap/ nie będę się więc powtarzał.

Niestety aby komunikować się z ePUAP przy pomocy ws-security nie może być tak jak w przypadku uzyskiwania TGSID że aplikacja tak na prawdę mogła być również skrytką. Niestety ale w tym wypadku musi to być prawdziwa aplikacja do której dodania jest konieczne posiadanie uprawnienia "Administrator aplikacji". Moje próby połączenia się ze skrytką nie udawały się pomimo tego że skrytka jest wyświetlana na liście aplikacji widniejącej w konsoli Draco.

5.1. Dodanie Agenta i powiązanie go z aplikacją

Nowością w stosunku do procesu autentykacji jest to że samo dodanie aplikacji nie wystarcza, ponieważ musimy skonfigurować coś co się nazywa "Agentem". Agent jest to coś co pilnuje bezpieczeństwa naszej komunikacji z ePUAPem, a inaczej mówiąc jest to po prostu jakiś kawałek algorytmu który sprawdza czy mamy prawo komunikować się z ePUAPową aplikacją i czy treść jaką wysyłamy przez protokół SOAP jest podpisana właściwym kluczem prywatnym którego odpowiednik publiczny znajduje się w konfiguracji tego "Agenta". Taki agent następnie może być wykorzystywany przy zabezpieczaniu komunikacji pomiędzy różnymi aplikacjami, jest to dosyc elastyczne rozwiązanie ponieważ jednym agentem możemy zabezpieczyć kilka aplikacji albo np. do różnych aplikacji możemy stworzyć oddzielnych agentów jak kto woli. W każdym bądz razie taki "Agent" musi być przypisany do aplikacji którą ma chronić.

Dodanie Agenta

Dodania "Agenta" dokonujemy w zakładce "Agenci" trzeba wybrać przycisk "Nowy agent" a po wypełnieniu wymaganych pól musimy zapisać dane za pomocą przycisku "Zapisz". Najważnejsze jest aby wypełnić pole przeznaczone na Certyfikat agenta przy pomocy danych pobranych z pliku certyfikatu wygenerowanego nam przez pracownika MSWIA w przypadku serwera testowego, lub przy pomocy certyfikatu kupionego od urzędu certyfikującego. Treścią którą musimy tu wkleić jest treść certyfikatu razem z nagłówkami "-----BEGIN CERTIFICATE-----" ... "-----END CERTIFICATE-----"

Dodanie Agenta do aplikacji

Przypisania "Agenta" do "Aplikacji" dokonuje się w ekranie zarządzania aplikacjami. Oczywiście musimy mieć wcześniej utworzonego tego Agenta i dopiero wtedy będzie on widoczny w małym okienku "Pozostali agenci w systemie " i należy przenieść jego nazwę do okienka "Agenci obsługujący aplikację".

Właściwie od tej chwili można by już zaczynać prowadzić komunikację z ePUAPem, ja jednak chciałem dodatkowo mieć możliwość przypisania do aplikacji różnych uprawnień jakie różni użytkownicy mogą mieć w danej aplikacji zewnętrznej. Aby pobrać z ePUAPu informację o uprawnieniach użytkownika w danej aplikacji z ePUAPu trzeba wywołać usługę o nazwie decisionQuery znajdującą się pod testowym adresem https://hetmantest.epuap.gov.pl/axis2/services/EngineSAMLDecision. Do tej usługi trzeba wysłać zapytanie zgodne z protokołem SAML o nazwie <AuthzDecisionQuery>. Opis wywołania tej usługi będzie w dalszej części tego dokumentu natomiast wcześniej aby uzyskać efekt w postaci poprawnego pobrania zadeklarowanych uprawnień warto dodać te uprawnienia do aplikacji

5.2. Przypisanie uprawnień do użytkownika

Dodanie uprawnienia do aplikacji

Proces dodawania uprawnienia dla użytkownika do aplikacji jest rozbity na kilka etapów. Najpierw trzeba dodać uprawnienie do aplikacji. Robi się to w ekranie zarządzania aplikacjami. Następnie trzeba wybrać właściwą "Grupę aplikacji" potem właściwą aplikację a potem po prawej stronie zakładkę "Uprawnienia". Tam przy pomocy przycisku "Nowe uprawnienie" można wpisać nazwę uprawnienia i jego opis. Z rzeczy istotnych w tym momęcie warto zrozumieć rolę zaznaczenia opcji "Jest widoczny globalnie". Zaznaczenie tej opcji spowoduje że uprawnienie to będzie widoczne dla wszystkich użytkowników sprawdzających to uprawnienie w kontekście tej aplikacji. Właściwie można przyjąć że zaznaczenie tej opcji jest trochę bez sesu bo w końcu w każdej aplikacji raczej chodzi aby właśnie różnicować uprawnienia różnych grup użtkowników, ale jest oczywiste że w pewnych szczególnych sytuacjach taka możliwość może się przydać.

Dodanie roli

Aby jednak w jakis sposób powiązać aplikację, uprawnienie i użytkownika musimy niestety dodać pewien twór pośredni w tym wypadku jest to Rola która mówiąc językiem bazodanowców będzie stanowić swego rodzaju tabelę pośrednią w relacji wiele do wielu. Dodanie roli możemy zrobić w ekrania zarządzania rolami gdzie w zakładce "Konfiguracja" przy pomocy przycisku "Nowa rola" możemy dodać nową rolę. Choć wydaje się teoretycznie że nie musimy dodawać nowej roli ponieważ na liście ról są już jakieś role np. "Rola domyślna" to jednak wydaje mi się że warto zdefiniować sobie nową rolę określającą nam specyfikę danej grupy uprawnień w aplikacji, pozwoli nam to zachować większy porządek.

Powiązanie użytkownika z rolą

Mając już rolę możemy określić jakie konta (użytkownicy) są przypisani do tej roli. Wybieramy więc teraz zakładkę "Konta" gdzie z pola "Wszystkie konta" do pola "Konta przypisane do roli" przenosimy użytkownika który ma być przypisany do tej roli. Poniżej jest również możliwość przypisania do tej roli całych grup użytkowników, co też może być bardzo wygodną opcją przy konieczności zarządzania bardziej rozbudowanymi modelami autoryzacji.

Przypisanie aplikacji do roli

W dalszej kolejności musimy określić jakie aplikacje są powiązane z tą rolą. Przechodzimy więc do zakłądki "Aplikacje". Wybieramy przycisk pojedynczego wyboru (radiobutton) o nazwie "Aplikacje zdefiniowane w organizacji" a następnie wybieramy właściwą grupę organizacji. Teraz z pola "Pozostałe aplikacje" do pola "Aplikacje przydzielone do roli" możemy przesunąć naszą aplikacje wiążąc ją tym samym z tą określoną rolą.

Powiązanie roli i uprawnień

Zostało nam już tylko powiązanie roli z uprawnieniami i aplikacjami. Choć może się wydawać że powinniśmy już mieć powiązanie pomiędzy aplikacją użytkownikiem a uprawnieniem, ponieważ na ekranie zarządzania aplikacjami dodawaliśmy uprawnienie wybierając wcześniej naszą aplikacje a na ekranie zarządzania rolami dodawaliśmy do roli i konto użytkownika jak i aplikacje, tak jednak nie jest. Dopiero zrobienie powiązania pomiędzy kontem aplikacją i uprawnieniem za pomoc?? roli przynosi efekt taki że za pomocą usługi decisionQuery możemy pobrać informacje o posiadaniu przez zalogowanego użytkownika konkretnego uprawnienia.

Idąc za ciosem połączmy teraz rolę, aplikację i uprawnienie. Jeśli ciągle jesteśmy w ekranie zarządzania rolami wybieramy zakładkę "Uprawnienia" następnie wybieramy pole typu "radiobutton" "Aplikacje zdefiniowane w organizacji". Następnie wybieramy właściwą grupę aplikacji, potem wybieramy naszą aplikację w której wcześniej dodawaliśmy te uprawnienia. Dopiero teraz zobaczymy po lewej stronie w polu "Pozostałe uprawnienia" listę możliwych do wyboru uprawnień. i teraz przy pomocy przycisków strzałek możemy przenieść uprawnienia które chcemy przypisać temu użytkownikomi (lub jakieść grupie użytkowników) do pola "Uprawnienia przypisane do roli". W tym momęcie chyba stało się jasne jaką rolę pełniło definiowanie uprawnień w ekranie zarządzania aplikacjami. Chodziło tam o wszystkie uprawnienia jakie tylko są możliwe do przypisania w tej konkretnej aplikacji. A już konkretne przypisanie uprawnienia do użytkownika (za pośrednictwem roli oczywiście) następuje dopiero na zakładce "Uprawnienia" w ekranie zarządzania rolami.

Przyznam się że początkowo tak skomplikowany system przyznawania uprawnięń budził u mnie pewne zdziwienie, ale zrozumiałem że ma to swoje uzasadnienie wyrażające się w dużej elastyczności przyjętego przez ePUAP mechanizmu przydzielania uprawnień. No niestety coś za coś, czyli większa elastyczność kosztem bolącej głowy przy próbie zrozumienia i skonfigurowania tego mechanizmu.

6. Podpisywanie koperty SOAP według specyfikacji ws-security

6.1. Wygląd podpisanej koperty SOAP

Pełną specyfikację standardu ws-security opracowaną przez organizację standaryzacyjną OASIS można znaleść np. tu http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0.pdf Jak zwykle w przypadku takich dokumentów znacznie lepiej się je czyta jeśli już przynajmniej trochę wiemy o co chodzi, niestety znajomość języka Angielskiego również sie przydaje bo z nieznanych mi przyczyn ta specyfikacja nie została opracowana po Polsku :-) Oto przykład wyglądu podstawowej koperty SOAP używanej przy wymianie informacji z portalem ePUAP przez ws-security

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
	<SOAP-ENV:Header>
		<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" SOAP-ENV:mustUnderstand="1">
			<wsse:BinarySecurityToken wsu:Id="BinSecTokenId" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">
				UjEhMB8GCSqGSIb3DQEJARYSZS5oZWJkYUBhcnIuZ292LnBsMR4wHAYDVQQEExVB
				Z2VuY2phIFJ5bmt1IFJvbG5lZ28xHjAcBgNVBCoTFUFnZW5jamEgUnlua3UgUm9s
				...
				bmVnbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAm1b9s+mCbAxaschiFxzZ
				7cPSdJZ9oiiGuSAyFEXx89T4JNklPiwCAo3drFTElqzaLBRUU8ptJ40FnADyw1Fr
				D1g59HjifffLN6WY9Zso8gMCAwEAAaMaMBgwCQYDVR0TBAIwADAL=
			</wsse:BinarySecurityToken>
			<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
				<ds:SignedInfo>
					<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
					<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
					<ds:Reference URI="#SoapBodyId">
						<ds:Transforms>
							<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
						</ds:Transforms>
						<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
						<ds:DigestValue>
							xds4fIZPsgdfg45334sXDr35eewfIA=
						</ds:DigestValue>
					</ds:Reference>
					<ds:Reference URI="#TimeStampId">
						<ds:Transforms>
							<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
						</ds:Transforms>
						<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
						<ds:DigestValue>
							o1sgs4RnJre543tg33Wsaa56sNww55yhY=
						</ds:DigestValue>
					</ds:Reference>
				</ds:SignedInfo>
				<ds:SignatureValue>
					gtrewrt343422f34rtfeagg5r4554yh66g6th54qw34EeTm5HAmRa2NnY8Wrger4QJcmsW8+jxi4suxgt4544ttg5Xrw=
				</ds:SignatureValue>
				<ds:KeyInfo>
					<wsse:SecurityTokenReference>
						<wsse:Reference URI="#BinSecTokenId"/>
					</wsse:SecurityTokenReference>
				</ds:KeyInfo>
			</ds:Signature>
			<wsu:Timestamp wsu:Id="TimeStampId">
				<wsu:Created>
					2011-07-26T20:31:46Z
				</wsu:Created>
				<wsu:Expires>
					2011-07-26T21:31:46Z
				</wsu:Expires>
			</wsu:Timestamp>
		</wsse:Security>
	</SOAP-ENV:Header>
	<SOAP-ENV:Body wsu:Id="SoapBodyId">
		BODY CONTENT
	</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Opiszę w kilku zdaniach o co chodzi. Prawdopodobnie jeśli mieliście już do czynienia z protokołem SOAP bez trudu rozpoznacie podstawowe ramy koperty SOAP. Naszym celem jest podpisanie zawartośći sekcji <body> koperty SOAP. Portal ePUAP oczekuje od nas że dostarczymy mu ten podpis w sekcji <head> w środku znacznika <Security>. W znaczniku <Security> potrzebujemy umieścić trzy główne znaczniki czyli.

Elementy <BinarySecurityToken> i <Timestamp> musimy zrobić niestety sami na piechotę modyfikując kopertę SOAP wygenerowaną dla nas przez bibliotekę Nusoap. Natomiast (i to jest bardzo dobra dla nas nowina) element <Signature> razem z identyfikatorami do body i timestamp może dla nas wygenerować biblioteka Xmlseclibs. Niestety biblioteka ta nie potrafi zrobić dla nas elementu <SecurityTokenReference> tak więc ten element podobnie jak elementy <BinarySecurityToken> i <Timestamp> musimy zrobic sami.

Warty odnotowania w tym miejscu jest fakt że element <BinarySecurityToken>, który powinien zawierać certyfikat z kluczem plublicznym musi być wypełniony samą wartością binarną klucza czyli albo trzeba programistycznie albo ręcznie w edytorze (jeśli certyfikat podajemy jako string) wyciąć z certyfikatu nagłówek "-----BEGIN CERTIFICATE-----" i stopkę "-----END CERTIFICATE-----". Natomiast wyciętej już treści nie należy dodaktkowo poddawać kodowaniu base64 ponieważ ciało certyfikatu już jest tym sposobem zakodowane.

6.2. Implementacja w języku PHP podpisania koperty SOAP według standardu ws-security.

Nie bardzo wiem jak się zabrać za opisywanie funkcji do podpisywania koperty SOAP. Jest w niej połączenie kilku technik. Biblioteka Nusoap wygeneruje nam podstawową kopertę SOAP dopisując przy pomocy metody serializeEnvelope() do elementu head interesujace nas elementy <BinarySecurityToken> i <Timestamp> które sami tworzymy wypełniając je właściwymi wartościami. Zawartość którą mamy przekazać podpisaną do portalu ePUAP przekazujemy metodzie serializeEnvelope() jako pierwszy parametr $requestContent. Metoda serializeEnvelope() dokona połączenia przekazywanych treści, czyli to co ma się znaleść w sekcji <body> i <head> i zwróci nam ciąg znakowy reprezentujący przygotowaną kopertę SOAP.

Korzystając z metody serializeEnvelope() musimy pamiętać że musimy ustawić właściwy styl koperty i styl parametrów. Ponieważ niestety niektóre usługi web oferowane przez portal ePUAP wymagają formatowania sekcji <body> według zasady "document\literal" a niektóre według "rpc\literal" dlatego nasza metoda callSecRequest mająca być dosyć uniwersalną warstwą opakowująca w ws-security to co ePUAP chce uzyskać, musi w formie parametrów "style" i "use" uzyskiwać właściwe wartości czy dla "$style" czyli "document" lub "rpc" i dla "$use" czyli "encoded" lub "literal". Choć w przypadku ePUAPu nie znalazłem przypadku użycia parametrów formatowanych jako "encoded"

Cała ta pracowicie przygotowana koperta SOAP nie jest jeszcze kompletna tak więc aby nie grzebać się bezpośrednio w napisach, wczytamy nasz dokument przy pomocy metody $doc->loadXML($soapEnvelope); do obiektu DOMDocument reprezentującego nam strukturę XML w postaci drzewa na którym przy pomocy standardowych metod obsługi drzewa DOM będziemy dokonywali dopisywania interesujących nas elementów i atrybutów.

Natomiast biblioteka Xmlseclibs dopisze nam do tej koperty element <Signature> który musi się znaleść pomiędzy elementami <BinarySecurityToken> i <Timestamp>. Dopisanie to wykona nam metoda $objDSig->insertSignature($securityNode,$timestampNode); której jako parametry przekazujemy dwa elementy drzewa DOM. Pierwszy z parametrów określa w jakim elemencie ma się znaleść element <Security>. Natomiast drugi parametr metody insertSignature() określa przed jakim elementem (w tym wypadku chodzi o Timestamp) element Signature ma być umieszczony (kolejność umieszczenia jest ważna).

Dalej przy pomocy obiektu DOMXPath i jego metody query zajmujemy się znalezieniem interesujących nas elementów do których musimy dopisać właściwe identyfikatory w atrybutach URI i Id. Tworzymy również za pomocą metody createElementNS() element SecurityTokenReference i wypełniamy atrybut URI własciwą wartością do atrybutu Id elementu BinarySecurityToken

O ile referencję SecurityTokenReference do elementu BinarySecurityToken musimy sami utworzyć, to na szczęście w przypadku referencji do elementu "body" i "timestamp" możemy je utworzyć przy pomocy biblioteki xmlseclibs. Pomoże nam w tym metoda $objDSig->addReference($bodyNode, XMLSecurityDSig::SHA1,NULL,array('prefix'=>'wsu','prefix_ns'=>$wsuNamespace)); i $objDSig->addReference($timestampNode, XMLSecurityDSig::SHA1,NULL,array('prefix'=>'wsu','prefix_ns'=>$wsuNamespace)); które zrobią to za nas. Musimy tylko wskazać tym metodom do jakiego elementu ma się odnosić referencja. Warta zauważenia w tym przypadku jest również konieczność jawnego zmuszenia tych metod za pomocą tablicy z elementami "prefix" i "prefix_ns" aby atrybut "Id" wstawiany do elementu na który wskazuje referencja był z właściwej przestrzeni nazw "wsu" (bez tego drobnego szczegółu nie dogadamy się przez ws-security z ePUAP, a mówiąc bardziej dokładnie z niektórymi metodami się dogadamy a z niektórymi nie. Mówiąc jeszcze bardziej dokładniej, prefixu przestrzeni nazw "wsu" w atrybucie Id wymagają usługi odpowiedzialne za podpisywanie dokumentów xml profilem zaufanym, ale ponieważ pozostałym usługom ten prefix nie przeszkadza więc do wszystkich usług można używać tej samej wersji metody)

	function callSecRequest($serviceUrl,$serviceMethod,$requestContent,$pubKey,$privKey,&$msg,$style='document',$use='literal')
	{
		require_once(xmlseclibs.php');
		require_once(Nusoap.php');
		$client=new nusoap_client($serviceUrl);
		$client->soap_defencoding = 'UTF-8';
		$client->decode_utf8 = true;
		$client->http_encoding = 'UTF-8';
		$client->xml_encoding = 'UTF-8';
		//$client->setHTTPProxy($proxyServer,$proxyPort,$proxyUser,$proxyPassword');  //proszę użyć w przypadku konieczności łączenia się z internetem przez serwer proxy
		$client->setCurlOption(CURLOPT_SSLVERSION, 3);
		$securityStart="<wsse:Security \n"
						."xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"\n"
						."xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"\n"
						."SOAP-ENV:mustUnderstand=\"1\">\n";
		$securityEnd="</wsse:Security>\n";
		$BinarySecurityToken="<wsse:BinarySecurityToken \n"
							."wsu:Id=\"binarytoken\" \n"
							."ValueType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3\" \n"
							."EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">\n"
							.implode("\n",array_slice(explode("\n",$pubKey),1,-1))  //a tu wycinamy napisy BEGIN CERTYFICATE i END CERTYFICATE ponieważ EPUAP oczekuje BinarySecurityToken jako base64 bez tych nagłówków
							."</wsse:BinarySecurityToken>\n";	
		$timestamp = 	"<wsu:Timestamp>"
						."<wsu:Created>".getTimestamp()."</wsu:Created>"
						."<wsu:Expires>".getTimestamp(60*60)."</wsu:Expires>"  //przesuniecie o godzinę
						."</wsu:Timestamp>";	
						
		$sheaders = $securityStart.$BinarySecurityToken.$timestamp.$securityEnd; //komponujemy dodatek "security" który zostanie umieszczony w nagłówku SOAP:Header
		
		$soapEnvelope = $client->serializeEnvelope($requestContent, $sheaders, array(), $style, $use);  //tworzymy całą kopertę SOAP z zawartym tam nagłówkiem (jeszcze nie podpisanym i nie są przydzielone również referencje
		//w tym momecie mamy już wstępnie utworzoną kopertę SOAP z zawartością SOAP:BODY jednak nie jest jeszcze w pełni utworzony nagłówek security ani nie jest tam wstawiony podpis które będzie wstawiony tam w elemencie "signature" brakuje również referecji wskazujących które elementy koperty SOAp są podpisane
		//dlatego teraz zajmiemy się dalszym wstawianiem odpowiednich danych do elementu security.
		$doc = new DOMDocument();
		$doc->loadXML($soapEnvelope); //całą kopertę soap wczytujemy do drzewa dom i na tym drzewie będziemy już rzeźbić
		//znajdujemy element body aby dodac do niego referencje poniewaz tylko body bedziemy podpisywali
		$xp = new DOMXPath($doc);
		$wsseNamespace = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
		$wsuNamespace = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
		//musimy zarejestrować właściwe namespaces z prefixami aby zapytania xpath umiały znaleść właściwe węzły 
		$xp->registerNamespace('SOAP-ENV', 'http://schemas.xmlsoap.org/soap/envelope/'); //namespace sekcji body
		$xp->registerNamespace('wsse',$wsseNamespace); //namespace node security
		$xp->registerNamespace('wsu',$wsuNamespace);
		$xp->registerNamespace('ds',XMLSecurityDSig::XMLDSIGNS);

		$bodyNode = $xp->query('//SOAP-ENV:Body')->item(0);
		if($bodyNode)									//znaleźliśmy element soap:body mozemy przypisać mu id i dodać do niego referencje w elemencie "Signature"->"SignedInfo"->"Reference[URI]" 
		{
			//echo "jest BODY";
			$objDSig = new XMLSecurityDSig();			//tworzymy obiekt za pomocą którego będziemy podpisywali kopertę SOAP
			$objDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
			$objDSig->addReference($bodyNode, XMLSecurityDSig::SHA1,NULL,array('prefix'=>'wsu','prefix_ns'=>$wsuNamespace));  //dodajemy referencje do body możemy tu użyć gotowej funkcji z biblioteki "xmlseclibs"

			//teraz dodamy referencję do elementu Timestamp opisującego moment czasowy podpisu
			$timestampNode = $xp->query('//wsu:Timestamp')->item(0);
			if($timestampNode)
				$objDSig->addReference($timestampNode, XMLSecurityDSig::SHA1,NULL,array('prefix'=>'wsu','prefix_ns'=>$wsuNamespace));
			$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'private'));
			$objKey->loadKey($privKey);   //tworzymy obiekt klucza prywatnego, w tym wypadku odczytujemy go z konfiguracji
			$objDSig->sign($objKey);							//podpisujemy kopertę SOAP kluczem prywatnym
			//$objDSig->add509Cert($pub_key);\				
			//znajdujemy element do którego wstawimy sekcję podpisu w tym wypadku jest to Security
			$securityNode = $xp->query('//wsse:Security')->item(0);							
			if($securityNode)
			{
				//echo "jest Security node";
				//teraz musimy dodać właśnie wygenerowany podpis (na podstawie referecji do zewnętrzego obiektu) do całośći koperty SOAP
				$timestampNode = $xp->query('//wsu:Timestamp')->item(0);
				if($timestampNode)
					$objDSig->insertSignature($securityNode,$timestampNode);  //wstawiamy podpis do węzła "Security" ale ma być umieszczony przed węzłem "TimeStamp" (to ważne aby był przed nim)
					//$objDSig->appendSignature($securityNode);
					//Teraz ustawimy referencje do BinarySecurityToken niestety ponieważ funkcja addRefInternal jest prywatna nie możemy jej stąd wywołać i musimy dodanie atrybutu reference zrobić na piechotę
				$binarySecurityTokenNode = $xp->query('//wsse:BinarySecurityToken')->item(0);
				if($binarySecurityTokenNode)
				{
					$uri = XMLSecurityDSig::generate_GUID();
					//echo "jest BinarySecurityToken";
					$binarySecurityTokenNode->setAttributeNS($wsuNamespace,'Id', $uri); //dodajemy atrybut Id do BinarySecurityToken aby potem użyć tego samego identyfikatora przy dodawaniu referencji w elemencie securityTokenReference
					$keyInfoNode = $xp->query('//ds:KeyInfo')->item(0);
					if(!$keyInfoNode)
					{
						$signatureNode = $xp->query('//ds:Signature')->item(0);	
						$keyInfoNode = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS,'ds:KeyInfo');
						if($signatureNode)
							$signatureNode->appendChild($keyInfoNode);
					}
					$securityTokenReferenceNode = $xp->query('//wsse:SecurityTokenReference')->item(0);
					if($keyInfoNode && ! $securityTokenReferenceNode)
					{
						$securityTokenReferenceNode = $doc->createElementNS($wsseNamespace, 'wsse:SecurityTokenReference');
						$referenceNode = $doc->createElementNS($wsseNamespace,'wsse:Reference');
						$referenceNode->setAttribute("URI", '#'.$uri);         //dodajemy teraz do referencji ten sam identyfikator co jest w BinarySecurityToken z elementu Security który właściwie to nie wchodzi do podpisu
						$securityTokenReferenceNode->appendChild($referenceNode);
						$keyInfoNode->appendChild($securityTokenReferenceNode);
						//$objDSig->addRefInternal($securityTokenReferenceNode,$binarySecurityTokenNode, XMLSecurityDSig::SHA1);
					}
				}
				$soapEnvelope = $doc->saveXML();
			}//if($securityNode)
			else
			{
				$msg .="Nie odnaleziono elementu wsse:Security";	
			}
		}//if($bodyNode)
		else
		{
			$msg .="Nie odnaleziono elementu SOAP-ENV:Body";
		}				
		//echo $soapEnvelope;	
		$result2  = $client->send($soapEnvelope, $serviceMethod);   //wysyłamy kopertę SOAP
		if($client->fault)
		{
			$msg .= "ERROR przy wysyłaniu metody ".$serviceMethod." :".$client->fault." ".var_export($result2,true);
			//echo  'Error: '.$client->fault;
			//print_r($result2);	
			//echo '<h2>Request</h2>';
			//echo '<pre>' . htmlspecialchars($client->request, ENT_QUOTES) . '</pre>';
			//echo '<h2>Response</h2>';
			//echo '<pre>' . htmlspecialchars($client->response, ENT_QUOTES) . '</pre>';
			// Display the debug messages
			//echo '<h2>Debug</h2>';
			//echo '<pre>' . htmlspecialchars($client->debug_str, ENT_QUOTES) . '</pre>';		
		}
		else
		{
			if ($client->getError())
			{		
				$msg .= "ERROR przy wysyłaniu metody ".$serviceMethod." :".$client->getError()." ".var_export($result2,true);	
				//echo 'Error2: '.$client->getError();
				//print_r($result2);
				//echo '<pre>' . htmlspecialchars($client->request, ENT_QUOTES) . '</pre>';
			}
			else   //udało się więc zwracamy wynik jaki dostaliśmy od klienta
			{				
				//echo '<h2>Result</h2><pre>';
				//print_r($result2);				
				//echo '</pre>';
				//echo '<h2>Request</h2><pre>';
				//echo '<pre>' . htmlspecialchars($client->request, ENT_QUOTES) . '</pre>';
				//echo '<h2>Response</h2><pre>';
				//echo '<pre>' . htmlspecialchars($client->response, ENT_QUOTES) . '</pre>';
				//print_r($result2);
				if(isset($result2['faultcode']))
				{
					$msg .=  'faultcode='.$result2['faultcode'].' faultstring='.$result2['faultstring'].' detail='.$result2['detail'];
					return false;
				}
				return $result2;				
			}
		}
		return false;						
	}//callSecRequest

Gdy mamy już przygotowaną właściwą strukturę koperty SOAP zapisujemy (serializujemy) ją do zmiennej przy pomocy metody $soapEnvelope = $doc->saveXML();, właściwie tylko po to aby od razu wysłać naszą pracowicie przygotowaną kopertę SOAP przy pomocy metody $client->send($soapEnvelope, $serviceMethod); na serwer ePUAP. Jeśli nie otrzymamy błędu zgłoszonego bądź przez serwer bądz przez obiekt klienta, to wynikiem działania metody$client->send() będzie tablica (array) zawierająca treść zależną od tego jaki zestaw danych zwraca nam wywoływana przez nas usługa web.

Muszę się w tym miejscu poskarżyć że w trakcie mojej nierównej walki z ePUAPem spotkała mnie przykra, przykrość :-) mianowicie nagle bez przyczyny moje metody przestały się dogadywać z serwerem zwracając dziwny błąd protokołu ssl. Po naprawde trwającej cały dzień walce z kodem doszedłem do wniosku że winę za to ponosi niezgodność wersji protokołu SSL używanej przez serwer i mojego klienta "CURL" wykorzystywanego z kolei przez bibliotekę "Nusoap". CURL powinien automatycznie rozpoznać wersję SSLa używana przez serwer i już potem zgodnie z tą wersją prowadzić dalszą komunikację, ale CURL odmawiał współpracy i ciągle próbował dogadać się z serwerem rozmawiającym w SSLv3 przy pomocy SSLv2. Sytuację uratowało po prostu ustawienie CURLowi przy pomocy metody $client->setCurlOption(CURLOPT_SSLVERSION, 3); wersji SSLv3 na sztywno. Dopiero później dowiedziałem się właściwie przypadkiem co się stało. Otóż okazało się że po prostu ePUAP w pewnym momęcie przeszedł na SSLv3 o czym nie wiedziałem. Dla administratorów ePUAP było to przestawienie jednego konfiguracyjnego parametru, a dla mnie to był cały dzień rwania włosów z głowy "o matko co ja powiem dyrektorowi przecież już się pochwaliłem że udało mi się w końcu z nimi połączyć :-\"

7. Odpytanie ePUAP o uprawnienia, czyli wywołanie usługi decisionQuery

O ile sama możliwość podpisania koperty SOAP przez ws-security jest sama w sobie nikomu nie potrzebna to już wywołanie dzięki temu usługi na serwerze ePUAP owszem (a przynajmniej mam nadzieję że komuś się to przyda) Na pierwszy ogień weźmiemy usługę decisionQuery udostępnianą przez ePUAP. Usługa ta umożliwia odpytanie się ePUAPu o konkretne uprawnienie konkretnego użytkownika do konkretnej aplikacji. (opis tego jak takie uprawnienie ustawić przy pomocy interfejsu ePUAP znajduje się w punkcie 5.2.)

Usługa decisionQuery jest w przypadku ePUAP o tyle nietypowa że wymaga aby w sekcji "body" koperty "SOAP" umieścić treść zgodną ze specyfikacją protokołu SAML podczas gdy reszta usług z którymi w ePUAP miałem do czynienia wymaga jednak dużo prostrzych struktur (na szczęście OASIS się tym nie zajmował).

	/**
	 * 
	 * zwraca informację o tym czy dane uprawnienie jest dozwolone czy zabronione
	 * @param string $urlEngineSAMLDecision adres url usługi
	 * @param string $rightName nazwa uprawnienia na ePuap
	 * @param string $nameID nazwa użytkownika (login z ePUAP
	 * @param string $pubKey klucz publiczny
	 * @param string $privKey klucz prywatny
	 * @param unknown_type $msg zmienna na komunikat błędu
	 * @author Extern
	 * @return false lub true
	 */
	function getDecisionQuery($urlEngineSAMLDecision,$rightName,$nameID,$pubKey,$privKey,&$msg)
	{		
		$authzDecisionQuery="<samlp:AuthzDecisionQuery ID=\"".getUniqueID(20)."\" "
							."IssueInstant=\"".getTimestamp()."\" "
							."Resource=\"".$CI->config->item('appID')."\" Version=\"2.0\" "
							."xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">"
							."<saml:Subject xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">"
							."<saml:NameID>".$nameID."</saml:NameID>"
							."</saml:Subject>"
							."<saml:Action Namespace=\"hetman.epuap.gov.pl\" "
							."xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">".$rightName."</saml:Action>"
							."<saml:Evidence xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">"
							."<saml:AssertionIDRef>"."_".$_COOKIE['TGSID']."</saml:AssertionIDRef>"
							."</saml:Evidence>"
							."</samlp:AuthzDecisionQuery>";
		
		$respond = callSecRequest($urlEngineSAMLDecision,'decisionQuery',$authzDecisionQuery,$pubKey,$privKey,$msg);
		if($respond)
			return  $respond['Assertion']['AuthzDecisionStatement']['!Decision'] == 'Permit';
		return false;		
	}

Główne zadanie wygenerowania i podpisania koperty SOAP wykona dla nas opisana wyżej funkcja callSecRequest Jednak wcześniej musimy przygotować dla niej to co ma się znaleść w sekcji body koperty SOAP. Tą zawartością jest dokument XML stworzony według specyfikacji SAML typu <samlp:AuthzDecisionQuery> jako atrybut Resource musimy ustawić identyfikator aplikacji do której uprawnienie chcemy sprawdzić (niestety możemy się odpytać tylko o jedno uprawnienie na raz). jako element <saml:NameID> ustawiamy identyfikator (login) uzytkownika o którego uprawnienia się odpytujemy (e tam i tak liczy się tylko TGSID). Jako element <saml:Action> musimy podać nazwę uprawnienia o które się odpytujemy powinna to być ta sama nazwa której ustawienie opisałem w punkcie 5.2.. Kolejnym i w dodatku bardzo ważnym elementem do ustawienia jest <saml:AssertionIDRef> w którym musi się znalesć wartość identyfikatora TGSID którego pozyskanie jest opisane w pierwszym artykule z tej serii http://www.extern.pl/artykuly/sso_epuap/. Ważną zauważenia w tym przypadku jest fakt że tym razem identyfikator musi być poprzedzony znakiem podkreślenie "_", który to znak wcześniej według zaleceń pomocy technicznej dla integratorów zalecano mi usunąć przed utworzeniem ciastka z zawartością TGSID. Starałem się nie wnikać za bardzo w pokrętną logikę jaką kierowali się autorzy ePUAPu odnośnie tego prefixu przed TGSIDem tylko zachowałem się jak typowy konformista akceptujący i dostosowujący się do rzeczywistości na którą nie ma wpływu i po prostu jeśli wcześniej przed zapisaniem ciestka TGSID wyciołem z pierwszej pozycji znak podkreślenia to teraz po prostu go doklejam i tyle. A może po prostu nie należało go wcześniej usuwać, nie ważne nie chciało mi się już zmieniać kodu który działał.

Po wywołaniu funkcji callSecRequest dostaniemy jakąś odpowiedz od ePUAPu i właściwie interesuje nas w tym wypadku tylko to czy w zwróconej tablicy w kluczu ['Assertion']['AuthzDecisionStatement']['!Decision'] znajduje się wartość "Permit", jeśli tak to znaczy że użytkownik z sesji opisywanej przez TGSID ma to uprawnienie o które się pytaliśmy.

8. Odpytanie ePUAP o dane zalogowanego użytkownika czyli wywołanie uslugi getUserInfo

Kolejną usługą którą przedstawie jest możliwość odpytania portalu ePUAP o dane zalogowanego użytkownika. W odróżnieniu od metody decisionQuery metoda getUserInfo wymaga dużo prostrzej struktury służącej do wywołania usługi niż poprzednia metoda. Ponieważ w tym wypadku projektanci ePUAP nie musieli trzymać się żadnych standardów i wymyśli własny format wywołania. Format wywołania metody getUserInfo opisany jest przez dokument WSDL znajdujący się pod adresem https://hetmantest.epuap.gov.pl/axis2/services/AuthorizationWS?wsdl i choć jak z dokumentu WSDL wynika styl dokumentu jaki ma być wstawiony do elementu "soap:body" jest typu "document" to po przyjrzeniu się dokładniej temu czego oczekuje metoda webService wynika że dokument xml przekazany jako parametr do złudzenia przypomina wywołanie znane ze stylu "rpc" i opisane jest przez sekcję

<xs:element name="getUserInfo">
	<xs:complexType>
		<xs:sequence>
			<xs:element minOccurs="0" name="tgsId" nillable="true" type="xs:string"/>
			<xs:element minOccurs="0" name="appId" nillable="true" type="xs:string"/>
		</xs:sequence>
	</xs:complexType>
</xs:element>

Oczywiście to nic złego w tym że twórcy ePUAP użyli takiej metodologi powiem więcej że w mojej opini to nawet bardzo dobrze, jest porządek i wszystko jest cacy a jeśli by w przyszłości chcieli użyć stylu "rpc" to mają wszystko gotowe.

A o to wywołąnie usługi getuserinfo na serwerze

	/**
	 * 
	 * medota zwraca dane o użytkowniku
	 * @param string $urlAuthorizationWS adres url usługi
	 * @param string $appID identyfikator aplikacji w ePUAP
	 * @param unknown_type $msg zmienna na komunikat błędu
	 * @author Extern
	 * @return string ze strukturą xml zawierajacą dane użytkownika lub false w przypadku porażki
	 */
	function getUserInfo($urlAuthorizationWS,$appID,&$msg)
	{
		if(isset($_COOKIE['TGSID']))
		{
			$getUserInfoRequest = "<ns:getUserInfo xmlns:ns=\"http://ejb.draco.comarch.com\">"
							."<ns:tgsId>".$_COOKIE['TGSID']."</ns:tgsId>"
							."<ns:appId>".$appID."</ns:appId>"
							."</ns:getUserInfo>";							
			$respond = callSecRequest($urlAuthorizationWS,'getUserInfo',$getUserInfoRequest,$msg);			
			return $respond;							
		}
		return false;		
	}

Jak widać powyżej znowu wykorzystujemy uniwersalność funkcji callSecRequest która zapewni nam podpisanie koperty SOAP przez ws-security. Właściwie cała innowacyjność tego kodu w stosunku do wcześniejszego przykładu dotyczy tylko trzeciego parametru czyli tego co co zostanie wstawione do sekcji "soap:body" koperty SOAP. Jak widać w tym wypadku zawartość jest sporo prostrza i składa się jakby (bo jest to tak na prawdę jeden parametr) z dwóch parametrów "tgsId" przeznaczonego na identyfikator sesji TGSID, natomiast drugim parametrem (elementem w dokumencie XML) jest "appId" czyli identyfikator aplikacji który zapisaliśmy konfigurując aplikację w konsoli DRACO.

Jeśli wszystko jest poprawnie ePUAP zwróci nam dokument xml zawierający informacje o użytkowniku, takie jak imie, nazwisko, identyfikator (login), identyfikator podmiotu do jakiego przypisany jest użytkownik, oraz adres email. Niestety metoda ta nie zwróci nam identyfikatora PESEL zalogowanego użytkownika, który mógłby być dla nas bardzo przydatny. Dzieje się tak ponieważ identyfikator PESEL jest daną osobową (data urodzenia, płeć) dlatego ePUAP nie chce ryzykować i nie zwraca nam tego bardzo przydatnego identyfikatora. Niestety ten drobny szczegół dosyć znacznie ogranicza możliwość poważnego zastosowania tego całego ePUAPowego rozwiązania, ponieważ bez jednoznacznego identyfikatora jakakolwiek próba powiązania zalogowanego użytkownika z danymi o użytkowniku króre możemy mieć w naszych bazach jest skazana na wielką niepewność bo przykładowo Adamów Kowalskich prawdopodobnie jest w Polsce bardzo wielu.

Próbowałem nawet dyskutować na ten temat z ludzmi z pomocy technicznej ePUAPu argumentując że jest to bez sensu bo przecież użytkownik logujący się do naszej aplikacji przy pomocy ePUAPu tak na prawdę loguje się sam do swojego konta więc dlaczego sam nie może pobrać dla siebie swojego identyfikatora PESEL. Jednak ePUAPowcy nie dali się przekonać argumentując że nie bo nie. Jak się nad tym głębiej zastanowić to rzeczywiście istnieje pewne ryzyko związane z tym że jakiś nie uczciwy lub nadgorliwy twórca jakiegoś systemu zewnętrznego w stosunku do ePUAPU mógłby zbierać PESELe logujących się przy pomocy jego aplikacji do ePUAPu osób. Zarządzający portalem ePUAP nie chcą ponosić tego ryzyka więc profilaktycznie nie dają tej możliwości i kropka.

Usługa getUserInfo jest przykładem dosyć popularnego sposobu wywoływania większości usług na portalu ePUAP, jednak oczywiście różne usługi mają zarówno różne adresu URL pod którymi się znajdują jak i różne parametry wywołania których wymagają. Tak więc niestety od analizy dokumentów WSDL nie uciekniemy. Opis usług, ich adresów URL znajduje się na portalu ePUAP pod adresem Pomoc-Integratorzy-Specyfikacja WSDL

8. Załączniki

Kody źródłowe

9. Materiały



Dodaj komentarz:
Tak
Nie

Autor:mati
Data: 2020-05-21 9:43
Treść:

Autor:mati
Data: 2020-05-21 9:43
Treść:

Autor:mati
Data: 2020-05-21 9:43
Treść:

Autor:mati
Data: 2020-05-21 9:41
Treść:alert('plox daj instrukcje')

Autor:slavo
Data: 2020-03-20 14:35
Treść:Oj bardzo by się przydał opis integracji z ePUAP2 ... to opracowanie jest bardzo dobre ale już niestety nieaktualne :(

Autor:PiotrZ
Data: 2019-01-13 16:12
Treść:Swietne opracowanie.

Autor:Uwagi_jesz
Data: 2018-04-16 15:57
Treść:Przede wszystkim - dzięki za opis, bardzo przydatny. A teraz uwagi: 1) Masz taki kawałek tekstu: " Do przekazywania danych o certyfikacie zawierającym klucz publiczny który to klucz zostanie użyty do podpisania całej koperty " Uwagi: - podpisuje się samo Body, nie całą kopertę

Autor:Uwagi
Data: 2018-04-16 15:55
Treść:Przede wszystkim - dzięki za opis, bardzo przydatny. A teraz uwagi: 1) Masz taki kawałek tekstu " Do przekazywania danych o certyfikacie zawierającym klucz publiczny który to klucz zostanie użyty do podpisania całej koperty " Uwagi: - podpisuje się samo Body, nie całą kopertę - podpisujący w tym celu (podpisania) używa klucza prywatnego, a strona odbierająca weryfikuje podpis używając certyfikatu (dok

Autor:purofuego
Data: 2017-10-06 19:49
Treść:Podmiot, który zamierza wykorzystywać usługi dostępne w Profilu Zaufanym, powinien posiadać uprawnienia podmiotu publicznego. http://mc.bip.gov.pl/departament-utrzymania-i-rozwoju-systemow/integracja-systemow-z-profilem-zaufanym.html

Autor:cedo
Data: 2017-10-06 9:27
Treść:Chciałbym wiedzieć czy funkcjonalność podpisywania dokumentów XML (pytam w kontekście plików JPK) jest możliwa dla wszystkich (każdej firmy w Polsce) czy tylko dla urzędów ? Gdzie znajdę wskazówkę na ten temat ?

Autor:Maciek
Data: 2017-07-06 13:03
Treść:Witam, staram się zintegrować z nową testową platforma int.epuap.gov.pl i int.pz.gov.pl, udało mi się już wydębić od obsługi ePUAP certyfikat, do WS-security którym my podpisujemy żądania, niestety nie mogę wyciągnąć certyfikatów do truststore. Na platformie produkcyjnej są w dziale POMOC, niestety ne testowym ich nie ma. Ktoś może wie gdzie to można dostać?

Autor:maciek
Data: 2016-09-06 14:04
Treść:Zmiany w stosunku do powyższego artykułu w związku z wdrożeniem wydzielonego PZ (wrzesień 2016) - http://cu-devel.warmia.mazury.pl/wydzielonyPZ.txt

Autor:Damian Bar
Data: 2016-06-22 14:49
Treść:W razie pytań proszę o kontakt www.smart-net.eu

Autor:Damian Bar
Data: 2016-06-22 14:48
Treść:Witam Hm przyznam się że dopiero teraz znalazłem tą stronę i sam przerabiałem integrację Ale do generowania podpisanej koperty SOAP proponuje o wiele lepsża metodę i do tego ładnie obsługującą wyjątki Chodzi o wykorzystanie ws-php z code.google można ślicznie rozszerzyć klasę SoapClient. Przetestowane na ok 100 urzedach, działa

Autor:gosc
Data: 2016-02-05 7:04
Treść:Czy możemy zlecić Panu wyżej opisaną integrację?

Autor:HumbleHunt
Data: 2016-02-03 11:06
Treść:Mam pytanie, czy podczas walki z integracją ktoś z was spotkał się z problemem: No privilleges to ask about application Połączenie z webserwisem jako tako mam, natomiast dalej mnie nie przepuszcza. (Próba wywołania getUserInfo) Pozdrawiam :)

Autor:Fred
Data: 2015-01-13 18:20
Treść:Może ktoś spotkał się z błędem: soapenv:Server WSDoAllReceiver: security processing failed

Autor:Extern
Data: 2014-10-22 11:55
Treść:Właściwie całą wiedzę jaką miałem na ten temat (poza podpisywaniem dokumentu profilem zaufanym, o czym poradnik nie powstał) przekazałem w tych artykułach pisanych na gorąco. Po tych kilku już latach jakie minęły od czasu gdy dotykałem EPUAPu w głowie zostało mi na ten temat mniej niż zostało opisane powyżej.

Autor:igor
Data: 2014-09-10 19:46
Treść:a jak można się skontaktowac w celu zadania :-) dłuższego pytania ....

Autor:Wojtek
Data: 2014-04-14 14:53
Treść:Super, zaoszczędziłeś mi mnóstwo czasu. Świetny artykuł

Autor:Marek
Data: 2014-03-05 16:19
Treść:Świetny artykuł, bardzo mi pomógł. Mam jedno pytanie - bo nie mogłem znaleźć tego w dokumentacji, czy jest jakiś zdalny interface (web service) do wylogowania użytkownika o danym TGSID?

Autor:Jacek
Data: 2014-02-25 12:57
Treść:U mnie natomiast wystąpił problem, że SignatureValue odpowiedzi z epuapu była niepoprawna (ale w fiddlerze odpowiedz zawierała te rekordy które chciałem). Ktoś miał podobny problem?

Autor:Autor
Data: 2013-11-18 15:05
Treść:Miał ktoś z was ten problem ? po podpisaniu certyfikatem, przy próbie wysyłki WSEC5621E: Unsupported signed part: URI=

Autor:Magda
Data: 2013-10-24 14:37
Treść:Artykuł MEGA pomocny! dzięki! Spotkał się ktoś może z problemem " Allowed memory size of 134217728 bytes exhausted"? Nie wiem czy to problem z kodem czy ograniczeniem serwera?

Autor:Magda
Data: 2013-10-24 14:18
Treść:Artykuł MEGA pomocny! dzięki! Spotkał się ktoś może z problemem " Allowed memory size of 134217728 bytes exhausted"? Nie wiem czy to problem z kodem czy ograniczeniem serwera?

Autor:Magda
Data: 2013-10-24 11:17
Treść:na certyfikat testowy czekałam ok 2 dni, ale na uprawnienia administratora tydzień.

Autor:Autor
Data: 2013-10-22 12:47
Treść:Jak długo czekałeś na wydanie certyfikatu ?

Autor:Aaimnr
Data: 2012-10-15 11:12
Treść:Jeżeli chodzi o brak możliwości weryfikacji pesel, to czy nie jest tak, że choć nie możemy odpytać o pesel, to możemy uzyskać odpowiedź, czy podany przez nas pesel, jest poprawnym peselem ( PeselProxy.wsdl )? Funkcjonalność jest równoważna (możemy poprosić użytkownika o podanie pesel jeżeli jeszcze go nie mamy), a wyklucza uzyskanie danych osobowych bez zgody użytkownika.

Autor:Kamil
Data: 2012-08-20 13:09
Treść:Może ktoś zna odpowiedź: http://stackoverflow.com/questions/12035980/webservice-client-and-signing-messages

Autor:Kamil
Data: 2012-08-20 13:08
Treść:Może ktoś zna odpowiedź: http://stackoverflow.com/questions/12035980/webservice-client-and-signing-messages

Autor:kravietz
Data: 2012-04-20 10:41
Treść:Naprawdę świetny kawałek!

Autor:Miodzio po
Data: 2012-03-31 15:50
Treść:Dzięki wielkie za ten obszerny artykuł. Bardzo pomocna sprawa, super robota!

Autor:Sebastian
Data: 2011-09-09 10:33
Treść:Ten poradnik powinien być udostępniany w dokumentacji epuapu

Autor:Extern
Data: 2011-09-07 20:39
Treść:Miło mi widzieć że się wiedza przydaje, artykuł na dzisiejszą chwilę jest jeszcze nie skończony, nie opisałem jeszcze podpisywania dokumentu XML profilem zaufanym, ale niestety ciągły brak czasu.

Autor:Sebastian
Data: 2011-09-06 12:17
Treść:Witam, pracuję nad projektem, którego jednym z elementów jest podpisywanie dokumentów za pomocą profilu zaufanego. Chętnie postawiłbym jakiś dobry trunek autorowi za przebrnięcie przez to wszystko i umieszczenie poradnika w sieci. Nie ma tylko nigdzie numeru konta

Autor:Jacek
Data:2011-09-06 11:13
Treść:Dzięki za kolejną dawkę pożytecznych informacji :)

Copyright Extern