go to post Menno Voerman · Dec 4, 2023 Hi Scott, Would be great if you could post the outcome of the WRC tickey. In the near future we also would like to create a generic backend connection to Epic. Another part of the backend integration is the check of the JWT. So the JWT should be created at IRIS side with the correct client_id, scoped, audience etc. But on IRIS side you need to create a webpage where the public part of the key is hosted so Epic can check that the JWT is valid:
go to post Menno Voerman · Dec 3, 2023 Hi Scott, I think I cannot help you any further but you're specific question but I'm very interested in this one with Epic. For my understandig, are you trying to connect to an oAuth2 Interconnect instance with IRIS? What sort of client did you register at the appmarket of Epic. Is this backend integration of patient/practitioner?
go to post Menno Voerman · Aug 14, 2023 Thank you, very useful article. Didn't know the %JSONFIELDNAME part!
go to post Menno Voerman · Aug 9, 2023 Hi Pietro, Not sure if this will help you but I written a retry mechanisme in the BO with 2 additional settings MaxRetries and RetryWaitSeconds. See code below: Class BO.Rest.PostBodyHeaderAuth Extends EnsLib.REST.Operation { Parameter ADAPTER = "EnsLib.HTTP.OutboundAdapter"; Parameter INVOCATION = "Queue"; Property HeaderName As %String(MAXLEN = 1000) [ InitialExpression = "Authorization", Required ]; Property HeaderValue As %String(MAXLEN = 10000) [ InitialExpression = "Bearer", Required ]; Property UseSSL As %Integer [ InitialExpression = 1, Required ]; Property MaxRetries As %Integer [ InitialExpression = 3, Required ]; Property RetryWaitSeconds As %Integer [ InitialExpression = 300, Required ]; Property ContentType As %String(MAXLEN = 300) [ InitialExpression = "application/json; charset=utf-8", Required ]; Parameter SETTINGS = "HeaderName:Auth,HeaderValue:Auth,MaxRetries:Retry,RetryWaitSeconds:Retry,UseSSL:Connection,ContentType:ContentType"; Method PostBodyData(pRequest As BO.Rest.PostBodyHeaderAuth.PostBodyHeaderAuthReq, Output pResponse As BO.Rest.PostBodyHeaderAuth.PostBodyHeaderAuthResp) As %Status { Set tSc = $$$OK Set tCounter = 0 While (tCounter <= ..MaxRetries) { //Create a new %Net.HttpRequest Set tHttpRequest = ##class(%Net.HttpRequest).%New() //Get the serversetting from the Adapter Set tHttpRequest.Server = ..Adapter.HTTPServer Set tHttpRequest.Port = ..Adapter.HTTPPort //Set Content-Type Set tHttpRequest.ContentType = ..ContentType //Set Body from Request Object Do tHttpRequest.EntityBody.Write(pRequest.Body) // Set Https Flag If (..UseSSL = 1) { Set tHttpRequest.Https = 1 Set tHttpRequest.SSLConfiguration = ..Adapter.SSLConfig } Else { Set tHttpRequest.Https = 0 } //Set the Header value for Authentication If ($LENGTH(pRequest.HeaderValue) > 0) { Set tSc = tHttpRequest.SetHeader(..HeaderName, pRequest.HeaderValue) } Else { Set tSc = tHttpRequest.SetHeader(..HeaderName, ..HeaderValue) } //Set Custom Timeouts from Adapter Set tHttpRequest.WriteTimeout = ..Adapter.WriteTimeout Set tHttpRequest.Timeout = ..Adapter.ResponseTimeout Set tHttpRequest.OpenTimeout = ..Adapter.ConnectTimeout If ($LENGTH(pRequest.Url) > 0) { Set tUrl = pRequest.Url } Else { Set tUrl = ..Adapter.URL } //Create a trace with the complete url $$$TRACE(..Adapter.HTTPServer_"/"_tUrl) //Do the post call Set tSc = tHttpRequest.Post(tUrl) //Create the tHttpResponse object #dim tHttpResponse As %Net.HttpResponse //Set the response object Set tHttpResponse = tHttpRequest.HttpResponse //If an technical error has occured If ($$$ISERR(tSc)) { $$$TRACE("Error:"_$System.Status.GetErrorText(tSc)) } ElseIf ('$IsObject(tHttpResponse)) { $$$TRACE("Invalid HTTP Response Object") Set tSc=$$$ERROR($$$EnsErrGeneral, "Invalid HTTP Response Object.") } Else { ;Build Operation Response Set pResponse = ##class(BO.Rest.PostBodyHeaderAuth.PostBodyHeaderAuthResp).%New() Set pResponse.HTTPStatusCode = tHttpResponse.StatusCode Set pResponse.HTTPStatusLine = tHttpResponse.StatusLine ;Check the HTTPStatusCode Set tSuccess = ##class(Util.Functions).IsSuccess(tHttpResponse.StatusCode) If (tSuccess) { Set tCounter = ..MaxRetries + 1 Do tHttpResponse.Data.Rewind() Set pResponse.HTTPResponseData = tHttpResponse.Data } Else { $$$TRACE("An error HTTP code has been received. HTTPSCode="_tHttpResponse.StatusCode) Try { Do tHttpResponse.Data.Rewind() Set pResponse.HTTPResponseData = tHttpResponse.Data } Catch exception {} } } Set tCounter = tCounter+1 If (tCounter <= ..MaxRetries) { $$$TRACE("Retry: "_tCounter_". Wait for "_..RetryWaitSeconds_" seconds") HANG ..RetryWaitSeconds } } return tSc } }
go to post Menno Voerman · Aug 8, 2023 Hi Heloisa, Thanks, wil go for the until (wait until specific time has exceeded or file has been found)
go to post Menno Voerman · Aug 8, 2023 Hi Eduard, I has indeed looking for the Deferrd Response. But I think it's not useful in this case looking at the documentation
go to post Menno Voerman · Aug 8, 2023 @Guillaume Rongier, I've had some struggles with converting but that was nog due to your functions. See a snippet of working code below: ClassMethod Decrypt(Stream As %Stream.GlobalCharacter, key As %String) As %Stream.GlobalCharacter [ Language = python ] { import iris import os from cryptography.hazmat.primitives import hashes, padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend from base64 import b64decode def stream_to_string(stream)-> str: string = "" stream.Rewind() while not stream.AtEnd: string += stream.Read(1024) return string def string_to_stream(string:str): stream = iris.cls('%Stream.GlobalCharacter')._New() n = 1024 chunks = [string[i:i+n] for i in range(0, len(string), n)] for chunk in chunks: stream.Write(chunk) return stream # Convert the Base64 encoded key to bytes key_bytes = b64decode(key) cipher_bytes = bytes(stream_to_string(Stream),'iso-8859-1') # Extract the IV from the first 16 bytes of the cipher iv = cipher_bytes[:16] # Create the AES cipher object backend = default_backend() cipher = Cipher(algorithms.AES(key_bytes), modes.CBC(iv), backend=backend) decryptor = cipher.decryptor() # Decrypt the data, excluding the IV decrypted_bytes = decryptor.update(cipher_bytes[16:]) + decryptor.finalize() # Remove padding unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() decrypted_data = unpadder.update(decrypted_bytes) + unpadder.finalize() return string_to_stream(decrypted_data) }
go to post Menno Voerman · Jul 17, 2023 Hello Guillaume, Thank you for the example code. Will give it a try later on this week and will post the new code. Menno
go to post Menno Voerman · Jun 29, 2023 Hi Alex, Great, thank you for the quick reply. I was wrong with my idea. The %FromJSON is not the problem but the way I get it from the DynamicObject. Menno
go to post Menno Voerman · Jun 29, 2023 Hi Andy, I would say it should be enough to set indeed the Windows Service to manual/disabled during the maintenance: If youre in an mirror situation you could also disable the ISCAgent service, but I would say that the node will never switch because the other service is not up and running:
go to post Menno Voerman · May 10, 2023 For development VS Code is great. Indent is a lot better and also the syntax checking. One small functionality I'm missing is to open classes fast with a shortcut. We're using HealthConnect and at an item the Class Name is shown in the management portal: I can copy the class name, go to studio press Ctrl-O and paste the class name: In VS code I can open also with Ctrl-O but then I need to fill in /HS/FHIRServer/Interop.HTTPOperation.cls Could be a small improvement?
go to post Menno Voerman · Nov 8, 2022 Hello Marc, I saw the same, but after adjusting I get the same error when decrypting (Padding is invalid and cannot be removed) Latest Objectscript code: Class TEST.ENCRYPT { // Symmetric Keys sample to encrypt ClassMethod DoAESCBCEncrypt() As %Status { Set key="pZR8qfrz7t47G+dboyJCH4NnJRrF+dJbvxq37y/cLUo=" Write "Key="_key,! Set keyBase64=$SYSTEM.Encryption.Base64Encode(key,1) Write "KeyBase64="_keyBase64,! Set iv=$system.Encryption.GenCryptRand(16) Write "IV="_iv,! Set ivBase64 = $SYSTEM.Encryption.Base64Encode(iv,1) Write "IVBase64="_ivBase64,! Set text="This is just an encryption test with AES256, blocksize 128, padding PKCS7, mode, CBC with an IV of 16 bytes" Write "Plain Text: "_text,! Set encrypted=$SYSTEM.Encryption.AESCBCEncrypt($zcvt(text,"O","UTF8"),keyBase64,iv) Set EncryptedBase64=$SYSTEM.Encryption.Base64Encode(encrypted,1) Write "EncryptedBase64: "_EncryptedBase64,! Set encryptedComplete = ivBase64_EncryptedBase64 Write "EncryptedBase64WithIV: "_encryptedComplete,! Set ciphertext = $$$URLENCODE(encryptedComplete) write "URL Encoded:"_ciphertext,! return $$$OK } }
go to post Menno Voerman · Nov 7, 2022 Hi Julius, Thank you again. I agree with you that $system.Encryption.GenCryptRand(length) should be a nicer option to generate the random IV. About the IV, this a random IV to make every message unique. The sender will generate the IV and the receiver gets the first 24 characters and this is the IV and will be used in the decryption. //get IV string ivString = deEscape.Substring(0, 24); Console.WriteLine("IV String: " + ivString); The IV is used in the decryption. The part after the IV is the part thats need to be decrypted: string toDecryptWithoutIV = deEscape.Substring(24); Console.WriteLine("To Decrypt without IV: " + toDecryptWithoutIV); I can't find what's the problem in Objectscript. So i've switched to Python and in Python it works right away. See example code below: import base64 from base64 import b64encode from Crypto.Cipher import AES from Crypto.Util.Padding import pad import urllib.parse keyBase64="pZR8qfrz7t47G+dboyJCH4NnJRrF+dJbvxq37y/cLUo=" key = base64.b64decode(keyBase64) toEncrypt = "This is just an encryption test with AES256, blocksize 128, padding PKCS7, mode, CBC with an IV of 16 bytes" toEncryptBytes = str.encode(toEncrypt) cipher = AES.new(key, AES.MODE_CBC) ct_bytes = cipher.encrypt(pad(toEncryptBytes, AES.block_size)) iv = b64encode(cipher.iv).decode('utf-8') ct = b64encode(ct_bytes).decode('utf-8') complete=iv+ct complete = urllib.parse.quote(complete) print(complete) In our used version of IRIS, Python is embedded so I think this will be the first functionality where we're going to use Python.
go to post Menno Voerman · Nov 5, 2022 Hi Julius, Thank you for your response. I've tried your suggestions. See modified code below: Class TEST.ENCRYPT { // Symmetric Keys sample to encrypt ClassMethod DoAESCBCEncrypt() As %Status { set key="pZR8qfrz7t47G+dboyJCH4NnJRrF+dJbvxq37y/cLUo=" set iv=##class(%PopulateUtils).StringMin(16,16) Write "Key="_key,! Write "IV="_iv,! Set ivBase64 = $SYSTEM.Encryption.Base64Encode(iv,1) Write "IVBase64="_ivBase64,! set text="This is just an encryption test with AES256, blocksize 128, padding PKCS7, mode, CBC with an IV of 16 bytes" Write "Plain Text: "_text,! Set encrypted=$SYSTEM.Encryption.AESCBCEncrypt($zcvt(text,"O","UTF8"),key,iv) Set EncryptedBase64=$SYSTEM.Encryption.Base64Encode(encrypted,1) Write "EncryptedBase64: "_EncryptedBase64,! Set encryptedComplete = ivBase64_EncryptedBase64 Write "EncryptedBase64WithIV: "_encryptedComplete,! Set ciphertext = $$$URLENCODE(encryptedComplete) write "URL Encoded:"_ciphertext,! return $$$OK } } Unfortunately when decrypted with the C# code I'm getting the error ''padding is invalid and cannot be removed". Is this about the PaddingMode.PKCS7?
go to post Menno Voerman · Oct 25, 2021 Hi All, The problem is in HS.FHIRServer.RestHandler Class HS.FHIRServer.HC.FHIRInteropAdapter Extends HS.FHIRServer.RestHandler { Parameter isInteropAdapter As %Boolean = 1; Parameter ServiceConfigName As %String = "InteropService"; } For some reason its not allowed to send the bearer token with the unauthenticated application: // Access token present on unsecure CSP request is invalid. Otherwise, if access // token found on secure CSP request then add to FHIR request AdditionalInfo for // later evaluation by the FHIR service. If '%request.Secure { If ($ZConvert($Piece(%request.GetCgiEnv("HTTP_AUTHORIZATION")," ",1),"U") = "BEARER") || ($Get(%request.Data("access_token",1)) '= "") { Set %response.Status = ..#HTTP401UNAUTHORIZED Return $$$OK } Set accessToken = "" } Else { // InterSystems FHIRServer policy is to NOT allow passing access token in the // request URL or form encoded body (either can be found in %request.Data). If $Get(%request.Data("access_token",1)) '= "" { Set %response.Status = ..#HTTP401UNAUTHORIZED Return $$$OK } Set accessToken = ##class(%SYS.OAuth2.AccessToken).GetAccessTokenFromRequest(.tSC) $$$ThrowOnError(tSC) If accessToken '= "" { Do tRequest.AdditionalInfo.SetAt(accessToken, "USER:OAuthToken") Do tRequest.AdditionalInfo.SetAt(hsrestconfig.OAuthClientName, "USER:OAuthClient") } } We're gonna discuss the issue with intersystems but it looks like we need custom programming to make this situation work in the new HC version.
go to post Menno Voerman · Sep 21, 2021 Thank you @Alister Pino and @Julius Kavay for your help. In this scenario see the final solution below: ClassMethod MergeDynamicObjects(ObjComplete As %Library.DynamicObject, ObjAddendum As %Library.DynamicObject) [ Final ] { Set obj1 = ObjComplete.%Get("OptOuts") Set obj2 = ObjAddendum.%Get("OptOuts") Set OptOuts2Iter = obj2.%GetIterator() While OptOuts2Iter.%GetNext(.key , .value ) { Do obj1.%Push(value) } return ObjComplete }
go to post Menno Voerman · Apr 6, 2021 Hi @Stefan Wittmann, I was just curious about this functionality. For a project I need to convert ORU~R01 to MDM~T02 messages. For now it's fine to write the DTL by myself (small messages). I would be happy to see this functionality in a further release. It can indeed be very useful with migration projects.
go to post Menno Voerman · Apr 6, 2021 Hi all, I was wondering in which version I can find the DTL Generator, or is it still in development? In version ' IRIS for Windows (x86-64) 2020.1.1 (Build 408U) Sun Mar 21 2021 22:04:53 EDT' I cannot find it.
go to post Menno Voerman · Feb 25, 2021 Hello Sean, Great, this helps me a lot. I've got a working situation right now. I think the mistake that I made is that I add .cls after the name of the service. Thanks. Menno