Seal.Net Overblik

by Simon Krog Schrøder, last modified on 26-06-2017

Denne "Kom Godt i Gang Guide" omhandler Seal.Net. Guiden er beregnet til it-faglige personer som skal til eller er i gang med at udvikle systemer, der skal benytte sig af Seal.Net. Det anbefales at Platformsintroduktion læses inden denne guide.

1. Introduktion

Alle services der udbydes på NSP overholder den XML-baserede standard ”Den Gode WebService (DGWS)”. At designe og implementere en service der overholder denne profil, er ikke nødvendigvis en let opgave. Derfor er Seal.Net’s formål at indpakke DGWS specifikke detaljer, og abstrahere alle typer fra XML til objektform, for på den måde, at gøre det lettere for udvikleren at overholde standarden.

1.1. Support

Hvis du har brug for support, kan du lave en indberetning til Service desk. Her kan du også indrapportere fejl og oprette testdata.

1.2. Digitaliser.dk User Group

På digitaliser.dk er der oprettet en gruppe for Seal.Net. Der opfordres til at benytte forum funktionaliteten i denne gruppe som User Group, til at diskutere, informere om, og stille spørgsmål til Seal.Net. Gruppen vil desuden være opdateret med kode repository og andre relevante ressourcer. Gruppen findes her.

2. Relaterede komponenter 

Seal.Net samspiller med en række NSP komponenter. Det kan være en god idé at sætte sig ind i disse, før man går i gang med at udvikle systemer, der benytter sig af Seal.Net. Disse komponenter er listet nedenfor, med links til deres egen ”Kom Godt i Gang Guide”.

2.1. STS (Security Token Service)

Det kræver autentificering at få adgang til NSP-komponenter. STS fungerer som en fælles komponent, som kan udstede adgangsgivende billetter til andre komponenter, så disse kun kræver simpel verifikation. STS – Signering og STS – Billetomveksling ”Kom Godt i Gang Guide”’erne forklarer yderligere.

2.2. NTS (NSP Test Service)

NTS er et værktøj til verificering af korrektheden af et DGWS SOAP-request. Hvis der opleves problemer med en service, kan et request i stedet blive sendt til NTS, som vil levere et uddybende svar, om hvad der måtte være galt med det pågældende request. NTS tjekker en række egenskaber ved requests, som f.eks. UTF-8 encoding og eksistensen af de obligatoriske DGWS header-elementer. Yderligere information kan findes her.

2.3. NGW (NSP GateWay)

NGW er en særlig udgave af den såkaldte SOSI-GW, som har til formål at sørge for at brugeren kun bliver afkrævet koden til sit medarbejdercertifikat én gang, på tværs af systemer. SOSI-GW og NGW – NSP Gateway ”Kom Godt i Gang Guide”’erne forklarer yderligere.

2.4. DCC (DeCoupling Component)

DCC’en fungerer som en WebService Gateway hvilket begrænser kravet til klienten, om kendskab til lokationen af den specifikke service. DCC -Afkoblingskomponenten ”Kom Godt i Gang Guide”’en forklarer yderligere.

3. Kode repository

Seal.Net koden kan hentes med Subversion, på følgende adresser:

Den binære pakke kan hentes på https://www.nuget.org.

Sourcekoden er offentligt tilgængelig på https://svn.nspop.dk/public/components/seal.NET/

4. Eksempler og demoer

Til at hjælpe med at komme i gang, er her en liste over relevante kode eksempler og demoer, som kan give en idé om hvordan man kan bruge Seal.Net.

4.1. MinLog Demo

Benytter MinLog registreringsservicen. Demoen opretter et id-kort, og logger ind via STS’en. Herefter registrerer den en log-besked i MinLog registreringsservicen. Koden kan downloades her.

4.2. FMK Demo

Benytter FMK servicen Medicinecard til at hente et medicinkort både vha. et SOSI ID-kort og NemId. Koden kan downloades her.

4.3. FMK 1.4.6 C# .NET Client

Et lille projekt, der viser et simpelt eksempel på kald af hent medicinkort fra FMK 1.4.6. Det kan køre ud af boksen, men følgende ting bør gøres:

  • Udskift certifikatet med jeres eget p12 moces certificat
  • Ret konfiguration i app.config og settings
  • Ret stien til certifikatet i SosiUtil.cs, da det pt. er hardcoded
  • Systemet skal whitelistes til FMK, også i test

Koden kan downloades her.

4.4. Trin-for-trin guide eksempel kode

Her (link mangler) finder du koden  brugt til trin-for-trin guiden for Seal.Net.

5. Trin-for-trin opstarts-guide

Her er en trin-for-trin guide til hvordan man kommer i gang med Seal.Net. Guiden vil forklare hvordan man installerer Seal.Net via NuGet, hvordan man importerer en WSDL fil for FMK (Fælles Medicin Kort) servicen, hvordan man benytter WCF EndpointBehaviors til at validere beskeder, hvordan man laver et ID-kort, samt hvordan man laver et kald til servicen (FMK).

 (Note: Denne guide vil benytte et ’Console Project’ i Visual Studio 2015, for at simplificere processen. Det er selvfølgelig muligt at lave en hvilken som helst anden type projekt, men ’Console Project’ anbefales. Koden der er brugt i guiden, findes under "Eksempler og Demoer")

5.1. Installer Seal.Net til projektet

Før vi kommer i gang, skal der installeres de nødvendige Seal.Net assemblies. Dette gøres via NuGet, enten via Package Browseren, eller ved at skrive følgende kommando i Package Manager Console:

Install-Package Seal.net

Når installationen er fuldført, skulle der gerne være følgende assemblies i projektet:


5.2. Importér WSDL-filen for FMK (Fælles Medicin Kort) servicen

Vi vil nu importere WSDL-filen for den service der benyttes i resten af guiden. Denne guide benytter FMK version 1.4.6, som kan findes her.

Vælg ”WSDL med skemaer inline, version 1.4.6”. Udpak filen, og tilføj den til projektet via References > Add Service Reference. Skriv addressen til den udpakkede WSDL, og tryk Go. Giv servicen et navn (f.eks. MedicineCard) og tryk OK.

Servicen er nu tilføjet:


5.3. Lav et Request

For at kunne kalde denne nye FMK-service, kræver det at man medsender en Security, en Header samt en WhitelistingHeader.

Først defineres en række variable med de værdier, som skal bruges i kaldet, samt et MOCES og et VOCES certifikat. Nedenfor er de brugte værdier for eksemplet vist:

private const string ItSystemName = "FMK_test_client";
private const string SosiCareProviderCvr = "30808460";
private const string SosiCareProviderName = "Nets";
private const string Issuer = "NETS DANID A/S";
private const string StsUrl = "http://test2-cnsp.ekstern-test.nspop.dk:8080/sts/services/SecurityTokenService";
private const string StsOioToSosiUrl = "http://test2.ekstern-test.nspop.dk:8080/sts/services/OIOSaml2Sosi";
private const string UserGivenName = "Stine";
private const string UserSurName = "Svendsen";
public const string UserCpr = "1802602810";
private const string UserEmail = "user@test.dk";
private const string UserAuthCode = "ZXCVB";
private const string UserRole = "Læge";
private readonly X509Certificate2 _userCertificate = new X509Certificate2("MOCES_cpr_gyldig.p12", "Test1234");
private readonly X509Certificate2 _systemCertificate = new X509Certificate2("VOCES_gyldig.p12", "Test1234");

Certifikateterne der er brugt kan findes på Nets’ hjemmeside.

Download her versionerne ”Test Bruger med CPR (gyldig)” og "DanID Test (gyldig)".

5.3.1. Security

En Security er det samlende element for sikkerhedsoplysninger i beskeden, som indeholder informationer om signering, token og kryptering.

For at lave en Security, kræver det at man først har et ID-kort.

5.3.1.1. IdCard

Seal.Net giver mulighed for at konstruere to forskellige SOSI IdCards (UserIdCard og SystemIdCard) til anvendelse i fagsystemer indenfor sundhedsvæesnet. Disse konstrueres vha. en SOSIFactory. I eksempel koden benyttes et UserIdCard til at hente et medicinkort, hvorefter et SystemIdCard benyttes til at sætte status af medicinkortet til "ikke ajourført".

5.3.1.1.1. UserIdCard

Først laves en SOSIfactory.

CreateFactory

private SOSIFactory CreateFactory(X509Certificate2 cert)
        {
            GenericCredentialVault vault = new GenericCredentialVault("StepByStepTestStore");
            vault.SetSystemCredentials(cert);
            SosiTestFederation federation = new SosiTestFederation(new CrlCertificateStatusChecker());
            CredentialVaultSignatureProvider sigProvider = new CredentialVaultSignatureProvider(vault);
            SOSIFactory factory = new SOSIFactory(federation, sigProvider);
            return factory;
        }

SOSIFactory kræver en SignatureProvider som står for at levere en signatur i form af et certifikat, til signering af ID-kort på brugerens side, som tilsvarende kræver en CredentialVault, til opbevaring af disse certifikater. Seal.net leverer en enkelt implementation af en sådan vault; GenericCredentialVault. Her kan certifikater tilføjes og fjernes fra systemets CertStore. Det er desuden muligt at inkludere en Federation som senere kan benyttes til at verificere signaturer returneret fra STS'en. Seal.Net inkluderer to implementationer af FederationSosiFederation og SosiTestFederation, som hhv. verificerer op mod prod og test certifikater.

Yderligere kan der specificeres IssuerDGWS version og System Alias gennem App.config som følger:


Udelades disse, sættes de blot til deres default værdier, hhv: "TheSOSILibrary", "1.0.1" og "SOSI:ALIAS_SYSTEM".

Der defineres nu en UserInfo ud fra de tidligere definerede variable, hvorefter et UserIdCard kan generes og signeres af brugeren med det tidligere nævnte MOCES certifikat via SignatureProvider'en.

UserIdCard

UserInfo userInfo = new UserInfo(UserCpr, UserGivenName, UserSurName, UserEmail, UserRole, UserRole, UserAuthCode);
UserIdCard idCard = factory.CreateNewUserIdCard(ItSystemName, userInfo, new CareProvider(SubjectIdentifierType.medcomcvrnumber, SosiCareProviderCvr, SosiCareProviderName),
                                            AuthenticationLevel.MocesTrustedUser, "", "", _userCertificate, "");
idCard.Sign<Assertion>(factory.SignatureProvider);

I tilfælde af mismatch mellem de angivede værdier, vil man her blive mødt med en exception der forklarer fejlen, som f.eks:


Herefter laves et ”Sign in” via Security Token Service’en (Se STS (Security Token Service)), hvorefter den returnerede signatur verificeres op mod den tidligere nævnte Federation:

_idCard = SealUtilities.SignIn(idCard, Issuer, StsUrl);
_idCard.ValidateSignatureAndTrust(factory.Federation);

Vi har nu et signeret ID-kort, og kan begynde at lave vores Security. Denne laves ud fra XML i vores IdCard:

MakeSecurityUsingXmlDocuments()  Expand source

 public SecurityHeaderType MakeSecurity(IdCard idc)
        {
            var assertionDoc = new XmlDocument();
            var assertionElement = assertionDoc.ReadNode(idc.Xassertion.CreateReader()) as XmlElement;
            var timestampDoc = new XmlDocument();
            var timestampElement = timestampDoc.CreateElement("Timestamp", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
            var createdElement = timestampDoc.CreateElement("Created", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
            createdElement.InnerText = DateTimeEx.RoundFiveMinutesAgoUtc.ToLocalTime().ToString("yyyy-MM-ddThh:mm:sszzz");
            timestampElement.AppendChild(createdElement);
            var idAttribute = new XmlDocument().CreateAttribute("id");
            idAttribute.Value = Guid.NewGuid().ToString("D");
            return new SecurityHeaderType
            {
                Any = new[] {
                    timestampElement,
                    assertionElement
                },
                AnyAttr = new[] {
                    idAttribute
                }
            };
        }

Her generes og returneres en SecurityHeaderType ud fra vores ID-kort.

En Security kan ligeledes genereres ud fra DgwsTypes. Et eksempel på dette, kan findes i MinLog Demoen.

5.3.1.1.2. SystemIdCard

Genereringen af et SystemIdCard forgår stort set analogt til UserIdCard. I stedet benyttes dog nu et VOCES-certifikat i konstruktionen af SOSIFactory. Desuden kræver denne type IdCard færre informationer, og man er derfor også begrænset i hvilke actions der kan foretages med denne. Konstruktionen foregår som følger:

SystemIdCard

SystemIdCard idCard = factory.CreateNewSystemIdCard(ItSystemName, new CareProvider(SubjectIdentifierType.medcomcvrnumber, SosiCareProviderCvr, SosiCareProviderName), 
                                              AuthenticationLevel.VocesTrustedSystem, "", "", _systemCertificate, "");
idCard.Sign<Assertion>(factory.SignatureProvider);

Guiden vil fokusere på brugen af UserIdCard'et, men eksempel koden indeholder et eksempel på kald med SystemIdCard.

5.3.1.1.3. IdCard fra OIOSaml assertion

Hvis man har en Saml2Assertion fra en Identity Provider (IdP), er det ligeledes muligt at konvertere denne til et IdCard via STS'en. Dette gøres vha. den såkaldte OioSamlAssertionToIdCardRequestDomBuilder, som genererer et request, som efterfølgende kan sendes til STS'en. Denne opbygges som følger:

OioSamlToIdCardRequest

var assertion = new OioSamlAssertion(XElement.Load("nemlog-in_assertion.xml"));
GenericCredentialVault vault = new GenericCredentialVault("StepByStepTestStore");
vault.SetSystemCredentials(_userCertificate);
OIOSAMLFactory factory = new OIOSAMLFactory();
OioSamlAssertionToIdCardRequestDomBuilder domBuilder = factory.CreateOiosamlAssertionToIdCardRequestDomBuilder();
domBuilder.ItSystemName = ItSystemName;
domBuilder.SigningVault = vault;
domBuilder.UserAuthorizationCode = UserAuthCode;
domBuilder.UserEducationCode = null;
domBuilder.UserGivenName = UserGivenName;
domBuilder.UserSurName = UserSurName;
domBuilder.OioSamlAssertion = assertion;
var requestDoc = domBuilder.Build();
var assertionToIdCardRequest = factory.CreateOioSamlAssertionToIdCardRequestModelBuilder().Build(requestDoc);
return SealUtilities.SignIn(assertionToIdCardRequest, StsOioToSosiUrl);

Bemærk at der i forbindelse med SignIn til STS'en benyttes en anden URL end tidligere. Herefter er flowet fuldstændigt som med UserIdCard.

5.3.2. Header

Vi ønsker nu at lave vores HeaderHeaderen indeholder tekniske og logistiske data, der vedrører forsendelse af hele meddelelsen. Til dette formål laves metoden MakeHeader()

MakeHeader()  Expand source

public Header MakeHeader()
        {
            return new Header
            {
                SecurityLevel = 4,
                TimeOut = TimeOut.Item1440,
                TimeOutSpecified = true,
                Linking = new Linking
                {
                    FlowID = Guid.NewGuid().ToString("D"),
                    MessageID = Guid.NewGuid().ToString("D")
                },
                FlowStatus = FlowStatus.flow_running,
                FlowStatusSpecified = true,
                Priority = Priority.RUTINE,
                RequireNonRepudiationReceipt = RequireNonRepudiationReceipt.yes
            };
        }

Her er HeaderTimeOutLinkingFlowStatusPriority og RequireNonRepudiationReceipt alle en del af namespacet for den specifikke FMK service (i dette tilfælde MedicineCard).

SecurityLevel angiver her det sikkerhedsniveau som hele beskeden er underlagt, med værdier i intervallet 1-5.

FlowID er et unikt ID for en session. Alle beskeder i samme session har samme FlowID.

MessageID er et unikt ID for denne ene besked, og bruges til at identificere dubletter ved gensendelse. Begge disse ID'er bør altså gemmes.

5.3.3. WhiteListingHeader

FMK foretager autorisation af klient systemer. Denne er whitelist-baseret, og skal sikre at kun software, der er godkendt til at benytte medicinkortet, kan kalde dets services. Denne Whitelisting er kun i FMK-regi, og er ikke generelt nødvendig for de fleste andre NSP komponenter. WhitelistingHeaderen skal indeholde følgende elementer:

  • SystemOwnerName
    • Navn på leverandøren af afsendersystemet
  • SystemName
    • Navn på afsendersystemet
  • SystemVersion
    • Version på afsendersystemet
  • OrgResponsibleName
    • Navn på den organisation, der har ansvaret  for it-systemet
  • OrgUsingID
    • ID på den organisation, hvor brugeren befinder sig når service-kaldet udføres
  • OrgUsingName
    • Navn på den organisation hørende til OrgUsingID
  • RequestedRole
    • Sundhedsfaglig rolle (Læge, Jordemoder, Sygeplejerske, etc.)


WhiteListingHeaderen opbygges som følger:

MakeWhiteListingHeader()  Expand source

public WhitelistingHeader MakeWhitelistingHeader(bool isUserRequest)
        {
            return new WhitelistingHeader
            {
                ItemsElementName = new ItemsChoiceType9[]
                {
                    ItemsChoiceType9.OrgResponsibleName,
                    ItemsChoiceType9.OrgUsingName,
                    ItemsChoiceType9.OrgUsingID,
                },
                Items = new object[]
                {
                    WhiteListOrgResponsibleName,
                    WhiteListOrgUsingName,
                    new OrgUsingID
                    {
                        NameFormat = NameFormat.medcomskscode,
                        Value = WhitelistOrgSKS
                    },
                },
                RequestedRole = isUserRequest ? WhitelistRequestedRole : WhitelistRequestedRoleSystem,
                SystemName = WhitelistSystemName,
                SystemOwnerName = WhitelistSystemOwner,
                SystemVersion = WhitelistSystemVersion,
            };
        }

5.4. Lav et kald til FMK

Vi har nu alle de nødvendige dele til at kunne lave et request til FMK. Lav en metode GetMedCardRequest, hvor der laves en security, header og whitelistingheader. Lav herefter en client, og kald GetMedicineCardRequest_2015_06_01() som vist nedenfor:

var client = new MedicineCardPortTypeClient("MedicineCardPort");
GetMedicineCardRequest_2015_06_01 request = new GetMedicineCardRequest_2015_06_01()
{
    GetMedicineCardRequest = new GetMedicineCardRequestType()
    {
        PersonIdentifier = new PersonIdentifierType() { Value = SealService.UserCpr, source = "CPR" },
        IncludeEffectuations = true,
        IncludeNonRelevantPrescriptions = true,
        IncludePrescriptions = true,
    },
    Header = header,
    Security = sec,
    WhitelistingHeader = whiteListingHeader
};


var responses = new MedicineCardType[1];
PrescriptionReplicationStatusType prescriptionReplicationStatus;
var timingList = client.GetMedicineCard_2015_06_01(sec, header, null, request.WhitelistingHeader,
                 request.ConsentHeader, request.GetMedicineCardRequest, out prescriptionReplicationStatus, out responses);
GetMedicineCardResponse_2015_06_01 response = new GetMedicineCardResponse_2015_06_01(timingList, prescriptionReplicationStatus, responses);


5.5. Brug WCF EndpointBehaviors til at validere beskeder

I denne sidste del af guiden, vil det blive forklaret, hvordan man kan bruge WCF EndpointsBehaviors til at validere endpoints, validere beskeder (både indhold og headers), logging/tracing, eller modificere beskeder før de sendes videre.

5.5.1. Validering af Endpoints

Først laves en ny klasse, der implementerer interfacet IEndpointBehavior. I eksemplet er denne kaldt FmkMonitoringEndpointBehavior. Fra dette interface får man bl.a. en metode kaldet Validate, som tager et Endpoint. Denne metode vil blive kaldt lige før første kald til det givne endpoint. Her kan man så validere, at endpointet møder bestemte kriterier. I eksemplet, bliver det blot verificeret, at man ikke kalder andre hosts end de to FMK test miljøer.

 public class FMKMonitoringEndpointBehavior : IEndpointBehavior
    {
        private List<string> AllowedEndpoints = new List<string> { "test1.fmk.netic.dk", "test2.fmk.netic.dk" };
        public void Validate(ServiceEndpoint endpoint)
        {
            Console.WriteLine("Validate called!");
            var address = endpoint.Address.Uri;
            if (!AllowedEndpoints.Contains(address.Host))
            {
                throw new AccessViolationException("You are not allowed to call this host!");
            }
        }

Hvis valideringen går godt, returnerer man. Ellers bør man smide en exception.

Før man kan teste om Validate metoden virker, skal EndpointBehavior tilføjes til clienten. Dette kan gøres umiddelbart efter oprettelsen af clienten:

var client = new MedicineCardPortTypeClient("MedicineCardPort");
client.Endpoint.Behaviors.Add(new FMKMonitoringEndpointBehavior());

5.5.2. Validering af beskeder

Vi vil nu kigge på hvordan man kan validere, logge, trace eller modificere beskeder, både før og efter de sendes videre fra systemet. Til dette, oprettes en ny klasse, som implementerer interfacet IClientMessageInspector. I eksemplet er denne kaldt FmkClientMessageInspector. Her får man adgang til to metoder; BeforeSendRequest, som vil kaldes inden et givent request sendes videre fra systemet. Og AfterReceiveReply, som vil kaldes som det første, når man modtager et svar.

BeforeSendRequest er et oplagt sted at logge en besked, før den sendes videre. Man får også adgang til beskedens header, hvilket giver mulighed for at verificere eller modificere denne. I eksemplet, bliver det blot tjekket hvorvidt den ønskede action er tilladt:

public class FmkClientMessageInspector : IClientMessageInspector
    {
        private List<string> AllowedActions = new List<string> { "http://www.dkma.dk/medicinecard/xml.schema/2015/06/01#GetMedicineCard", "http://www.dkma.dk/medicinecard/xml.schema/2015/06/01#SetMedicineCardNotReviewed"}; 
        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            Console.WriteLine("BeforeSendRequest called!");
            var action = request.Headers.Action;
            if (!AllowedActions.Contains(action))
            {
                throw new ActionNotSupportedException("You cannot perform this action.");
            }
            return true;
        }

Bemærk at metoden returnerer et objekt. Hvad end der returneres her, vil være tilgængeligt i AfterReceiveReply metodens correlationstate.

AfterReceiveReply er ligeledes et oplagt sted at logge et svar, da dette er det første sted svaret havner. Man har her adgang til svaret, og kan både inspicere og modificere denne. Man har som tidligere nævnt også adgang til den correlationstate, som man returnerede i BeforeSendRequest. I eksemplet printes svaret blot til konsollen:

public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            Console.WriteLine("AfterReceiveReply called!");
            Console.WriteLine("Received the following reply '{0}'", reply.ToString());
            Console.WriteLine("CorrelationState: {0}", (bool)correlationState);
        }

Nu mangler vi bare at tilføje vores ClientMessageInspector. Dette gøres i FmkMonitoringEndpointBehavior klassen, som blev lavet tidligere. I metoden ApplyClientBehavior, instantieres en ny ClientMessageInspector og tilføjes til den givne ClientRuntime:

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            Console.WriteLine("ApplyClientBehavior called!");
            var myClientMessageInspector = new FmkClientMessageInspector();
            clientRuntime.MessageInspectors.Add(myClientMessageInspector);
        }

WCF EndpointBehaviors er nu helt klar til brug.

5.5.3. SealMessageInspect & SealEndpointBehavior

Der er udviklet en ClientMessageInspector og EndPointBehavior specifikt til Seal.Net. Disse kan validere de mest basale DGWS-krav på udkomne og indgående beskeder. De er endvidere i stand til at udpakke fejl fra en service og lave dem til exceptions. De kan findes på Triforks SVN (som kræver speciel adgang):



  • No labels