Nova postagem

Pesquisar

Artigo
· Jun. 7, 2024 10min de leitura

Création d'une API REST avec authentification JWT en ObjectScript

Avant-propos

Les versions 2022.2 et ultérieures d'InterSystems IRIS offrent la possibilité de s'authentifier auprès d'une API REST à l'aide de jetons web (JWT) JSON. Cette fonctionnalité renforce la sécurité en limitant le lieu et la fréquence de transfert des mots de passe sur le réseau et en fixant un délai d'expiration pour l'accès.

L'objectif de cet article est de servir de tutoriel sur la façon d'implémenter une API REST fictive en utilisant InterSystems IRIS et de verrouiller l'accès à cette API par le biais de JWT.

Discussão (0)1
Entre ou crie uma conta para continuar
Artigo
· Jun. 7, 2024 7min de leitura

Tasks flow with InterSystems IRIS Workflow Engine - Connecting to external applications

Finally and with a little delay, we conclude this series of articles about our Workflow Engine by showing an example of the connection that we could make from a mobile application.

In the previous article we showed the example that we were going to discuss, an application that allows detailed control of a chronic pathology such as hypertension for both the patient and their associated doctor. In this example, the patient will have access from their mobile phone to a web application (basically, a web page designed to be responsive with the device) in which they will receive notifications based on the measurements that the portable blood pressure monitor sends to the IRIS instance.

Therefore we will have two different accesses to our IRIS instance:

  • User access from mobile application.
  • Device access to submit blood pressure readings.

In this article we will see the first of them that allows the patient to manage the tasks that their readings generate.

Connection mobile application - IRIS

For this connection, the simplest thing is to configure a web application within IRIS and to do this we will access it from the management portal, System Administration -> Security -> Applications -> Web Applications:

Next, in the list shown to us, we will click on Create new application, which will open a screen like the following::

On this screen let's configure the following fields:

  • Name: in which we will define the URL that we will publish to give access to our functionality deployed in IRIS.
  • Namespace: the namespace to which we want the web application to be associated, this will allow us to later take advantage of the functionalities of interoperability productions.
  • REST: We will select this option since what we are going to publish is a REST API to allow HTTP connections.
  • Dispatch Class: ObjectScript class that will receive the HTTP call and decide what to do with it.
  • Use JWT Authentication: checking this option will enable the /login and /logout endpoints on the URL that we have defined for our application, which will allow us to obtain a JSON Web Token to authenticate our calls over IRIS.
  • Security Settings -> Allowed Authentication Methods: we will set a password to secure our calls.

Let's take a look at our Workflow.WS.Service class:

Class Workflow.WS.Service Extends %CSP.REST
{

Parameter HandleCorsRequest = 0;
Parameter CHARSET = "utf-8";
XData UrlMap [ XMLNamespace = "https://www.intersystems.com/urlmap" ]
{
<Routes>
	<Route Url="/getTasks" Method="GET" Call="GetTasks" />
    <Route Url="/saveTask" Method="POST" Call="SaveTask" />
</Routes>
}

ClassMethod OnHandleCorsRequest(url As %String) As %Status
{
	set url = %request.GetCgiEnv("HTTP_REFERER")
    set origin = $p(url,"/",1,3) // origin = "http(s)://origin.com:port"
    // here you can check specific origins
    // otherway, it will allow all origins (useful while developing only)
	do %response.SetHeader("Access-Control-Allow-Credentials","true")
	do %response.SetHeader("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS")
	do %response.SetHeader("Access-Control-Allow-Origin",origin)
	do %response.SetHeader("Access-Control-Allow-Headers","Access-Control-Allow-Origin, Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control")
	quit $$$OK
}

ClassMethod GetTasks() As %Status
{
    Try {
        Do ##class(%REST.Impl).%SetContentType("application/json")
        If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        Do ##class(%REST.Impl).%SetStatusCode("200")
        set sql = "SELECT %Actions, %Message, %Priority, %Subject, TaskStatus_TimeCreated, ID FROM EnsLib_Workflow.TaskResponse WHERE TaskStatus_AssignedTo = ? AND TaskStatus_IsComplete = 0"
        set statement = ##class(%SQL.Statement).%New(), statement.%ObjectSelectMode = 1
        set status = statement.%Prepare(sql)
        if ($$$ISOK(status)) {
            set resultSet = statement.%Execute($USERNAME)
            if (resultSet.%SQLCODE = 0) {
                set tasks = []
                while (resultSet.%Next() '= 0) {
                    set task = {"actions": "", "message": "", "priority": "", "subject": "", "creation": "", "id": ""}
                    set task.actions = resultSet.%GetData(1)
                    set task.message = resultSet.%GetData(2)
                    set task.priority = resultSet.%GetData(3)
                    set task.subject = resultSet.%GetData(4)
                    set task.creation = resultSet.%GetData(5)
                    set task.id = resultSet.%GetData(6)
                    do tasks.%Push(task)
                }                
            }
        }
        set result = {"username": ""}
        set result.username = $USERNAME
        Do ##class(%REST.Impl).%WriteResponse(tasks)
        
    } Catch (ex) {
        Do ##class(%REST.Impl).%SetStatusCode("400")
        return ex.DisplayString()
    }

    Quit $$$OK
}

ClassMethod SaveTask() As %Status
{
    Try {
        Do ##class(%REST.Impl).%SetContentType("application/json")
        If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        // Reading the body of the http call with the person data
        set dynamicBody = {}.%FromJSON(%request.Content)
        
        set task = ##class(EnsLib.Workflow.TaskResponse).%OpenId(dynamicBody.%Get("id"))
        set sc = task.CompleteTask(dynamicBody.action)

        if $$$ISOK(sc) {	        
            Do ##class(%REST.Impl).%SetStatusCode("200")
            Do ##class(%REST.Impl).%WriteResponse({"result": "success"})         
		}	
        
    } Catch (ex) {
        Do ##class(%REST.Impl).%SetStatusCode("400")
        Do ##class(%REST.Impl).%WriteResponse({"result": "error"})
    }

    Quit $$$OK
}

}

As you can see, from our WS we solve all the logic we need, but I do not recommend doing this, since we lose the traceability that we could achieve by sending the requests received to the production configured in the Namespace.

Taking a look at the URLMap section we will see that we have 2 endpoints configured in our WS:

  • getTasks: to recover all the user's pending tasks (if you see the SQL used we are passing the username directly from the variable generated when logging in).
  • saveTask: in which we will receive the user's response to the task and proceed to finish it by executing the CompleteTaks method of the EnsLib.Workflow.TaskResponse class.

On IRIS's part, everything would already be configured for the external application to connect.

Testing the external application with the Workflow Engine

First we will simulate sending a message from HL7 to our production by copying the file /shared/hl7/message_1_1.hl7 to the path /shared/in, let's see the message we are sending:

MSH|^~\&|HIS|HULP|EMPI||20240402111716||ADT^A08|346831|P|2.5.1
EVN|A08|20240402111716
PID|||07751332X^^^MI^NI~900263^^^HULP^PI||LÓPEZ CABEZUELA^ÁLVARO^^^||19560121|F|||PASEO MARIO FERNÁNDEZ 258 1 DERECHA^^MADRID^MADRID^28627^SPAIN||555819817^PRN^^ALVARO.LOPEZ@VODAFONE.COM|||||||||||||||||N|
PV1||N
OBX|1|NM|162986007^Pulso^SNM||72|^bpm|||||F|||20240402111716
OBX|2|NM|105723007^Temperatura^SNM||37|^Celsius|||||F|||20240402111716
OBX|3|NM|163030003^Presión sanguínea sistólica^SNM||142|^mmHg|||||F|||20240402111716
OBX|4|NM|163031004^Presión sanguínea diastólica^SNM||83|^mmHg|||||F|||20240402111716

For those of you who do not remember the previous articles, we had defined in our BPL that an alert would be generated when the systolic pressure exceeded 140 or the diastolic pressure exceeded 90, therefore, this message should generate a warning task for our patient with DNI (National Document of Identification) 07751332X and another automatic one that will remain open until IRIS receives the new notification.

Let's check our mobile application:

There we have 2 tasks generated, the first one that we have defined as manual and that depends on the user for its completion and the second that we have declared as automatic and that for it to disappear the user must take a new reading with their blood pressure monitor. If we take a look at the HTTP call we can see how we have included the JWT in it:

If the user clicks on the Accept button of the pending task, the flow will remain waiting to receive the blood pressure meter reading and will not advance to the next steps. Once the manual task is accepted and a new reading from the blood pressure monitor is received in which the marked limits are again exceeded, the system will generate two new warning tasks, one for the patient and another for the associated doctor in which a possible crisis is warned. :

Perfect! We have already set up our patient notification system in a simple and fast way.

Conclusion

As you have seen in this series of articles, the functionality of the Workflow Engine together with the interoperability capabilities of InterSystems IRIS provide incredible potential for the implementation of business processes that few other business solutions can provide. It is true that technical knowledge may be necessary to get the most out of it, but it is really worth the effort required.

4 Comments
Discussão (4)1
Entre ou crie uma conta para continuar
Artigo
· Jun. 7, 2024 7min de leitura

Flujos de tareas con InterSystems IRIS Workflow Engine - Conexión con aplicaciones externas

Finalmente y con un poco de retraso, concluimos esta serie de artículos sobre nuestro Workflow Engine mostrando un ejemplo de la conexión que podríamos hacer desde una aplicación para móvil.

En el artículo anterior mostrábamos el ejemplo que íbamos a tratar, una aplicación que permita realizar un control pormenorizado de una patología crónica como puede ser la hipertensión tanto al paciente como a su médico asociado. En este ejemplo el paciente dispondrá de acceso desde su móvil a una aplicación web (básicamente, una página web diseñada para ser responsive con el dispositivo) en la que recibirá notificaciones en base a las lecturas que el tensiómetro portatil remita a la instancia de IRIS.

Por lo tanto tendremos dos accesos diferentes a nuestra instancia de IRIS:

  • Acceso del usuario desde aplicación móvil.
  • Acceso del dispositivo para remitir las lecturas de tensión arterial.

En este artículo veremos la primera de ellas que permite al paciente gestionar las tareas que sus lecturas vayan generando.

Conexión aplicación móvil - IRIS

Para esta conexión lo más sencillo es configurar una aplicación web dentro de IRIS y para ello accederemos desde el portal de gestión, System Administration -> Security -> Applications -> Web Applications:

A continuación en la lista que se nos muestra pulsaremos Create new application que nos abrirá una pantalla como la siguiente:

En esta pantalla configuremos los siguientes campos:

  • Name: en el que definiremos la URL que publicaremos  para dar acceso a nuestra funcionalidad desplegada en IRIS.
  • Namespace: el namespace al que queremos que esté asociada la aplicación web, esto nos permitirá posteriormente aprovechar las funcionalidades de las producciones de interoperabilidad.
  • REST: seleccionaremos esta opción ya que lo que vamos a publicar es una API de REST para permitir conexiones HTTP.
  • Dispatch Class: clase de ObjectScript que recibirá la llamada HTTP y decidirá que hacer con la misma.
  • Use JWT Authentication: marcando esta opción se nos habilitarán sobre la URL que hemos definido para nuestra aplicación los endpoint /login /logout que nos permitirá obtener un JSON Web Token para autentificar nuestras llamadas sobre IRIS.
  • Security Settings -> Allowed Authentication Methods: que marcaremos a password para securizar nuestras llamadas.

Echemos un vistazo a nuestra clase Workflow.WS.Service:

Class Workflow.WS.Service Extends %CSP.REST
{

Parameter HandleCorsRequest = 0;
Parameter CHARSET = "utf-8";
XData UrlMap [ XMLNamespace = "https://www.intersystems.com/urlmap" ]
{
<Routes>
	<Route Url="/getTasks" Method="GET" Call="GetTasks" />
    <Route Url="/saveTask" Method="POST" Call="SaveTask" />
</Routes>
}

ClassMethod OnHandleCorsRequest(url As %String) As %Status
{
	set url = %request.GetCgiEnv("HTTP_REFERER")
    set origin = $p(url,"/",1,3) // origin = "http(s)://origin.com:port"
    // here you can check specific origins
    // otherway, it will allow all origins (useful while developing only)
	do %response.SetHeader("Access-Control-Allow-Credentials","true")
	do %response.SetHeader("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS")
	do %response.SetHeader("Access-Control-Allow-Origin",origin)
	do %response.SetHeader("Access-Control-Allow-Headers","Access-Control-Allow-Origin, Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control")
	quit $$$OK
}

ClassMethod GetTasks() As %Status
{
    Try {
        Do ##class(%REST.Impl).%SetContentType("application/json")
        If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        Do ##class(%REST.Impl).%SetStatusCode("200")
        set sql = "SELECT %Actions, %Message, %Priority, %Subject, TaskStatus_TimeCreated, ID FROM EnsLib_Workflow.TaskResponse WHERE TaskStatus_AssignedTo = ? AND TaskStatus_IsComplete = 0"
        set statement = ##class(%SQL.Statement).%New(), statement.%ObjectSelectMode = 1
        set status = statement.%Prepare(sql)
        if ($$$ISOK(status)) {
            set resultSet = statement.%Execute($USERNAME)
            if (resultSet.%SQLCODE = 0) {
                set tasks = []
                while (resultSet.%Next() '= 0) {
                    set task = {"actions": "", "message": "", "priority": "", "subject": "", "creation": "", "id": ""}
                    set task.actions = resultSet.%GetData(1)
                    set task.message = resultSet.%GetData(2)
                    set task.priority = resultSet.%GetData(3)
                    set task.subject = resultSet.%GetData(4)
                    set task.creation = resultSet.%GetData(5)
                    set task.id = resultSet.%GetData(6)
                    do tasks.%Push(task)
                }                
            }
        }
        set result = {"username": ""}
        set result.username = $USERNAME
        Do ##class(%REST.Impl).%WriteResponse(tasks)
        
    } Catch (ex) {
        Do ##class(%REST.Impl).%SetStatusCode("400")
        return ex.DisplayString()
    }

    Quit $$$OK
}

ClassMethod SaveTask() As %Status
{
    Try {
        Do ##class(%REST.Impl).%SetContentType("application/json")
        If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        // Reading the body of the http call with the person data
        set dynamicBody = {}.%FromJSON(%request.Content)
        
        set task = ##class(EnsLib.Workflow.TaskResponse).%OpenId(dynamicBody.%Get("id"))
        set sc = task.CompleteTask(dynamicBody.action)

        if $$$ISOK(sc) {	        
            Do ##class(%REST.Impl).%SetStatusCode("200")
            Do ##class(%REST.Impl).%WriteResponse({"result": "success"})         
		}	
        
    } Catch (ex) {
        Do ##class(%REST.Impl).%SetStatusCode("400")
        Do ##class(%REST.Impl).%WriteResponse({"result": "error"})
    }

    Quit $$$OK
}

}

Como podéis observar, desde nuestro WS resolvemos toda la lógica que necesitamos pero no os recomiendo hacer esto, ya que perdemos la trazabilidad que podríamos conseguir enviando las peticiones recibidas a la producción configurada en el Namespace.

Echando un vistazo a la sección de URLMap veremos que tenemos 2 endpoints configurados en nuestro WS:

  • getTasks: para recuperar todas las tareas pendientes del usuario (si veis la SQL utilizada estamos pasando el nombre de usuario directamente de la variable generada al loguearse).
  • saveTask: en las que recibiremos la respuesta del usuario a la tarea y procederemos a finalizarla ejecutando el método CompleteTaks de la clase EnsLib.Workflow.TaskResponse.

Por parte de IRIS ya estaría todo configurado para que la aplicación externa se conecte.

Probando la aplicación externa con el Workflow Engine

Primeramente simularemos el envío de un mensaje de HL7 a nuestra producción copiando el fichero /shared/hl7/message_1_1.hl7 a la ruta /shared/in, veamos el mensaje que estamos enviando:

MSH|^~\&|HIS|HULP|EMPI||20240402111716||ADT^A08|346831|P|2.5.1
EVN|A08|20240402111716
PID|||07751332X^^^MI^NI~900263^^^HULP^PI||LÓPEZ CABEZUELA^ÁLVARO^^^||19560121|F|||PASEO MARIO FERNÁNDEZ 258 1 DERECHA^^MADRID^MADRID^28627^SPAIN||555819817^PRN^^ALVARO.LOPEZ@VODAFONE.COM|||||||||||||||||N|
PV1||N
OBX|1|NM|162986007^Pulso^SNM||72|^bpm|||||F|||20240402111716
OBX|2|NM|105723007^Temperatura^SNM||37|^Celsius|||||F|||20240402111716
OBX|3|NM|163030003^Presión sanguínea sistólica^SNM||142|^mmHg|||||F|||20240402111716
OBX|4|NM|163031004^Presión sanguínea diastólica^SNM||83|^mmHg|||||F|||20240402111716

Para los que no recordéis los anteriores artículos, habíamos definido en nuestro BPL que se generase una alerta cuando la presión sistólica superase los 140 o la diastólica los 90, por lo tanto, este mensaje deberá generar una tarea de aviso para nuestro paciente con DNI 07751332X y otra automática que se mantendrá abierta hasta que IRIS reciba la nueva notificación.

Veamos nuestra aplicación móvil:

Ahí tenemos 2 tareas generadas, la primera de ellas que hemos definida como manual y que depende del usuario para su finalización y la segunda que hemos declarado como automática y que para que desaparezca el usuario deberá realizar una nueva lectura con su tensiómetro. Si echamos un vistazo a la llamada HTTP podremos ver como hemos incluido el JWT en la misma :

Si el usuario pulsa en el botón Accept de la tarea pendiente el flujo se mantendrá a la espera de recibir la lectura del tensiómetro y no avanzará a los siguiente pasos. Una vez aceptada la tarea manual y recibida una nueva lectura del tensiómetro en la que nuevamente se superan los límites marcados el sistema generará dos nuevas tareas de aviso, una para el paciente y otra para el médico asociado en el que se advierte de una posible crisis:

¡Perfecto! Ya tenemos montado nuestro sistema de avisos para los pacientes de una forma sencilla y rápida.

Conclusión

Como habéis podido ir viendo en esta serie de artículos la funcionalidad del Workflow Engine junto con las capacidades de interoperabilidad de InterSystems IRIS proveen de una potencialidad increible para la implementación de procesos de negocio que pocas otras soluciones empresariales pueden aportar. Es cierto que quizás sea necesario conocimientos técnicos para sacarle todo el jugo pero realmente merece la pena el esfuerzo requerido.

Discussão (0)1
Entre ou crie uma conta para continuar
InterSystems Oficial
· Jun. 7, 2024

¿Qué hay de nuevo en InterSystems Language Server 2.5.0? - Repost

Nota: esto fue publicado originalmente el 5 de junio de 2024, pero se presentó como si hubiera sido publicado el 9 de mayo de 2024, por lo que este repost corrige la fecha.

Las actualizaciones recientes del InterSystems Language Server introducen muchas mejoras significativas destinadas a mejorar la experiencia y la productividad del desarrollador. Aquí hablaré de algunas de las más importantes, mientras que la lista completa, que incluye numerosas correcciones de errores, se puede encontrar en el CHANGELOG del Language Server.

Descripciones detalladas para errores de sintaxis

En el pasado, todos los errores de sintaxis se informaban simplemente como "Error de sintaxis". Ahora, el error de sintaxis informado en el panel PROBLEMS de VS Code tiene el mismo detalle que encuentras al compilar en el terminal, ayudándote a identificar y resolver problemas en tu código más rápidamente. En la captura de pantalla a continuación, tengo varios errores en mi código ObjectScript. A la izquierda, en Before (antes), el servidor de lenguajes solo informaba "Error de sintaxis". Ahora, en After (después), verás que el servidor de lenguajes puede proporcionar descripciones de errores mucho más útiles en el panel PROBLEMS. Y recuerda, todo esto sucede mientras escribes, antes de la compilación. Así que es una buena idea mantener este panel visible si le soleis sacar partido a este tipo de ayudas.

Aquí podemos ver el antes (before) y después (after). 

Advertencias para palabras reservadas de SQL en clases persistentes

Ya sabéis que no debéis intentar usar palabras reservadas de SQL en nombres de clases o propiedades. Pero en caso de que lo olvidéis, o uséis una menos habitual, como LEVEL, por error, el analizador ahora os advertirá incluso antes de intentar la compilación. En la captura de pantalla a continuación, he nombrado una propiedad "unique". Esa es una palabra reservada en SQL, por lo que aparece subrayada con una advertencia y, al pasar el cursor sobre el texto subrayado, se muestra un mensaje apropiado.

Descripción emergente de la clase al pasar el cursor sobre una variable escrita

Al pasar el cursor sobre una variable, ahora veréis instantáneamente la clase que representa junto con la descripción de la clase. En la captura de pantalla a continuación, estoy pasando el cursor sobre la variable `task` en la línea 142 (de Tester.cls en el panel izquierdo). Obtengo el nombre completo de la clase seguido de la descripción proporcionada por el autor de la clase. En el panel derecho estoy mostrando la fuente de rs.pipeline.Task para que podáis ver de dónde proviene esa información.

Seguimiento de variables en procedure blocks

Si escribís rutinas, ahora recibiréis advertencias sobre variables potencialmente indefinidas en procedure blocks, al igual que desde hace tiempo ocurre con los métodos de clase. ¡Esto es muy útil para prevenir posibles errores en tiempo de ejecución que a menudo son difíciles de identificar!

 

Otras actualizaciones documentan las últimas características de IRIS, incluidas las funciones vectoriales, y añaden documentación más completa y funciones de ayuda dentro del editor. Los desarrolladores ahora pueden acceder a información detallada y ejemplos directamente dentro de su entorno de codificación, reduciendo la necesidad de cambiar de contexto y buscar recursos externos.

Estas nuevas características tienen como objetivo simplificar el proceso de codificación, reducir errores y mejorar la productividad general de los desarrolladores que usan el InterSystems Language Server. Una vez más, para obtener una lista detallada de todos los cambios y mejoras, podéis consultar el changelog completo en GitHub.

Discussão (0)1
Entre ou crie uma conta para continuar
Anúncio
· Jun. 7, 2024

[Vidéo] Mise à l'échelle du volume d'utilisateurs et de données dans InterSystems IRIS

Salut la Communauté!

Profitez de regarder la nouvelle vidéo sur la chaîne Youtube d'InterSystems France

📺 Mise à l'échelle du volume d'utilisateurs et de données dans InterSystems IRIS

Votre plate-forme de données doit soutenir votre entreprise à mesure que vous développez vos données et votre utilisation. Regardez la vidéo pour découvrir comment le logiciel InterSystems permet une mise à l'échelle horizontale et verticale.

Cette vidéo est doublée grâce à Bard, l'intelligence artificielle de Google. 

N'oubliez pas à partager vos réflexions et impressions dans des commentaires après avoir regardé cette vidéo !

Discussão (0)1
Entre ou crie uma conta para continuar