Till Boendekalkylen finns en modul kallad "Integrationsvy". Vyns syfte är:

  • Att enklare kunna integrera låneprocessens användargränssnitt med Boendekalkylens användargränssnitt.

  • Att snabbt kunna visa uppgifter som man vill se för ett ärende i låneprocessen

  • Att lösa en del tekniska utmaningar:

    • Kunna köra Boendekalkylen cross-domain i en IFRAME som dynamiskt kan ändra storlek

    • Cross-domain kunna kommunicera mellan Låneprocessens användargränssnitt och Boendekalkylens användargränssnitt

    • Kunna starta Boendekalkylens fulla gränssnitt, redigera ett underlag och sedan tillbaka igen till låneprocessen

Detta dokument beskriver hur man kommer igång med Boendekalkylens integrationsvy i ett användargränssnitt för låneprocess. Dokumentet beskriver detta ur perspektivet att Boendekalkylen körs som moln-lösning hos Vitec och därmed så beskrivs inte själva konfigurationen av Boendekalkylen som behöver göras då detta utförs av Vitec vid moln-drift.

Exempel på integrationsvyn i användargränssnittet i ett låneprocess-system

Inloggning för demo skapas på begäran. 

Övergripande systemskiss

Innehåll och utformning av själva integrationsvyn

Detta styrs av boendekalkylens konfigurationsfil som vid moln-drift hanteras av Vitec. Beställningar av justeringar görs på "verksamhetsnivå" till Vitec.

API - integrationsvy

API-funktion

Parametrar

Beskrivning

window.cpxBoendeKalkyl.InitieraVy

  • ElementID: String

  • KalkylID: String

  • URLParams: String

  • Callback: function(e)

Alternativ:

  • ElementId: String

  • Options: Object

  • URLParams: String

  • Callback: function(e)

Visar integrationsvyn för angiven kalkyl på önskat ställe på HTML-sidan.

När man öppnar/stänger boendekalkylens gränssnitt och när man sparar ett ändrat underlag så anropas callbacken och sidan som visar vyn kan då skicka case’t vidare i processen.

e.Anledning = ”OEPPNAR_BOENDEKALKYLEN”
e.Anledning = ”STAENGER_BOENDEKALKYLEN”
e.Anledning = ”SPARAT_KALKYLEN”
e.KalkylID = ID på kalkylen

Om boendekalkylen är konfigurerad att tillåta ”spara som ny” så kan man få ett nytt e.KalkylID än det man ursprungligen öppnade i integrationsvyn.

Callback är optional

Options: Object {
kalkylid: String,
viewactions: boolean (visa/dölj viewactions, d.v.s. redigeraknappen)
}

window.cpxBoendeKalkyl.Login

  • ElementID: String

  • LoginParametrar: Object

  • Ticket: String

  • Callback: function

Loggar in med angiven Ticket och anropar en callback när inloggninen är klar.

LoginParametrar styr autentiseringen och vad man skickar med här beror på vilken typ av autentiseringsmetod som är konfigurerad. 

Vad en ticket ska vara beror på hur boendekalkylens autentisering är konfigurerad. Vilken autentisering som används väljer ni som kund genom beställning till Vitec. Vid drift hos Vitec är det SAML som gäller.

  • Vid SAML-autentisering så ska ticket vara en base64-kodad "SAML Response with Signed Message" (för mer info om SAML, se https://www.samltool.com). I LoginParametrar måste man skicka med "konfigurationsid", se exempelkod sist i denna dokumentation för mer information och syntax.

  • Vid http-autentisering så behövs inte login-anropet så då ska man inte anropa login.

  • Vid övrig autentisering så görs autentisering först "server-server" med ett webservice-anrop och tillbaka från Boendekalkylen får man då en ticket. Denna ticket använder man sedan vid login-anropet

SAML

Vid SAML är det än så länge så att det är anroparen som autentiserar personen och skapar SAML-Response. Detta behövs för att det ska bli "Single Sign On". Dvs för att göra detta så behöver anroparen ha ett bibliotek som kan skapa ett SAML-Response samt ett privat certifikat för att signera med. Den publika delen av certifikatet ska skickas över till Vitec som installerar det i Boendekalkylen. 

Det finns ett projekt på CodeProject som med källkod visar tydligt hur man gör en SAML-login. Performing a SAML Post with C#.

SAML Exempelkod

public static string GetPostSamlResponse(string recipient, string issuer, string domain, string subject, StoreLocation storeLocation, StoreName storeName, X509FindType findType, X509Certificate2 cert, string certPassword, Dictionary<string, string> attributes) {
SigningHelper.SignatureType signatureType = SigningHelper.SignatureType.Response;
ResponseType responseType = new ResponseType();
responseType.ID = "_" + Guid.NewGuid().ToString();
responseType.Destination = recipient;
responseType.Version = "2.0";
responseType.IssueInstant = DateTime.UtcNow;
responseType.Issuer = new NameIDType {
Value = issuer.Trim()
};
responseType.Status = new StatusType();
responseType.Status.StatusCode = new StatusCodeType();
responseType.Status.StatusCode.Value = "urn:oasis:names:tc:SAML:2.0:status:Success";
XmlSerializer xmlSerializer = new XmlSerializer(responseType.GetType());
StringWriter stringWriter = new StringWriter();
XmlWriter xmlWriter = XmlWriter.Create(stringWriter, new XmlWriterSettings {
OmitXmlDeclaration = true,
Indent = true,
Encoding = Encoding.UTF8
});
string text = string.Empty;
AssertionType assertionType = CreateSamlAssertion(issuer.Trim(), recipient.Trim(), domain.Trim(), subject.Trim(), attributes);
responseType.Items = new AssertionType[] {
assertionType
};
xmlSerializer.Serialize(xmlWriter, responseType);
xmlWriter.Close();
text = stringWriter.ToString();
text = text.Replace("SubjectConfirmationData", string.Format("SubjectConfirmationData NotOnOrAfter=\"{0:o}\" Recipient=\"{1}\"", DateTime.UtcNow.AddMinutes(5.0), recipient));
stringWriter.Close();
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(text);
XmlElement newChild = SigningHelper.SignDoc(xmlDocument, cert, "ID", (signatureType == SigningHelper.SignatureType.Response) ? responseType.ID : assertionType.ID);
xmlDocument.DocumentElement.InsertBefore(newChild, xmlDocument.DocumentElement.ChildNodes[1]);
string outerXml = xmlDocument.OuterXml;
byte[] bytes = Encoding.UTF8.GetBytes(outerXml);
return Convert.ToBase64String(bytes);
}
private static AssertionType CreateSamlAssertion(string issuer, string recipient, string domain, string subject, Dictionary<string, string> attributes) {
AssertionType assertionType = new AssertionType();
assertionType.ID = "_" + Guid.NewGuid().ToString();
assertionType.Issuer = new NameIDType {
Value = issuer.Trim()
};
assertionType.Version = "2.0";
assertionType.IssueInstant = DateTime.UtcNow;
ConditionsType conditionsType = new ConditionsType();
conditionsType.NotBefore = DateTime.UtcNow;
conditionsType.NotBeforeSpecified = true;
conditionsType.NotOnOrAfter = DateTime.UtcNow.AddMinutes(5.0);
conditionsType.NotOnOrAfterSpecified = true;
conditionsType.Items = new ConditionAbstractType[] {
new AudienceRestrictionType {
Audience = new string[] {
domain.Trim()
}
}
};
NameIDType nameIDType = new NameIDType();
nameIDType.NameQualifier = domain.Trim();
nameIDType.Value = subject.Trim();
SubjectConfirmationType subjectConfirmationType = new SubjectConfirmationType();
SubjectConfirmationDataType subjectConfirmationData = new SubjectConfirmationDataType();
subjectConfirmationType.Method = "urn:oasis:names:tc:SAML:2.0:cm:bearer";
subjectConfirmationType.SubjectConfirmationData = subjectConfirmationData;
SubjectType subjectType = new SubjectType();
AttributeStatementType attributeStatementType = new AttributeStatementType();
AuthnStatementType authnStatementType = new AuthnStatementType();
authnStatementType.AuthnInstant = DateTime.UtcNow;
AuthnContextType authnContextType = new AuthnContextType();
AuthnContextType arg_14C_0 = authnContextType;
ItemsChoiceType5[] itemsElementName = new ItemsChoiceType5[1];
arg_14C_0.ItemsElementName = itemsElementName;
authnContextType.Items = new object[] {
"AuthnContextClassRef"
};
authnStatementType.AuthnContext = authnContextType;
subjectType.Items = new object[] {
nameIDType,
subjectConfirmationType
};
assertionType.Subject = subjectType;
IPHostEntry hostEntry = Dns.GetHostEntry(Environment.MachineName);
SubjectLocalityType subjectLocalityType = new SubjectLocalityType();
subjectLocalityType.Address = hostEntry.AddressList[0].ToString();
attributeStatementType.Items = new AttributeType[attributes.Count];
int num = 0;
foreach (KeyValuePair<string, string> current in attributes) {
AttributeType attributeType = new AttributeType();
attributeType.Name = current.Key;
attributeType.NameFormat = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic";
attributeType.AttributeValue = new object[] {
current.Value
};
attributeStatementType.Items[num] = attributeType;
num++;
}
assertionType.Conditions = conditionsType;
assertionType.Items = new StatementAbstractType[] {
authnStatementType,
attributeStatementType
};
return assertionType;
}

Möjlig vidareutveckling för SAML-inloggning

Tycker ni att detta är bökigt så kan vi vidareutveckla Integrationsvyn så att den kan hantera att användaren inte är inloggad och då visa en inloggningsruta som användaren först måste fylla i innan integrationsvyn visas, men det blir ju då ett extra steg för användaren som inte är speciellt användarvänligt. 

Sedan finns ett mellanting vi skulle kunna vidareutveckla och det är att vi tillhandahåller webservice-funktioner till vår autentiserings-system "Vitec IAM" så att ni till det systemet kan anropa en webservice och skicka in användarnamn + lösenord och sedan få tillbaka en SAML Response som ni kan skicka in till integrationsvyn och till webservice-anrop ni vill göra i process-systemet. Gör ni på detta sätt så behöver ni alltså bara visa en inloggningsruta en gång och sedan kan ni återanvända SAML Response under en viss tid till alla anrop så länge som kunden är inne och kör i process-systemet.

Båda dessa vidareutvecklingar måste beställas i förväg och hanteras som anpassningsarbete på konsultbasis.

Komplett exempel för att starta Integrationsvyn med SAML-inloggning

Nedanstående exempel förutsätter att det finns en boendekalkyl installerad på context-path "j" på en websajt med host-namn "https://capitexboendekalkyl-exempel.capitex.vitec.net" samt att den är konfigurerad för SAML och integrationsvy.

  • URL till scriptet från Vitec vid "moln-drift" hos Vitec

  • "konfigurationsId" i exempelkoden nedan fås från Vitec vid "moln-drift" hos Vitec

  • Den ticket som syns nedan fungerar endast med ett specifikt testceritifikat och för en begränsad tid, så den ticket'en fungerar alltså inte att använda i något exempel-anrop

  • KalkylID 700fa55:16307059af3:-7fed i exempelkoden nedan är bara ett exempel och KalkylID är alltså en uppgift som sparas i låneprocessen tillsammans med ärendet och den måste hämtas därifrån.

<html>
<body>
<script src="https://capitexboendekalkyl-exempel.capitex.vitec.net/j/cpxintegration.js"></script>
<div id="cpxcontainer"/>
<script>
var ticket = "PFJlc3BvbnNlI…HhtbG5zOnhzaT0ia+"; //OSB! Nedkortad icke fungerande ticket…
window.cpxBoendeKalkyl.Login(
"cpxcontainer",
{konfigurationsId:'guiconf'},
ticket,
function() {
window.cpxBoendeKalkyl.InitieraVy(
"cpxcontainer",
"700fa55:16307059af3:-7fed",
function(e) {
// Här ska man informera låneprocess-systemet att kalkylen blivit uppdaterad,
// hur man gör beror på vilket process-system som används.
// I vissa system anropar man process-systemets klient-api som i sin tur anropar backend
// I vissa system anropar man själv process-systemets backend med ett "ajax/rest"-anrop
alert(e.Anledning);
});
});
</script>
</body>
</html>

För att ovanstående ska fungera måste boendekalkylen skicka ett OnApplicationLoaded event till integrationsvyn. Detta görs genom att i konfigurationen av boendekalkylen ha med följande custom javascript

<capitex_boendekalkyl_vy_customjs>
<![CDATA[
window.parent.postMessage({function: "OnApplicationLoaded"}, "*");
]]>
<capitex_boendekalkyl_vy_customjs>

Exempel för att starta Integrationsvyn utan inloggning

Om kalkylen hostas hos banken och access kontrolleras genom extern accesskontroll som hanteras av banken så startar man integrationsvyn utan login enligt nedan:

Integrationsvy utan login

<html>
<body>
<script src="https://capitexboendekalkyl-exempel.capitex.vitec.net/j/cpxintegration.js"></script>
<div id="cpxcontainer"/>
<script>
window.cpxBoendeKalkyl.InitieraVy("cpxcontainer", "700fa55:16307059af3:-7fed",
function(e) {
// Här ska man informera låneprocess-systemet att kalkylen blivit uppdaterad,
// hur man gör beror på vilket process-system som används.
// I vissa system anropar man process-systemets klient-api som i sin tur anropar backend
// I vissa system anropar man själv process-systemets backend med ett "ajax/rest"-anrop
alert(e.Anledning);
});
</script>
</body>
</html>

Exempel med options variabeln:

Integrationsvy utan login

<!DOCTYPE html>
<html lang="sv">
<head>
<meta charset="utf-8"/>
</head>
<body>
<script src="https://capitexboendekalkyl-exempel.capitex.vitec.net/j/cpxintegration.js"></script>
<div id="cpxcontainer4" class="capitex-application"/>
<script>
var id = "" + Math.random();
document.getElementsByClassName("capitex-application")[0].id = id;
var options = {};
options.kalkylid = "5ca1de53:166afbdd26c:-7ffd";
options.viewactions = false;
window.cpxBoendeKalkyl.InitieraVy(id, options, '', function(e) {});
</script>
</body>
</html>