发布新帖

查找

文章
· 六月 8 阅读大约需 6 分钟

Cómo crear un agente Copilot en Teams para consultar la documentación de Intersystems

¿Usas Office 365 y Teams en tu organización? 

Si es así, estás de suerte, te voy a enseñar cómo crear un agente para Copilot que te permitirá buscar información directamente en la documentación de IRIS. Es un proceso rápido y sencillo que te ayudará a agilizar tus búsquedas.

Además, la nueva versión de Office 365 incluye una versión gratuita de Copilot que puedes descargar y anclar fácilmente en Teams. 

Ve al menú Aplicaciones, busca Copilot y haz clic en Agregar. En ese momento comenzará la instalación de Copilot dentro de Teams. 

En la imagen verás el botón Abrir, ya que en mi caso Copilot ya está instalado.

En este momento, ya tienes tu Copilot activo y listo para usar. También puedes encontrarlo con el nombre de Copilot Chat, ya que utiliza el modelo GPT-4 de OpenAI, concretamente la versión GPT-4-turbo.

Haz clic derecho sobre el ícono de Copilot y selecciona Anclar para tenerlo siempre disponible cada vez que abras Teams.

Si te preocupa la seguridad, no te preocupes: este Copilot está protegido mediante lo que Microsoft denomina protección de datos de empresa. 


Todos los documentos que subas, así como las consultas que realices, no se utilizan para entrenar el modelo.  Además, todos los archivos adjuntos se almacenan exclusivamente en tu cuenta de OneDrive. 


Te dejo el enlace a la documentación oficial para que puedas consultarlo con más detalle y quedarte más tranquilo: 
https://learn.microsoft.com/en-us/copilot/microsoft-365/enterprise-data-protection

Puede que Copilot no sea tu IA favorita, y que pienses que hay otras más potentes para ciertas tareas. Es una discusión larga y con muchos matices: está claro que las IAs han evolucionado y se han especializado mucho, pero para el caso de uso que voy a explicar, esta versión de Copilot Chat es más que suficiente. Lo mejor de todo es que aprovecha las herramientas que ya tienes en tu organización, sin necesidad de contratar ni configurar nada adicional.

Ya sabes: 
Si la vida te da limones, crea un agente con la documentación de Intersystems. Y si lo que quieres es un gin-tonic, tu IA favorita estará encantada de que pases por caja y te dé exactamente lo que necesitas.

Ahora vamos a generar nuestro propio agente para acceder a la documentación de Intersystems. 


En la parte derecha de la pantalla verás una barra de herramientas que muestra los agentes activos. Si es la primera vez que accedes, lo más probable es que esta lista esté vacía.  Tendrás dos opciones: 

- Obtener agentes 
- Crear agente

Usaremos esta segunda opción, ya que actualmente no existe un agente específico para Intersystems.


 

Date una vuelta por los agentes predefinidos; seguramente encontrarás alguno muy interesante de alguna aplicación que ya estés utilizando, como Jira o Confluence.  Estos agentes son versiones especializadas de Copilot, diseñadas para interactuar con aplicaciones o servicios específicos. Cada agente está configurado para: 

  • Conectarse a una fuente de datos o aplicación concreta (por ejemplo, Jira, Confluence, etc.). 
  • Entender el contexto y el lenguaje específico de esa aplicación (como términos como “ticket”, “issue”, “repositorio”, etc.). 
  • Ejecutar acciones dentro de esa plataforma, como crear tareas, buscar información o generar informes.

Os pongo unos ejemplos prácticos de los dos agentes que tengo actualmente en mi Copilot: uno obtenido directamente de los predefinidos y otro que os enseñaré a crear para Intersystems.

Agente Jira Cloud:

  • Entiende términos como “sprint”, “backlog” e “issue”.
  • Puede buscar tareas asignadas a ti.
  • Puede crear un nuevo ticket con solo una instrucción en lenguaje natural.

Agente Intersystems Docs:

  • Busca la documentación técnica de Intersystems.
  • Puede responder preguntas como “¿cómo se configura un namespace?” o “¿qué es un global?”.

_____________________________________

Ahora, empecemos a generar nuestro agente. Dentro del apartado Copilot en Teams, nos dirigimos a la sección Crear Agente.

En ese momento aparecerá un menú donde podrás generar tu agente simplemente indicándole, con una breve descripción, qué quieres que haga

Con tres sencillas propuestas, Copilot nos genera nuestro agente, mostrando a la derecha cómo va quedando nuestro prompt

Si en lugar de utilizar la pestaña “Describir” vamos a la pestaña “Configurar”, podremos realizar estos pasos manualmente y modificar aspectos como el icono o las solicitudes de inicio de nuestro prompt.

Cuando ya tengas todo a tu gusto, presiona el botón Crear y automáticamente se generará tu nuevo agente.

Inicialmente, este agente será solo para tu uso, pero podrás configurarlo para que esté disponible para usuarios específicos de tu organización o para todos.

Al finalizar el proceso, ya tendrás tu agente disponible en la barra de herramientas de la derecha. Solo queda validar que funciona correctamente.

Para ello, selecciona la propuesta:

“¿Me puedes decir los parámetros de la función $ZDT?”

Si todo está configurado correctamente, el agente te mostrará la información correspondiente de la documentación y, además, te propondrá un enlace directo a la documentación oficial de Intersystems de donde ha obtenido la información.

Y así, puedes hacer tantas consultas como se te ocurran, por ejemplo:

“¿Cómo se genera un nuevo namespace?”

También puedes pedirle que te proporcione instrucciones de código, pero ten cuidado: la IA siempre te devolverá una respuesta, aunque no siempre sea correcta.

Es fundamental que revises y valides todo lo que te diga; nunca des por válida una respuesta sin comprobarla previamente. Este agente está pensado para ahorrarte tiempo buscando en toda la documentación, pero no siempre acierta, especialmente cuando se trata de generar código. En particular, el ObjectScript todavía le cuesta un poco.

A partir de aquí, ya tienes tu agente para buscar en la documentación de Intersystems.

Pero no te quedes solo con la documentación oficial: puedes añadir más enlaces donde buscar información. Por ejemplo, puedes incluir:

  • La URL de la comunidad de Intersystems para consultar artículos.
  • La página de aprendizaje, donde encontrarás información sobre los cursos disponibles relacionados con tus dudas.
  • El Open Exchange.
  • El portal de ideas.

Añade todo lo que consideres que realmente te puede ayudar. Eso sí, empieza con las fuentes que de verdad vas a utilizar.

No siempre “más es mejor”, ya que las IA pueden confundirse si les das demasiada información. Ponle las cosas fáciles al principio y ve añadiendo más fuentes a medida que veas que esta versión de Copilot resuelve tus dudas sobre Intersystems de manera efectiva.

Para concluir, crear tu propio agente Copilot en Teams para consultar la documentación de Intersystems es una forma práctica y eficiente de aprovechar las herramientas que ya tienes a tu alcance.

No olvides que la clave está en personalizar tu agente con las fuentes que realmente te sean útiles y validar siempre la información que recibes.

Con un poco de práctica, verás cómo agilizas tu trabajo diario y resuelves dudas mucho más rápido.

¡Anímate a experimentar y a sacarle el máximo provecho a esta tecnología que tienes a tu disposición!

讨论 (0)1
登录或注册以继续
文章
· 六月 7 阅读大约需 18 分钟

Usando Python no InterSystems Iris

Olá,

Neste artigo vamos ver o uso do python como linguagem de programação no InterSystems Iris. Para tal vamos usar como referência a versão Community 2025.1 que está disponível para ser baixada em https://download.intersystems.com mediante o login no ambiente. Para maiores informações sobre o download e instalação do Iris veja o link da comunidade https://community.intersystems.com/post/how-download-and-install-intersystems-iris

Uma vez instalado o íris agora precisamos ter o python disponível no nosso ambiente. Temos vários tutoriais explicando a instalação e configuração do python no Iris. Uma boa fonte de referência é o link da documentação oficial da InterSystems em https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=AFL_epython

Uma vez instalado e configurado o python no Iris podemos fazer um primeiro teste: abrir o shell do python via terminal do Iris. Para isso vamos abrir uma janela de terminal do Iris e executar o comando Do $$SYSTEM.Python.Shell():

Fig. 1 – Tela do shell python no Iris

Se você estiver com seu ambiente configurado corretamente você verá a tela acima. A partir daí podemos então executar comandos, como por exemplo, ver a versão do python. Para isso vamos usar o módulo sys:

Fig. 2 – Tela do shell python no Iris

Pronto. Temos o Iris e o python prontos para trabalhar. Agora podemos criar, por exemplo, uma classe Iris e nela programar alguns métodos utilizando python. Vamos ver um exemplo:

Class Demo.Pessoa Extends %Persistent
{

Property nome As %String;

Method MeuNome() [ Language = objectscript ]
{
              write ..nome
}

Method MeuNomePython() [ Language = python ]
{
              print(self.nome)
}


}

 A classe acima te uma propriedade (nome) e dois métodos, um em objectscript e outro em python, apenas para comparação. Chamando estes métodos temos o resultado na tela abaixo:

 

Fig. 3 – Chamada de método no Iris

 

 

Veja então que podemos ter na mesma classe métodos codificados em objectscript e em python. E de um deles podemos chamar o outro. Veja o exemplo a seguir. Vamos criar um novo método GetChave e do método MeuNomePython vamos fazer uma chamada e recuperar a informação:


Method MeuNomePython() [ Language = python ]
{
              chave=self.GetChave()
              print(self.nome)
              print(chave)
}

Method GetChave() As %Integer [ Language = objectscript ]
{
              Return $Get(^Chave)
}

Vamos criar uma global com o valor que desejamos que seja recuperado:

Fig. 4 – Criação de global no Iris

 

Pronto. Com a global criada vamos agora chamar o nosso método:

Fig. 5 – Chamada de método no Iris

 

Veja que agora nosso método em python faz uma chamada a outro método, este codificado em objectscript. O inverso também é válido.

Python tem diversas bibliotecas úteis, como por exemplo:

  • iris – Permite interação com o banco de dados e  ambiente Iris
  • matplot – Visualização de dados e criação de gráficos
  • numpy -  Provê suporte a arrays e estrutura de dados
  • scikit-learn – Permite criar e implementar models de aprendizado de máquina
  • pandas – É utilizada para manipulação e análise de dados

Uma outra funcionalidade presente no Iris com python é a possibilidade de acessar dados via SQL, ou seja, podemos ter os dados armazenados em tabelas no Iris e código em python consumindo estes dados. Vamos ver um exemplo de código que lê uma tabela Iris e gera um arquivo XLS utilizando a biblioteca iris e pandas:

ClassMethod tabela() As %Status [ Language = python ]
{

              import iris
              import pandas as pd
             
              rs = iris.sql.exec("select * from demo.alunos")
              df = rs.dataframe()

              # Salvar o DataFrame como um arquivo XLS
              caminho_arquivo = 'c:\\temp\\dados.xlsx'
              df.to_excel(caminho_arquivo, index=False)
             
              return True
}

Como visto, utilizamos no código as bibliotecas iris e pandas. Então criamos um recordset (rs) com o comando SQL desejado e depois disso um dataframe pandas (df) a partir deste recordset. A partir do dataframe exportamos os dados da tabela para um arquivo Excel no caminho especificado (df.to_excel). Veja que com pouquíssimas linhas montamos um código extremamente útil. Aqui o uso das bibliotecas python foi fundamental. Elas já nos forneceram o suporte ao dataframe (pandas) e a partir daí a sua manipulação (to_excel). Executando nosso código temos então a tabela excel gerada a partir dos dados da tabela:

Fig. 6 – Chamada de método no Iris

 

Fig. 7 – Planilha gerada pelo método

 

 

Python tem diversas bibliotecas prontas para uso, com diversas funcionalidades, assim como muito código em comunidades que podem ser utilizados nas aplicações.

Uma delas, que mencionamos acima, é a scikit-learn, que permite o uso de diversos mecanismos de regressão, permitindo a criação de métodos de predição baseado em informações, como por exemplo, uma regressão linear. Podemos ver um exemplo de código de regressão abaixo:

 ClassMethod CalcularRegressaoLinear() As %String [ Language = python ]
{
    import iris
    import json

    import pandas as pd
    from sklearn.linear_model import LinearRegression
    from sklearn.metrics import mean_absolute_error
    import numpy as np
    import matplotlib
    import matplotlib.pyplot as plt
    matplotlib.use("Agg")
    
    rs = iris.sql.exec("select venda as x, temperatura as y from estat.fabrica")
    df = rs.dataframe()
    
    print(df)
    
    # Reformatando x1 para uma matriz 2D exigida pelo scikit-learn
    X = df[['x']]
    y = df['y']

    # Inicializa e ajusta o modelo de regressão linear
    model = LinearRegression()
    model.fit(X, y)

    # Extrai os coeficientes da regressão
    coeficiente_angular = model.coef_[0]
    intercepto = model.intercept_
    r_quadrado = model.score(X, y)
    
    # Calcula Y_pred baseado no X
    Y_pred = model.predict(X)
            
    # Calcula MAE
    MAE = mean_absolute_error(y, Y_pred)

    # Previsão para a linha de regressão
    x_pred = np.linspace(df['x'].min(), df['x'].max(), 100).reshape(-1, 1)
    y_pred = model.predict(x_pred)

    # Geração do gráfico de regressão
    plt.figure(figsize=(8, 6))
    plt.scatter(df['x'], df['y'], color='blue', label='Dados Originais')
    plt.plot(df['x'], df['y'], color='black', label='Linha dos Dados Originais')
    plt.scatter(df['x'], Y_pred, color='green', label='Dados Previstos')
    plt.plot(x_pred, y_pred, color='red', label='Linha da Regressão')
    plt.scatter(0, intercepto, color="purple", zorder=5, label="Ponto do intercepto")
    plt.title('Regressão Linear')
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.legend()
    plt.grid(True)

    # Salvando o gráfico como imagem
    caminho_arquivo = 'c:\\temp\\RegressaoLinear.png'
    plt.savefig(caminho_arquivo, dpi=300, bbox_inches='tight')
    plt.close()
        
    # Formata os resultados em JSON
    resultado = {
        'coeficiente_angular': coeficiente_angular,
        'intercepto': intercepto,
        'r_quadrado': r_quadrado,
        'MAE': MAE
    }

    return json.dumps(resultado)
}

 

O código lê uma tabela em Iris via SQL, cria um dataframe pandas baseado nos dados da tabela e calcula uma regressão linear, gerando um gráfico com a reta da regressão, além de trazer indicadores da regressão. Tudo isso a partir da biblioteca scikit-learn.

O artigo da comunidade https://pt.community.intersystems.com/post/usando-o-python-no-intersystems-iris-%E2%80%93-calculando-uma-regress%C3%A3o-linear-simples traz mais informações sobre o uso do scikit-learn para calcular regressão linear.

O Iris também permite o armazenamento dados vetoriais, o que abre inúmeras possibilidades. A biblioteca langchain_iris traz mecanismos que auxiliam no armazenamento e recuperação de informações em base de dados vetoriais.

O código a seguir pega um arquivo PDF e gera uma base de dados vetorial com os embeddings gerados para futura recuperação de dados:

 

ClassMethod Ingest(collectionName As %String, filePath As %String) As %String [ Language = python ]
{

    import json
    from langchain_iris import IRISVector
    from langchain_openai import OpenAIEmbeddings
    from langchain_community.document_loaders import PyPDFLoader

    try:
        apiKey = <chatgpt_api_key>
        loader = PyPDFLoader(filePath)
        splits = loader.load_and_split()
        vectorstore = IRISVector.from_documents(
            documents=splits,
            embedding=OpenAIEmbeddings(openai_api_key=apiKey),
            dimension=1536,
            collection_name=collectionName,
        )
        return json.dumps({"status": True})
    except Exception as err:
        return json.dumps({"error": str(err)})
}

 

Ao ler o arquivo PDF, o mesmo é “quebrado” em pedaços (splits) e esses pedaços são armazenados na forma de embeddings no Iris. Embeddings são vetores que representam aquele split.

Fig. 8 – Tabela com coluna do tipo vetor no Iris

 

 

Uma vez feita a ingestão do arquivo podemos agora recuperar informações e passar para a LLM gerar um texto de retorno baseado em uma pergunta formulada. A pergunta é convertida em vetores e é feita a busca no banco de dados. Os dados recuperados são então enviados a LLM que formata uma resposta. No exemplo usamos o ChatGPT:

Fig.9 – Chamada de método no Iris

 

Abaixo o código da busca realizada:

ClassMethod Retrieve(collectionName As %String, question As %String, sessionId As %String = "") [ Language = python ]
{
    import json
    import iris
    from langchain_iris import IRISVector
    from langchain_community.chat_message_histories import ChatMessageHistory
    from langchain_core.chat_history import BaseChatMessageHistory
    from langchain_core.runnables.history import RunnableWithMessageHistory
    from langchain_openai import ChatOpenAI, OpenAIEmbeddings    

    from langchain.chains import create_history_aware_retriever
    from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
    from langchain.chains import create_retrieval_chain
    from langchain.chains.combine_documents import create_stuff_documents_chain

    
    try:
        
        apiKey = <chatgpt_api_key>
        model = "gpt-3.5-turbo"
        llm = ChatOpenAI(model= model, temperature=0, api_key=apiKey)
        embeddings = OpenAIEmbeddings(openai_api_key=apiKey)

        vectorstore = IRISVector(
            embedding_function=OpenAIEmbeddings(openai_api_key=apiKey),
            dimension=1536,
            collection_name=collectionName,
        )

        retriever = vectorstore.as_retriever()

        contextualize_q_system_prompt = """Dado um histórico de bate-papo e a última pergunta do usuário \
        que pode fazer referência ao contexto no histórico do bate-papo, formule uma pergunta independente \
        que pode ser entendido sem o histórico de bate-papo. NÃO responda à pergunta, \
        apenas reformule-o se necessário e devolva-o como está."""
        
        contextualize_q_prompt = ChatPromptTemplate.from_messages(
            [
                ("system", contextualize_q_system_prompt),
                MessagesPlaceholder("chat_history"),
                ("human", "{input}"),
            ]
        )
        history_aware_retriever = create_history_aware_retriever(
            llm, retriever, contextualize_q_prompt
        )

        qa_system_prompt = """
                            Você é um assistente inteligente que responde perguntas com base em dados recuperados de uma base. Dependendo da natureza dos dados, você deve escolher o melhor formato de resposta:

                            1. **Texto:** Se os dados contêm principalmente informações descritivas ou narrativas, responda em formato de texto.
                           
                            2. **Tabela:** Se os dados contêm informações estruturadas (ex: listas, valores, métricas, comparações diretas), responda em formato HTML com o seguinte estilo:
                               - Bordas de 1px sólidas e de cor #dddddd.
                               - O cabeçalho deve ter um fundo cinza escuro (#2C3E50) e texto em branco.
                               - As células devem ter padding de 8px.
                               - As linhas pares devem ter um fundo cinza claro (#f9f9f9).
                               - As linhas devem mudar de cor ao passar o mouse sobre elas, usando a cor #f1f1f1.
                               - O texto nas células deve estar centralizado.

                            3. **Lista:** Se os dados contêm informações estruturadas (ex: listas, valores, métricas, comparações diretas), responda em formato HTML com o seguinte estilo:
                               - Bordas de 1px sólidas e de cor #dddddd.
                               - O cabeçalho deve ter um fundo cinza escuro (#2C3E50) e texto em branco.
                               - As células devem ter padding de 8px.
                               - As linhas pares devem ter um fundo cinza claro (#f9f9f9).
                               - As linhas devem mudar de cor ao passar o mouse sobre elas, usando a cor #f1f1f1.
                               - O texto nas células deve estar centralizado.
                                                        
                            4. **Gráfico:** Se os dados contêm informações que são mais bem visualizadas em um gráfico (ex: tendências, distribuições, comparações entre categorias), gere um gráfico apropriado. Inclua um título, rótulos de eixo e uma legenda quando necessário. Responda utilizando um link do quickchart.io.
                                                        
                            Contexto: {context}
                            Pergunta: {input}
                            """
        
        qa_prompt = ChatPromptTemplate.from_messages(
            [
                ("system", qa_system_prompt),
                MessagesPlaceholder("chat_history"),
                ("human", "{input}"),
            ]
        )

        question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)
        rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)

        def get_session_history(sessionId: str) -> BaseChatMessageHistory:
            rs = iris.sql.exec("SELECT * FROM (SELECT TOP 5 pergunta, resposta, ID FROM Vector.ChatHistory WHERE sessionId = ? ORDER BY ID DESC) SUB ORDER BY ID ASC", sessionId)
            history = ChatMessageHistory()
            for row in rs:
                history.add_user_message(row[0])
                history.add_ai_message(row[1])
            return history

        def save_session_history(sessionId: str, pergunta: str, resposta: str):
            iris.sql.exec("INSERT INTO Vector.ChatHistory (sessionId, pergunta, resposta) VALUES (?, ?, ?) ", sessionId, pergunta, resposta)

        conversational_rag_chain = RunnableWithMessageHistory(
            rag_chain,
            get_session_history,
            input_messages_key="input",
            history_messages_key="chat_history",
            output_messages_key="answer",
        )

        ai_msg_1 = conversational_rag_chain.invoke(
            {"input": question, "chat_history": get_session_history(sessionId).messages},
            config={
                "configurable": {"session_id": sessionId}
            },
        )
        
        save_session_history(sessionId, question, str(ai_msg_1['answer']))
        return str(ai_msg_1['answer'])
    except Exception as err:
        return str(err)
}

 

Aqui neste código entram vários aspectos que devem ser considerados neste tipo de código como o contexto da conversa, o prompt da LLM, embeddings e pesquisa vetorial.

E podemos ainda ter uma interface para realizar as chamadasao método, o que dá uma aparência mais sofisticada para a consulta. No exemplo temos uma página web acessando uma api REST que chama o método de consulta:

 

Fig. 10 – Tela de aplicação web chamando API REST no Iris

 

Estes são exemplos de uso do python no Iris. Mas o universo de bibliotecas disponíveis é muito maior. Podemos utilizar bibliotecas de reconhecimento de imagem, OCR, biometria, estatística e muito mais.

讨论 (0)1
登录或注册以继续
公告
· 六月 7

Videos for InterSystems Developers, May 2025 Recap

Hello and welcome to the May 2025 Developer Community YouTube Recap.
InterSystems Global Summit
"Code to Care" videos
How Project BEST Transforms FDA Adverse Event Reporting with FHIR
By Don Woodlock, Head of Global Healthcare Solutions, InterSystems
Streamlining Electronic Prior Authorizations with FHIR
By Don Woodlock, Head of Global Healthcare Solutions, InterSystems
More from InterSystems developers
Prompt the frontend UI for InterSystems IRIS with Lovable
By Evgeny Shvarov, Senior Manager of Developer and Startup Programs, InterSystems
SMART on FHIR: Introduction & FHIR Server Setup
By Tani Frankel, Sales Engineer Manager, InterSystems
SMART on FHIR: OAuthServer
By Tani Frankel, Sales Engineer Manager, InterSystems
SMART on FHIR: FHIR Server - OAuth Config
By Tani Frankel, Sales Engineer Manager, InterSystems
讨论 (0)1
登录或注册以继续
文章
· 六月 7 阅读大约需 6 分钟

Persistencia de sesión Oauth con token OpenID en cookie

¿Conoces a Google? Seguro que si 😄 a menudo hacemos login en webs con nuestra cuenta de Gmail por la comodidad de simplemente hacer click! sin tener que escribir email ni contraseña, esto es posible porque nuestro navegador guarda un token de acceso que nos identifica y, en este caso Google, comparte un acceso para poder consultar información de nosotros como el correo electrónico.

🔐 Existen unas pautas o proceso para hacer esta identificación de forma segura, lo que se conoce como Oauth.

En este artículo no voy a explicar cómo funciona Oauth, te mostraré cómo hacer para persistir la sesión en el Oauth de IRIS sin tener que introducir usuario y contraseña cada vez que entras y de paso cómo saltar la pantalla de aceptación de permisos.

Aquí tienes una imagen marcando el flujo que vamos a crear.

¡¡Vamos al lío!!

Como primer paso puedes montar todo un sistema de Oauth en IRIS con el proyecto https://github.com/intersystems-ib/workshop-iris-oauth2, en el archivo Readme tienes los pasos para hacerlo funcionar con Docker.

Abre desde VS code el área de trabajo del proyecto.

 

 


Crea una clase que extienda de %OAuth2.Server.Authenticate, en nuestro caso la hemos llamado cysnet.oauth.server.Authenticate.

 

 

Configura Oauth para usar la clase personalizada.


Aquí la joya de la corona, crea dos métodos en la clase personalizada.

ClassMethod LoginFromCookie(authorizationCode As %String) As %Status [ Internal, ServerOnly = 1 ]
{
    #dim sc As %Status = $$$OK
    Set currentNS = $NAMESPACE
    Try {
        // Get cookie with jwt access token
        Set cookieToken = %request.GetCookie("access_token")
        If cookieToken '= "" {
            ZNspace "%SYS"
            // Get valid access token from cookie
            Set accessToken = ##class(OAuth2.Server.AccessToken).OpenByToken(cookieToken,.sc)
            If $$$ISOK(sc) && $ISOBJECT(accessToken) {
                // Get current access token
                Set currentToken = ##class(OAuth2.Server.AccessToken).OpenByCode(authorizationCode,.sc)
                If $$$ISOK(sc) && $ISOBJECT(currentToken) {
                    // Get oauth client
                    Set client = ##class(OAuth2.Server.Client).Open(currentToken.ClientId,.sc)
                    If $$$ISOK(sc) && $ISOBJECT(client) {
                        // Skip login page
                        Set currentToken.Username = accessToken.Username
                        #dim propertiesNew As %OAuth2.Server.Properties = currentToken.Properties
                        Set key=""
                        For {
                            Set value=accessToken.Properties.ResponseProperties.GetNext(.key)
                            If key="" Quit
                            Do ..SetTokenProperty(.propertiesNew,key,value)
                        }
                        Do ##class(OAuth2.Server.Auth).AddClaimValues(currentToken, currentToken.ClientId, accessToken.Username)
                        Set currentToken.Properties = propertiesNew
                        Set currentToken.Stage = "permission"
                        Do currentToken.Save()
                        // Skip permissions page
                        Set url = %request.URL_"?AuthorizationCode="_authorizationCode_"&Accept=Aceptar"
                        Set %response.Redirect = url
                    } Else {
                        Set sc = $$$ERROR($$$GeneralError, "Error getting oauth client")
                    }
                } Else {
                    Set sc = $$$ERROR($$$GeneralError, "Error getting current token")
                }
            } Else {
                Set sc = $$$ERROR($$$GeneralError, "Error getting cookie token")
                // Clear cookie access_token
                Set %response.Headers("Set-Cookie") = "access_token=; Path=/; Max-Age=0"
            }
            ZNspace currentNS
        } Else {
            Set sc = $$$ERROR($$$GeneralError, "Error cookie access_token missing")
        }
        
    } Catch ex {
        ZNspace currentNS
        If $$$ISOK(sc) {
            Set sc = ex.AsStatus()
        }
    }
    
    Quit sc
}

ClassMethod SetTokenProperty(Output properties As %OAuth2.Server.Properties, Name, Value, Type = "string") [ Internal, ServerOnly = 1 ]
{
    // Add claims and more
    Set tClaim = ##class(%OAuth2.Server.Claim).%New()
    Do properties.ResponseProperties.SetAt(Value,Name)
    Do properties.IntrospectionClaims.SetAt(tClaim,Name)
    Do properties.UserinfoClaims.SetAt(tClaim,Name)
    Do properties.JWTClaims.SetAt(tClaim,Name)
    Do properties.IDTokenClaims.SetAt(tClaim,Name)
    Do properties.SetClaimValue(Name,Value,Type)
    Quit $$$OK
}

 

Sobrescribe el método DisplayLogin

ClassMethod DisplayLogin(authorizationCode As %String, scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties, loginCount As %Integer = 1) As %Status
{
	Set sc = ..LoginFromCookie(authorizationCode)
	If $$$ISOK(sc) {
		Quit sc
	} Else {
		$$$LOGERROR($system.Status.GetErrorText(sc))
	}

	Quit ..DisplayLogin(authorizationCode, scope, properties, loginCount)
}

 

Veamos paso por paso que hace el método LoginFromCookie, supongamos que ya hemos hecho login con anterioridad y tenemos el token JWT de OpenID en una cookie llamada access_token.

  1. Obtiene la cookie access_token.
    Set cookieToken = %request.GetCookie("access_token")
  2. Cambia al namespace %SYS para usar los métodos de las librerías de Oauth.
    ZNspace "%SYS"
  3. Obtiene el token con la cookie, este método elimina los tokens expirados, nos vale para saber que el token es válido y no ha sido modificado.
    Set accessToken = ##class(OAuth2.Server.AccessToken).OpenByToken(cookieToken,.sc)
  4. Obtiene el token recién creado para el cliente de Oauth que solicita el login de usuario.
    Set currentToken = ##class(OAuth2.Server.AccessToken).OpenByCode(authorizationCode,.sc)
  5. Obtiene el cliente de Oauth.
    Set client = ##class(OAuth2.Server.Client).Open(currentToken.ClientId,.sc)
  6. Replica el usuario y las propiedades en el token nuevo.
    Set currentToken.Username = accessToken.Username
    #dim propertiesNew As %OAuth2.Server.Properties = currentToken.Properties
    Set key=""
    For {
    	Set value=accessToken.Properties.ResponseProperties.GetNext(.key)
    	If key="" Quit
    	Do ..SetTokenProperty(.propertiesNew,key,value)
    }
    Do ##class(OAuth2.Server.Auth).AddClaimValues(currentToken, currentToken.ClientId, accessToken.Username)
    Set currentToken.Properties = propertiesNew
  7. Salta el login y guarda los cambios del token.
    Set currentToken.Stage = "permission"
    Do currentToken.Save()
      En este punto podríamos llamar al método DisplayPermissions si queremos mostrar la pantalla de aceptación de permisos.  
  8. Saltar pantalla de permisos y enviar el código de autorización al callback del cliente de oauth
    Set url = %request.URL_"?AuthorizationCode="_authorizationCode_"&Accept=Aceptar"
    Set %response.Redirect = url

 

Otros apuntes

Nos ha pasado en otros entornos con versiones anteriores a la actual de IRIS que la redirección no funciona, una posible solución a esto es devolver un javascript que lo haga:

&html<<script type="text/javascript">
    window.location.href="#(url)#";
</script>>

 

Respecto a la pantalla de permisos lo ideal sería almacenar en una persistencia: Cliente de OAuth, Usuario y Scopes aceptados, y no mostrar la pantalla si los permisos fueron aceptados con anterioridad.

Este es mi primer post publicado, espero haberlo hecho de manera clara y que sea de utilidad para llevar el Oauth de IRIS al siguiente nivel.
 

Si has llegado hasta aquí agradecerte de corazón el tiempo de leer este post y si tienes alguna duda puedes hacérmela llegar en los comentarios.

Un saludo, Miguelio, Cysnet.

讨论 (0)1
登录或注册以继续
问题
· 六月 6

Web Gateway/Apache configuration to make SOAP Client Requests?

I am having issues trying to send SOAP requests to a Cloud Based AWS Application that lives outside of our network. 

It is using a Basic Authentication, Key, Certificate Authority and Whitelist for Security. 

If I attempt the connection using wget from the command line I am able to connect,

:>wget --bind-address=10.95.129.245 --server-response https://xxxxxxxxxx/xxxxxxx/services/Mirth
--2025-06-06 15:54:51--  https://xxxxxxx/xxxxxxxx/services/Mirth
wget: /ensemble/.netrc:16: unknown token xxxxxxx
wget: /ensemble/.netrc:16: unknown token xxxxxxxx
Resolving xxxxxxx.com (xxxxxxx)... 34.233.89.102, 54.165.234.62
Connecting to hcis-staging.cbord.com (xxxxxxxx)|xxxxxxx|:443... connected.
HTTP request sent, awaiting response...
  HTTP/1.1 200 OK
  Cache-control: no-cache="set-cookie"
  Content-Type: text/html; charset=utf-8
  Date: Fri, 06 Jun 2025 19:54:51 GMT
  Server: nginx
  Set-Cookie: AWSELB=D507B3110AAE4C39FED576EDFCA8C486670B04F18A5E3BBF0AE38321B36B528F14A78096B862E1C523ADEB028C4EB54BB3C1A6750FC29A6832764251160DDA704F73127995;PATH=/
  Set-Cookie: AWSELBCORS=D507B3110AAE4C39FED576EDFCA8C486670B04F18A5E3BBF0AE38321B36B528F14A78096B862E1C523ADEB028C4EB54BB3C1A6750FC29A6832764251160DDA704F73127995;PATH=/;SECURE;SAMESITE=None
  Content-Length: 754
  Connection: keep-alive
Length: 754 [text/html]
Saving to: 'Mirth'
 

 

 

however when I attempt from a Business Operation I am getting....

06/06/2025 15:51:49.8039637 *********************
Output from Web client with SOAP action = http://xxxxxxxx/msg/SendMessage
<?xml version="1.0" encoding="UTF-8" ?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:s='http://www.w3.org/2001/XMLSchema'>
  <SOAP-ENV:Body><SendMessage xmlns="http://xxxxxxxxx/msg/"><key xsi:type="s:string">xxxxxxxxxxxxxxx</key><encodedMessage xsi:type="s:string">xxxx@EnsLib.HL7.Message</encodedMessage></SendMessage></SOAP-ENV:Body>
</SOAP-ENV:Envelope>

06/06/2025 15:51:54.8081368 *********************
Input to Web client with SOAP action = http://xxxxxxx/msg/SendMessage

ERROR #6059: Unable to open TCP/IP socket to server xxxxxxx:443
string**** SOAP client return error. method=SendMessage, action=http://xxxxxxxx/msg/SendMessage
     ERROR #6059: Unable to open TCP/IP socket to server xxxxxxxxxx:443

Within the Business Operation class, I was able to define the Local Interface it should use.

Class osuwmc.Nutrition.OSU.CBORD.Operation.CBORDHL7Port Extends Ens.BusinessOperation [ ProcedureBlock ]
{

Parameter ADAPTER = "EnsLib.SOAP.OutboundAdapter";

Property LocalInterface As %String(MAXLEN = 255);

Parameter SETTINGS = "LocalInterface:Connection:selector?context={Ens.ContextSearch/TCPLocalInterfaces}";

Method OnInit() As %Status
{
 IF '$IsObject(..Adapter.%Client.HttpRequest) {
	set ..Adapter.%Client.HttpRequest = ##class(%Net.HttpRequest).%New()
 }
 set ..Adapter.%Client.HttpRequest.LocalInterface = $ZStrip($P(..LocalInterface,"("),"*W")

	quit $$$OK
}

Method SendMessage(pRequest As osuwmc.Nutrition.OSU.CBORD.Request.SendMessageRequest, Output pResponse As osuwmc.Nutrition.OSU.CBORD.Response.SendMessageResponse) As %Library.Status
{
 Set ..Adapter.WebServiceClientClass = "CBORDHL7WSService.CBORDHL7Por.....

Do I have to do something special with the Web Gateway and Apache Web Service to allow the connection out?

2 条新评论
讨论 (2)3
登录或注册以继续