#Ensemble

0 Seguidores · 95 Postagens

InterSystems Ensemble é uma plataforma completa e fácil de usar que permite aos usuários conectarem pessoas, processos e aplicações em tempo recorde.

Saber mais

Documentação

InterSystems Oficial Danusa Calixto · Maio 31, 2024

A partir do lançamento da plataforma de dados InterSystems IRIS® 2022.3, a InterSystems corrigiu o mecanismo de execução de licença para incluir solicitações REST e SOAP. Devido a essa alteração, ambientes com licenças não baseadas em núcleo que usam REST ou SOAP podem experimentar maior utilização de licença após a atualização. Para determinar se este aviso se aplica à sua licença InterSystems, siga as instruções nas perguntas frequentes vinculadas abaixo. 

Esta tabela resume a execução:

0
0 62
Artigo Rochael Ribeiro · Abr. 8, 2024 3m read

Rubrica de perguntas frequentes da InterSystems

As definições de classe criadas pelos usuários são armazenadas em classes de definição de classe. Elas podem ser usadas para obter uma lista de definições de classes a partir de um programa.

Observação: as classes de definição de classe se referem a todas as classes contidas no pacote %Dictionary.

Na amostra de código abaixo, uma lista de definições de classe é obtida usando o método query Summary da classe %Dictionary.ClassDefinitionQuery.

Class ISJ.Utils
{
ClassMethod ClassInfo()
{
    #dim ex As %Exception.AbstractException
    try {
        set currentNS=$NAMESPACE
        while (1) {
            read "Please specify namespace: ",x
            if x'=""  quit
        }
        set $NAMESPACE=x 
        write !!
        Set statement = ##class(%SQL.Statement).%New()
        Do statement.%PrepareClassQuery("%Dictionary.ClassDefinitionQuery","Summary")
        set rs = statement.%Execute()
        while rs.%Next() {
            set name=rs.%Get("Name")
            if name["%" continue            // Skip the class with % in the name
            if $extract(name,1,3)="csp" continue  // skip csp.*
            if $extract(name,1,3)="csr" continue  // skip csr.*
            write name,!
        }
        set $NAMESPACE=currentNS
    }
    catch ex {
        write "Error occured: ",ex.DisplayString(),!
        set $NAMESPACE=$get(currentNS)
    }
}
}

Veja um exemplo de execução a seguir.

Ao executar o método da classe, será preciso especificar um namespace, então especifique o nome do namespace que você deseja referenciar.

USER>do##class(ISJ.Utils).ClassInfo()
Please specify namespace : USER

CSPX.Dashboard.BarChart CSPX.Dashboard.Chart CSPX.Dashboard.ChartSeries CSPX.Dashboard.FuelGauge

<skip>

INFORMATION.SCHEMA.VIEWTABLEUSAGE ISJ.Utils Test.JSONTest Test.Person Test.REST Test.VSCode.REST

USER>

Artigo relacionado: Como obter a lista de rotinas programaticamente

0
0 95
Artigo Larissa Prussak · Mar. 18, 2024 4m read

Ao usar o InterSystems IRIS como mecanismo de interoperabilidade, todos sabemos e amamos como é fácil usar o Message Viewer para revisar rastreamentos de mensagens e ver exatamente o que está acontecendo em sua produção. Quando um sistema lida com milhões de mensagens por dia, você pode não saber exatamente por onde começar sua investigação.

Ao longo dos meus anos apoiando as produções da IRIS, muitas vezes me peguei investigando coisas como...

  • Que tipo de rendimento esse fluxo de trabalho tem?
  • Onde está o gargalo?
  • Quais são meus erros mais comuns?
0
0 76
Artigo Larissa Prussak · Mar. 15, 2024 1m read

Rubrica de perguntas frequentes da InterSystems


Os mapas de registros são usados ​​para mapear com eficiência arquivos contendo registros delimitados ou registros de largura fixa para classes de mensagens usadas pela função de interoperabilidade e para mapear arquivos de classes de mensagens da função de interoperabilidade para arquivos de texto.

As definições de mapeamento do mapa de registros podem ser criadas usando o Portal de Gerenciamento, e também fornecemos um assistente de registro CSV que permite definir durante a leitura de um arquivo CSV.

0
0 69
Artigo Larissa Prussak · jan 3, 2024 1m read

Rubrica de perguntas frequentes da InterSystems

Se vários produtos InterSystems estiverem instalados no mesmo sistema, a versão mais recente do driver ODBC da InterSystems entre os produtos instalados permanecerá registrada no gerenciador de drivers.

Você pode mudar para qualquer driver alterando a entrada de registro abaixo.

 Observe que a execução de RegFiles.bat não altera o driver ODBC.

A entrada do registro é a seguinte.

HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI\InterSystems ODBC35 key Driver
HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI\InterSystems ODBC35 key Setup
0
1 113
Artigo Danusa Calixto · Dez. 4, 2023 1m read

InterSystems FAQ

Ao executar comandos do sistema operacional, use $ZF(-100).

do$ZF(-100,"",program,args) // Execute the Windows command [synchronously].
do$ZF(-100,"/ASYNC",program,args) // Executes a Windows command [asynchronously].

Ao executar comandos do shell do sistema operacional, como mkdir e copy, especifique também /SHELL.

do$zf(-100,"/shell /async","mkdir","c:\temp\x")

Consulte os seguintes documentos para obter detalhes:

Sobre $ZF(-100) [IRIS]
Sobre $ZF(-100)

0
0 84
Artigo Danusa Calixto · Nov. 27, 2023 3m read

Oi, pessoal,

Publiquei um novo pacote no OEX.

Resumindo, é um tipo de dados de string que oferece várias opções de configuração e flexibilidade.

Caso de negócio

  1. Você tem algumas classes de bibliotecas e outras estruturas reutilizáveis que usa em vários sistemas.
  2. Você tem um ambiente "multi-tenant" em que uma base de código é compartilhada entre vários inquilinos, sendo que cada um tem seu próprio namespace configurado.
    1. Regras específicas e processos de negócios são armazenados no namespace desse inquilino.
    2. As estruturas de classes principais são as mesmas.
    3. Os inquilinos têm diferentes requisitos de validação de dados e alguns querem a consistência dos dados em todos os sistemas da organização.

Se você tiver seis inquilinos com diferentes níveis de validação em um número de identificação (SSN), você deve acomodá-los sem precisar criar um subconjunto de classes para cada um.

Alguns inquilinos também querem que os dados sejam depurados para garantir a consistência.

Solução do problema

Uma classe de string com a opção de configurar o tempo de compilação e de execução que pode ser alterada dinamicamente.

Essa classe oferece o seguinte

  1. Defina o valor do parâmetro REGEX para a validação de expressão regular no método IsValid.
    1. Defina o REGEXMODE como "globalref", e IsValid obterá o valor de um global para usar como o padrão de expressão regular.
      1. Por exemplo, REGEXMODE = "^MyConf(""IDNumberRegex"")" Em seguida, configure o padrão nesse global e nó.
  2. Defina o valor do parâmetro DATACLEANUPSEQUENCE a ser aplicado durante o método Normalize.
    1. O valor é uma %List de %Lists que contêm as instruções para $ZSTRIP.
      1. Por exemplo, DATACLEANUPSEQUENCE = "$lb($lb(""<>WC"","""",""""),$lb(""*P"","""",""-""))"
    2. Defina DATACLEANUPMODE como "globalref", e Normalize lerá a sequência de limpeza do global indicado pelo valor em DATACLEANUPSEQUENCE. Por exemplo, DATACLEANUPSEQUENCE  = "^MyConf(""LastNameCleanup"")" Em seguida, configure a sequência nesse global e nó.
      1. Por exemplo, "^MyConf(""LastNameCleanup"") = $lb($lb("*PC","","'- "),$lb("<->W","",""))
  3. Defina o valor do parâmetro ADDITIONALVALIDATIONCALL como a chamada de classmethod que será feita durante IsValid, depois de todas as outras validações.
    1. O valor tem o formato "##class(My.ValidatorClass).MyClassMethod()"
    2. Defina ADDITIONALVALIDATIONMODE como "globalref", e IsValid lerá o método de validação a ser usado do global indicado pelo valor em ADDITIONALVALIDATIONCALL.
      1. Por exemplo, ADDITIONALVALIDATIONCALL= "^MyConf(""LastNameCleanup"")" Em seguida, configure a sequência nesse global e nó.
        1. Por exemplo, "^MyConf(""IDNumberValidator"") = "##class(BaseLib.Validators.ZAID).Validate()"

O repositório do GitHub contém uma demonstração completa dessa funcionalidade, além de exemplos de classes.

Obrigado

0
0 75
Artigo Danusa Calixto · Nov. 10, 2023 10m read

​Palavras-chave: ChatGPT, COS, Tabelas de consulta, IRIS, IA 

Objetivo

Aqui está outra pequena observação antes de seguirmos para a jornada de automação assistida por GPT-4. Confira abaixo algumas "ajudinhas" que o ChatGPT já oferece,  em várias áreas, durante as tarefas diárias. 

Saiba também quais são as possíveis lacunas, riscos e armadilhas da automação assistida por LLMs, caso você explore esse caminho. Também adoraria ouvir os casos de uso e as experiências de outras pessoas nesse campo. 

Tabelas de consulta

Uma das tarefas mais simples podem ser as tabelas de consulta.  Com frequência, precisamos transformar tabelas de código Excel ou CSV em tabelas de consulta XML de forma manual. Principalmente, quando há espaços em branco e outros elementos distribuídos de maneira aleatória nos valores em milhares de registros, e você precisa organizá-los diversas vezes antes dos prazos de publicação. 

O ChatGPT pode ajudar um pouco nisso? Aqui está um teste rápido.

Prompt:

Você é um programa que converte um arquivo CSV automaticamente em um arquivo de tabela de consulta XML.
A entrada é um arquivo CSV separado por vírgulas com 3 colunas de dados :
Blood Science ,XCOM,CANCELLED/REJECTED SAMPLE
Blood Science ,ADX ,ADD ON TEST COMMENT
Blood Transfusion,DARA,ALERT FOR DARA/CAMELLIA
Blood Transfusion, BT1 ,REJECT SAMPLE UNLABELLED
A saída deve ser um arquivo XML no seguinte formato:


<lookupTable>
     <entry table="HIE.ICE.FilterOBR4" key="XCOM">Blood Science</entry>
     <entry table="HIE.ICE.FilterOBR4" key="ADX ">Blood Science</entry>
     <entry table="HIE.ICE.FilterOB

R4" key="DARA">Blood Transfusion</entry>
     <entry table="HIE.ICE.FilterOBR4" key="BT1">Blood Transfusion</entry>
</lookupTable>
Observação: a sequência do atributo XML em cada raiz deve ser mantida

ChatGPT:

Aqui está um script em Python que lê o arquivo CSV, o processa e gera o arquivo XML:

import csv

defconvert_csv_to_xml(input_file, output_file):"""
    Convert a CSV file to an XML lookup table file.

    Parameters:
    input_file (str): Path to the input CSV file.
    output_file (str): Path to the output XML file.

    Returns:
    int: 0 if the conversion was successful, 1 if there was an error.
    """try:
        # Create a dictionary to hold the lookup table data
        lookup_table = {}

        # Read in the CSV file and process the datawith open(input_file, "r") as csvfile:
            csvreader = csv.reader(csvfile)
            for row in csvreader:
                category = row[0].strip()
                key = row[1].strip()
                value = row[2].strip()
                lookup_table[key] = category

        # Generate the XML filewith open(output_file, "w") as xmlfile:
            xmlfile.write('<?xml version="1.0"?>\n')
            xmlfile.write('<lookupTable>\n')
            for key in lookup_table:
                xmlfile.write(f'<entry table="HIE.ICE.FilterOBR4" key="{key}">{lookup_table[key]}</entry>\n')
            xmlfile.write('</lookupTable>\n')

        return0except Exception as e:
        print("Error:", e)
        return1

Esse script primeiro lê o arquivo CSV usando o módulo csv e cria um dicionário para manter os dados da tabela de consulta. Depois, ele processa cada linha do arquivo CSV e adiciona os dados relevantes ao dicionário.

Por fim, o script gera o arquivo XML ao iterar pelo dicionário e gravar as tags e os atributos XML apropriados no arquivo de saída. Veja que a sintaxe da string f é usada para inserir dinamicamente os dados da chave e da categoria nas tags XML.

Executei o script rapidamente em um Jupyter Notebook e deu certo. Ele também removeu todos os espaços em branco (seriam vários ajustes manuais). Ele ofereceu uma ferramenta para lidar com CSVs longos que têm valores confusos, sem limitação de tamanho do token. 

Nesse caso específico, eu diria que a produtividade aumentou cerca de 20 vezes em relação às abordagens manuais repetidas para lidar com várias tabelas de código confusas.

Depois de economizar um pouco de tempo, eu queria saber se ele poderia gerar uma amostra de COS para criar a classe Util para BAUs de serviços futuros etc. 

Prompt:  

Você pode converter a função em Python acima para o ClassMethod do COS da InterSystems usando a função correta específica do COS?

ChatGPT:

Com certeza! Aqui está o código convertido para o ClassMethod do ObjectScript da InterSystems:

ClassMethod ConvertCSVToXML(inputFile As%String, outputFile As%String) As%Integer
{
    try {
        // Create a dictionary to hold the lookup table dataset lookupTable = {}

        // Read in the CSV file and process the dataset csvReader = ##class(%File.CharacterStream).%New()
        do csvReader.LinkToFile(inputFile)
        while (csvReader.AtEnd = 0) {
            set row = csvReader.ReadLine()
            set category = $ZSTRIP($PIECE(row, ",", 1), "B")
            set key = $ZSTRIP($PIECE(row, ",", 2), "B")
            set value = $ZSTRIP($PIECE(row, ",", 3), "B")
            set lookupTable(key) = category
        }
        do csvReader.%Close()

        // Generate the XML fileset xmlWriter = ##class(%File.CharacterStream).%New()
        do xmlWriter.LinkToFile(outputFile)
        do xmlWriter.WriteLine("<?xml version=""1.0""?>")
        do xmlWriter.WriteLine("<lookupTable>")
        set keys = lookupTable.%GetKeys()
        while ('keys.%AtEnd) {
            set key = keys.%GetNext()
            set category = lookupTable(key)
            do xmlWriter.WriteLine("<entry table=""HIE.ICE.FilterOBR4"" key="""_key_""">"_category_"</entry>")
        }
        do xmlWriter.WriteLine("</lookupTable>")
        do xmlWriter.%Close()

        quit0
    }
    catch (ex) {
        Write"Error: "_ex.DisplayString(), !
        quit1
    }
}

No ObjectScript da InterSystems, podemos usar a classe %File.CharacterStream para ler e gravar arquivos. Usamos o método LinkToFile() para abrir os arquivos de entrada e saída, e depois usamos os métodos ReadLine() e WriteLine() para ler e gravar os dados nos arquivos.

Para remover os espaços à esquerda e à direita de cada campo, usamos a função $ZSTRIP() com a sinalização "B".

Também usamos o método %GetKeys() do objeto lookupTable para iterar as chaves no dicionário.

Por fim, o método retorna um código de status 0 se a conversão for bem-sucedida ou 1 se houver um erro.

Ele não fornece uma ferramenta correta que funciona de maneira tão imediata quanto o Python.  %File.CharacterStream deve ser  %Stream.FileCharacter, a remoção de espaços em branco deve usar "<>W", e a iteração pelos globais deve usar $ORDER etc. </p>

Os prompts podem ser melhorados, mas não sou muito bom em pedir favores, então fiz alguns ajustes rápidos durante o processo. 

ClassMethod ConvertCSVToXML2(inputFile As%String, outputFile As%String) As%Integer
{
  
  s tSC = $$$OKtry {
		set^lookupTable = ""// Read in the CSV file and process the dataset csvReader = ##class(%Stream.FileCharacter).%New()
        set tSC =  csvReader.LinkToFile(inputFile)
        while (csvReader.AtEnd = 0) {
            set row = csvReader.ReadLine()
            set category = $ZSTRIP($PIECE(row, ",", 1), "<>W")
            set key = $ZSTRIP($PIECE(row, ",", 2), "<>W")
            set value = $ZSTRIP($PIECE(row, ",", 3), "<>W")
            set^lookupTable(key) = category
        }
        //zw ^lookupTabledo csvReader.%Close()
        
        // Generate the XML fileset xmlWriter = ##class(%Stream.FileCharacter).%New()
        set tSC = xmlWriter.LinkToFile(outputFile)
        set tSC = xmlWriter.WriteLine("<?xml version=""1.0""?>")
        set tSC = xmlWriter.WriteLine("<lookupTable>")
        set key = $O(^lookupTable("")) //lookupTable.%GetKeys()while (key '= "") {
	        //w keyset category = $GET(^lookupTable(key))
            w !,key, " ", category
            set tSC =  xmlWriter.WriteLine("<entry table=""HIE.ICE.FilterOBR4"" key="""_key_""">"_category_"</entry>")
            set key = $O(^lookupTable(key))
        }
        set tSC = xmlWriter.WriteLine("</lookupTable>")
   		set tSC = xmlWriter.%Save("</lookupTable>")
        set tSC = xmlWriter.%Close()
  }
    catch (ex) {
        Write"Error: "_ex.DisplayString(), !
        s tSC = ex
    }
   return tSC
}

 

Então, quais são as lacunas detectadas aqui?  

Algumas possíveis implicações que vieram à minha mente:
1. Produtividade:  a recompensa de produtividade dependeria da proficiência do COS do desenvolvedor.  Essa ferramenta aumentaria ainda mais qualquer vantagem de proficiência em programação.  
2. Lacunas: minha pergunta seria — como podemos aumentar a precisão pela aprendizagem few-shot via prompts, com ou até mesmo sem ajustes?  Se você explora essa área em LLMs, eu adoraria ouvir suas ideias ou sonhos.  
 

Gerações automáticas de testes de unidades
 

Ao discutir o desenvolvimento, não podemos escapar dos testes.  
Parece que isso está mudando agora. Ferramentas polidas que parecem bastante "simples" e usam o poder dos GPTs, como RubberDuck, surgiram para ajudar.

Então, testei a extensão do RubberDuck no VSCode e configurei com minha chave da API OpenAI.
Depois, abri a função em Python mencionada acima no VSCode, conforme a seguir:

Em seguida, selecionei o menu "Generate Unit Test ...", que gera automaticamente uma cobertura de teste de unidade em alguns segundos, pelo menos 100 vezes mais rápido do que digitando. Ele fornece um marcador de posição rápido para ajustes.

Podemos fazer isso para o código COS também, mesmo que o RubberDuck e o ChatGPT ainda não entendam realmente o COS (e não é culpa do LLM):

Ele gerou esse marcador de posição de teste de unidade sem entender muito bem o COS — deixarei as lacunas abertas para conselhos agora e, especialmente, se ou como as lacunas precisarão ser resolvidas com um senso de propósito.

Precisaria de alguns ajustes, mas, usando o ChatGPT, parece que agora ele consegue gerar, analisar e comentar automaticamente o código, além de gerar marcadores de posição de testes de unidade para você. (Bem, mais ou menos, dependendo da linguagem de programação usada e do que queremos que ele faça em seguida).

Conclusão?


Novamente, não tenho conclusões rápidas, já que ainda não consigo explorar muito os limites e as armadilhas.  

Base matemática??


Talvez algum dia, assim como o conceito de "entropia" na teoria da informação foi criado em 1948, outro gênio da matemática possa simplesmente nos iluminar com outro conceito para quantificar as distâncias e lacunas "linguísticas" entre todos os tipos de linguagens, sejam humanas ou de máquina. Até lá, não saberemos realmente o potencial real, limites, riscos e armadilhas desses LLMs. Ainda assim, isso não parece impedir o avanço dos LLMs a cada mês ou semana. 

Outros casos de uso??


Painéis de análise baseados em consultas de linguagem natural humana: algumas semanas atrás, estava trabalhando na P&D de um projeto de inovação e, por acaso, percebi outro caso de uso:  transformar consultas clínicas em linguagem humana em uma consulta de máquina em SQL/MDX.  Na verdade, mais ou menos, sem qualquer ajuste ainda. Parece que ele está começando a aproximar linguagens humanas e tons de máquina, até certo ponto?   
Não seria difícil imaginar uma situação assim: um médico digita uma consulta clínica em um chat na sua linguagem natural e um painel clínico é gerado automaticamente, destacando os pacientes que possam ter perdido a detecção precoce da insuficiência cardíaca, além de agrupar e classificar esses pacientes em regiões, gênero e faixas etárias. As diferenças na qualidade do cuidado seriam destacadas em minutos, em vez de meses de esforços de engenharia. 

E, certamente, um assistente de IA personalizado de cuidados.  Parecia tão remoto e complexo no ano passado, mas, ao que tudo indica, se torna rapidamente uma realidade com o GPT4.  Teoricamente, nada impediria o GPT4 e os LLMs de analisarem meus registros de atendimento, apenas pedaços de dados estruturados e semiestruturados (como exames de laboratório e medicamentos), dados não estruturados (observações e relatórios clínicos etc.) e dados de imagem (raios X, tomografias e ressonâncias magnéticas), para começar a entender melhor e coordenar meus cuidados e consultas em breve.  


Ressalva 
 

Desculpe dizer o óbvio, mas o objetivo desta postagem não se trata de como podemos criar tabelas de consulta XML com rapidez, manualmente ou não. Na verdade, também sou muito bom com Excel e Notepad++. Em vez disso, o objetivo é entrar em contato com quem quer compartilhar casos de uso, reflexões, desafios, riscos e experiências na jornada das automações assistidas por LLMs etc
E o poder dos LLMs veio dos desenvolvedores, de todos os repositórios públicos e das postagens que todos fizeram em fóruns públicos. GPTs não surgem do nada. Nos últimos meses, isso ainda não foi totalmente valorizado e reconhecido.
A AGI apresenta riscos à humanidade nessa velocidade, mas, pessoalmente, me sinto um pouco sortudo, aliviado e dispensado, já que  estamos nos serviços de saúde. 
Outros riscos rotineiros incluem a conformidade de privacidade de dados de acordo com a HIPPA, o GDPR e os Acordos de Processamento de Dados.  

0
0 105
Artigo Flávio Lúcio Naves Júnior · Ago. 23, 2023 1m read

Pergunta feita várias vezes para InterSystems 

Isso pode ser obtido usando a consulta AllFields query da classe %SYS.ProcessQuery.

Para obter mais detalhes, consulte o documento Process (Job)【IRIS】Process (Job).

Um exemplo de execução no terminal é o seguinte.

1
0 124
Artigo Danusa Calixto · Ago. 7, 2023 1m read

InterSystems FAQ

Se o valor de uma variável local é um OREF ou não, pode ser determinado usando $IsObject(). Seja v a variável que você deseja verificar,

$IsObject(v)=1// v is an OREF$IsObject(v)=0// v is not an OREF$IsObject(v)=-1// v is an OREF but does not point to a valid object

Observe que $IsObject(v) dará um erro UNDEFINED se v for indefinido.

Para evitar erros UNDEFINED, é recomendado o uso do $Get assim:

$IsObject($Get(v))
0
0 100
Artigo Cristiano Silva · Ago. 7, 2023 2m read

Transformações DTL e chamadas GetValueAt/SetValueAt em mensagens HL7 truncarão todos os campos com mais de 32 K. Para evitar isso, os métodos GetFieldStreamRaw e StoreFieldStreamRaw devem ser utilizados ao lidar com campos que possam ser maiores que 32K. OBX:5 é um exemplo frequente. Esses métodos têm algumas sutilezas e devem ser usados com cuidado.

0
0 61
Artigo Cristiano Silva · Jul. 31, 2023 2m read

Ao criar Business Hosts personalizados, muitas vezes é necessário adicionar propriedades à classe para configurações adicionais que serão usadas na inicialização ou operação do host. O próprio nome da propriedade nem sempre é muito descritivo, por isso é uma vantagem ter uma exibição de legenda personalizada com o campo.

No Ensemble, era bastante simples:

TEST> Set^CacheMsg("EnsColumns","pt-br","<propriedade>") = "<legenda>"

Mas envolve um pouco mais de esforço no IRIS...

0
0 53
Pergunta Paulo Brandão · Maio 31, 2023

Bom dia pessoal.

Estou fazendo uma rotina onde no final preciso deletar um arquivo de um diretório de rede, porém para realizar essa ação, preciso utilizar um usuário específico, diferente do usuário que está executando o Iris no servidor.

É possível usar um usuário diferente, passando o usuário e senha para deletar um arquivo na rede via Iris?

2
0 144
Artigo Henrique Dias · Jun. 10, 2023 9m read

20 anos. 

Agora em 2023 eu completo 20 anos trabalhando com tecnologia, desenvolvendo sistemas, implementando, criando novas soluções e posso afirmar que trabalhar com tecnologia InterSystems fez e faz parte da minha vida. Afinal, foram 18 anos trabalhando todos os dias com essa tecnologia.

Comecei a trabalhar com o Caché 4, lá atrás em 2003, vivenciei as mudanças para o Caché 5, Caché e Ensemble 2008, 2010, 2012, 2014, 2017, 2018 e até finalmente chegarmos no InterSystems IRIS. 

4
0 205
Artigo Miqueias Santos · Jun. 7, 2023 4m read

Quando você compila rotinas ou classes no terminal, os resultados da compilação são exibidos na tela, portanto, mesmo que ocorra um erro, é fácil verificar.Se você deseja obter apenas informações de erro, precisa planejar um pouco.

O seguinte descreve como obter informações de erro do resultado da compilação em lote de rotinas/classes.

para rotinas

Para rotinas de compilação em massa em um namespace no Terminal, o método CompileAll() da classe %Library.Routine. use

0
0 108
Artigo Heloisa Paiva · Jun. 1, 2023 2m read

Esse é um artigo da página de "Perguntas frequentes" (FAQ) da InterSystems.

 1. Exportar API

a. Use $system.OBJ.Export() para especificar rotinas individuais para exportar. Por exemplo:

do $system.OBJ.Export("TEST1.mac,TEST2.mac","c:\temp\routines.xml",,.errors)

O formato que você deve especificar é: NomeDaRotina.extensão, e a extensão pode ser: mac, bas, int, inc, obj.

Os erros durante a exportação se armazenam na variável "errors".

Veja a referência da classe %SYSTEM.OBJ para mais detalhes sobre $system.OBJ.Export().

0
0 128
Artigo Cristiano Silva · Maio 12, 2023 5m read

Quem nunca deve ter passado pela seguinte situação:

Tenho uma aplicação/global de configuração que não está e não pode ser mapeada para uma classe, porém é necessário fornecer uma procedure específica para que uma ferramenta de relatório usando ODBC ou JDBC ou ainda utilização de resultset do prório IRIS, possa ter acesso aos dados e gerar o relatório.

No IRIS temos uma funcionalidade que nos permite criar uma query que pode ser acessada internamente e também ser exposta como uma stored procedure, com nossa própria lógica. Essa funcionalidade é Custom Class Query.

0
0 193
Anúncio Danusa Calixto · Maio 11, 2023

A partir de 16 de Maio, a documentação para as versões do InterSystems Caché® e InterSystems Ensemble® anteriores a 2017.1 estarão disponíveis somente em formato PDF no site da documentação da InterSystems. Instancias locais destas versões continuarão apresentando o conteúdo dinamicamente. 

0
0 181
Artigo Cristiano Silva · Abr. 27, 2023 6m read

InterSystems FAQ rubric

Neste artigo, apresentaremos como lidar com a situação: "Excluí acidentalmente uma global!"

Arquivos de backup e journals são usados para recuperar globais específicas que foram excluídas acidentalmente. A restauração é executada especificando as condições e restaurando registros do journal usando o utilitário ^ZJRNFILT. Dessa forma, você pode aplicar um backup pontual do banco de dados e até incluindo a exclusão de uma global específica para registros do journal que contêm as exclusões. Para obter mais informações sobre o utilitário ^ZJRNFILT, consulte a documentação:

0
0 98
Artigo Danusa Calixto · Mar. 27, 2023 1m read

InterSystems FAQ 

Você pode recuperar datas e tamanhos de rotina programaticamente usando a consulta RoutineList da classe %Library.Routine (ou apenas %Routine).

A consulta RoutineList tem um argumento e o nome da rotina a ser pesquisado pode ser especificado por correspondência de prefixo ou correspondência intermediária. (Para curingas, especifique * ou ?)

No exemplo a seguir. *.MAC é especificada como argumento.

SET tStatement =  ##class(%SQL.Statement).%New()
 DO tStatement.%PrepareClassQuery("%Routine" , "RoutineList") 
 SET rs = tStatement.%Execute("*.MAC",,0) 
 DO rs.%Display() 
0
0 104
InterSystems Oficial Danusa Calixto · Mar. 27, 2023

A InterSystems está empenhada em fornecer suporte de produto de alta qualidade aos clientes para todos os produtos, novos e antigos. À medida que os produtos envelhecem – o Caché agora tem 25 anos – esse suporte evoluirá.

O InterSystems IRIS foi lançado em 2018 e é o sucessor do Caché e do Ensemble. Muitos clientes do Caché/Ensemble migraram para o IRIS ou planejam fazê-lo nos próximos anos. Os clientes que continuarem a usar Caché ou Ensemble devem estar cientes do seguinte comunicado importante:

0
0 110

Responsabilidades

•             Atuar na manutenção, correção e melhorias dos sistemas da empresa, de acordo com o seu segmento de atuação.

•             Fornecer suporte e acompanhamento nas dificuldades operacionais dos sistemas, esclarecendo dúvidas de usuários.

•             Analisar solicitações efetuadas pelos clientes, como criação de relatórios, novas telas e funcionalidades.

•             Estudar melhorias e novas possibilidades sistêmicas no mercado que possam melhorar os processos internos da empresa.

•             Desenvolver aplicativos usando o Intersystems Caché e ou IRIS.

0
0 161
Artigo Danusa Calixto · jan 11, 2023 20m read

Criado por Daniel Kutac, Engenheiro de vendas, InterSystems

Parte 3. Apêndice

Explicação sobre as classes OAUTH do InterSystems IRIS

Na parte anterior da nossa série, aprendemos a configurar o InterSystems IRIS para atuar como um cliente OAUTH, além de um servidor de autorização e autenticação (pelo OpenID Connect). Nesta parte final da série, vamos descrever classes que implementam o framework OAuth 2.0 do InterSystems IRIS. Também vamos discutir casos de uso para métodos selecionados de classes de API.

As classes de API que implementam o OAuth 2.0 podem ser separadas em três grupos diferentes de acordo com a finalidade. Todas as classes são implementadas no namespace %SYS. Algumas delas são públicas (por % pacote), outras não e não devem ser chamadas diretamente pelos desenvolvedores.

Classes internas

Estas classes pertencem ao pacote OAuth2.

A tabela a seguir lista algumas classes de interesse (para uma lista completa de classes, consulte a Referência de Classes online da sua instância do Caché). Nenhuma dessas classes deve ser usada diretamente por desenvolvedores de aplicativos, exceto as listadas abaixo.

  <td>
    Descrição
  </td>
</tr>

<tr style="height:0px">
  <td>
    OAuth2.AccessToken
  </td>
  
  <td>
    PersistentOAuth2.AccessToken armazena um token de acesso do OAuth 2.0 e as informações relacionadas. É uma cópia do cliente OAUTH do token de acesso. OAuth2.AccessToken é indexado pela combinação de SessionId e ApplicationName. Portanto, apenas um escopo pode ser solicitado para cada SessionId/ApplicationName. Se uma segunda solicitação for feita com um escopo diferente e o token de acesso ainda não tiver sido concedido, o escopo na nova solicitação se tornará o escopo esperado.
  </td>
</tr>

<tr style="height:0px">
  <td>
    OAuth2.Client
  </td>
  
  <td>
    Persistente A classe OAuth2.Application descreve um cliente OAuth2 e faz referência ao servidor de autorização que usa para autorizar o aplicativo com base no RFC 6749. Um sistema cliente pode ser usado com vários servidores de autorização para diferentes aplicativos.
  </td>
</tr>

<tr style="height:0px">
  <td>
    OAuth2.Response
  </td>
  
  <td>
    Página CSP É a página de destino para respostas de um servidor de autorização do OAuth 2.0 usado de código do cliente OAuth 2.0 do InterSystems IRIS. A resposta é processada aqui e redirecionada ao alvo final.
  </td>
</tr>

<tr style="height:0px">
  <td>
    OAuth2.ServerDefinition
  </td>
  
  <td>
    Persistente Armazena informações do servidor de autorização usadas por um cliente OAUTH (essa instância do InterSystems IRIS). Podem ser definidas várias configurações de cliente para cada definição de servidor de autorização.
  </td>
</tr>

<tr style="height:0px">
  <td>
    OAuth2.Server.AccessToken
  </td>
  
  <td>
    Persistente Os tokens de acesso são gerenciados pelo OAuth2.Server.AccessToken no servidor OAUTH. A classe armazena o token de acesso e as propriedades relacionadas. Essa classe também é o meio de comunicação entre várias partes do servidor de autorização.
  </td>
</tr>

<tr style="height:0px">
  <td>
    OAuth2.Server.Auth
  </td>
  
  <td>
    Página CSP O servidor de autorização apoia o fluxo de controle de autorização para o código de autorização e os tipos de concessão implícitos conforme a especificação no RFC 6749. A classe OAuth2.Server.Auth é uma subclasse de %CSP.Page que atua como o endpoint de autorização e controla o fluxo de acordo com o RFC 6749.
  </td>
</tr>

<tr style="height:0px">
  <td>
    OAuth2.Server.Client
  </td>
  
  <td>
    Persistente OAuth2.Server.Configuration é uma classe persistente que descreve os clientes registrados com esse servidor de autorização.
  </td>
</tr>

<tr style="height:0px">
  <td>
    OAuth2.Server.Configuration
  </td>
  
  <td>
    Persistente Armazena a configuração do servidor de autorização. Todas as classes de configuração têm uma página correspondente no Portal de Gerenciamento de Sistemas onde os usuários preenchem os detalhes da configuração.
  </td>
</tr>
Nome da classe

Objetos OAuth2.Client, OAuth2.ServerDefinition, OAuth2.Server.Client e OAuth2.Configuration podem ser abertos, modificados e salvos para criar ou modificar configurações sem usar a IU. Você pode usar essas classes para manipular configurações de maneira programática.

Classes de personalização do servidor

Estas classes pertencem ao pacote %OAuth2. O pacote contém um conjunto de classes internas — utilitários. Só descrevemos as classes que podem ser usadas por desenvolvedores. Estas classes são mencionadas na página de configuração do servidor do OAuth 2.0

  <td>
    Página CSP %OAuth2.Server.Authenticate atua como uma subclasse para todas as classes Authenticate escritas por usuários, além da classe Authenticate padrão. A classe Authenticate é usada pelo endpoint de autorização no OAuth2.Server.Auth para autenticar o usuário. Essa classe permite a personalização do processo de autenticação.Os seguintes métodos talvez sejam implementados para substituir o padrão no OAuth2.Server:·        DirectLogin – use apenas quando não quiser mostrar a página de login·        DisplayLogin – implementa o formulário de login do servidor de autorização·        DisplayPermissions – implementa o formulário com uma lista de escopos solicitados Outras personalizações de aparência e visual podem ser feitas ao modificar o CSS. Os estilos CSS são definidos no método DrawStyle. loginForm é para o formulário DisplayLogin permissionForm é para o formulário DisplayPermissions
  </td>
</tr>

<tr style="height:0px">
  <td>
    %OAuth2.Server.Validate
  </td>
  
  <td>
    Página CSP Esta é a classe Validate User padrão incluída no servidor. A classe padrão usará o banco de dados do usuário da instância do Cache onde o servidor de autorização está localizado para validar o usuário. As propriedades aceitas serão o emissor (Issuer), as funções e o sub (Username). A Classe Validate User é especificada na configuração do servidor de autorização. Precisa conter um método ValidateUser, que validará uma combinação de nome de usuário/senha e retornará um conjunto de propriedades associadas ao usuário.
  </td>
</tr>

<tr style="height:0px">
  <td>
    %OAuth2.Server.Generate
  </td>
  
  <td>
    Objeto registrado O %OAuth2.Server.Generate é a classe Generate Token padrão incluída no servidor. A classe padrão gerará uma string aleatória como o token de acesso opaco. A classe Generate Token é especificada na configuração do servidor de autorização. Precisa conter um método GenerateAccessToken que será usado para gerar um token de acesso com base na array de propriedades retornada pelo método ValidateUser.
  </td>
</tr>

<tr style="height:0px">
  <td>
    %OAuth2.Server.JWT
  </td>
  
  <td>
    Objeto registrado O %OAuth2.Server.JWT é a classe Generate Token que cria um JSON Web Token incluído no servidor. A classe Generate Token é especificada na configuração do servidor de autorização. Precisa conter um método GenerateAccessToken que será usado para gerar um token de acesso com base na array de propriedades retornada pelo método ValidateUser.
  </td>
</tr>

<tr style="height:0px">
  <td>
    %OAuth2.Utils
  </td>
  
  <td>
    Objeto registrado Esta classe implementa, entre outros, o registro de várias entidades. Um código de amostra no capítulo Personalização mostra o possível uso.
  </td>
</tr>
%OAuth2.Server.Authenticate

A imagem a seguir mostra a seção correspondente da configuração do servidor de autorização OAuth 2.0

Caso você use o OpenID Connect com token de identidade formatado JWT (id_token), substitua a classe Generate Token padrão %OAuth2.Server.Generate com %OAuth2.Server.JWT na configuração ou deixe a classe Generate padrão.

Discutiremos as opções de personalização com mais detalhes mais tarde em um capítulo separado.

Classes de API públicas

As classes de API públicas são usadas por desenvolvedores de aplicativos para fornecer valores corretos para o fluxo de mensagens de aplicativos da Web, bem como para realizar a validação de tokens de acesso, introspecção e assim por diante.

Essas classes são implementadas no pacote %SYS.OAuth2. A tabela lista algumas das classes implementadas.

  <td>
    Objeto registrado A classe %SYS.OAuth2.AccessToken define as operações do cliente que permitem que um token de acesso seja usado para autorizar um servidor de recursos. O token subjacente é armazenado no OAuth2.AccessToken no banco de dados CACHESYS. OAuth2.AccessToken é indexado pela combinação de SessionId e ApplicationName. Portanto, apenas um escopo pode ser solicitado para cada SessionId/ApplicationName. Se uma segunda solicitação for feita com um escopo diferente e o token de acesso ainda não tiver sido concedido, o escopo na nova solicitação se tornará o escopo esperado.
  </td>
</tr>

<tr style="height:0px">
  <td>
    %SYS.OAuth2.Authorization
  </td>
  
  <td>
    Objeto registrado A classe %SYS.OAuth2.Authorization contém as operações usadas para autorizar um cliente ao obter um token de acesso. O token subjacente é armazenado no OAuth2.AccessToken no banco de dados CACHESYS. OAuth2.AccessToken é indexado pela combinação de SessionId e ApplicationName. Portanto, apenas um escopo pode ser solicitado para cada SessionId/ApplicationName. Se uma segunda solicitação for feita com um escopo diferente e o token de acesso ainda não tiver sido concedido, o escopo na nova solicitação se tornará o escopo esperado. Observe que essa classe está no CACHELIB e, portanto, disponível em qualquer lugar. No entanto, o armazenamento do token está no CACHESYS e, portanto, não está diretamente disponível para a maior parte do código.
  </td>
</tr>

<tr style="height:0px">
  <td>
    %SYS.OAuth2.Validation
  </td>
  
  <td>
    Objeto registrado A classe %SYS.OAuth2.Validation define os métodos usados para validar (ou invalidar) um token de acesso. O token subjacente é armazenado no OAuth2.AccessToken no banco de dados CACHESYS. OAuth2.AccessToken é indexado pela combinação de SessionId e ApplicationName. Portanto, apenas um escopo pode ser solicitado para cada SessionId/ApplicationName. Se uma segunda solicitação for feita com um escopo diferente e o token de acesso ainda não tiver sido concedido, o escopo na nova solicitação se tornará o escopo esperado.
  </td>
</tr>
%SYS.OAuth2.AccessToken

Vamos analisar alguns métodos e classes desse grupo mais a fundo.

Cada classe de aplicação cliente, que usa o token de acesso, PRECISA conferir a validade dele. Isso é feito em algum lugar no método OnPage (ou o método correspondente na página ZENMojo ou ZEN).

Este é o fragmento de código:

 // Check if we have an access token from oauth2 server
 set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,"scope1,
     scope2",.accessToken,.idtoken,.responseProperties,.error)

 // Continue with further checks if an access token exists.
 // Below are all possible tests and may not be needed in all cases.
 // The JSON object which is returned for each test is just displayed.
 if isAuthorized {
    // do whatever – call resource server API to retrieve data of interest
 }

Sempre que chamamos a API do servidor de recursos, precisamos fornecer o token de acesso. Isso é feito pelo método AddAccessToken da classe %SYS.OAuth2.AccessToken, veja o fragmento de código aqui

 set httpRequest=##class(%Net.HttpRequest).%New()
  // AddAccessToken adds the current access token to the request.
  set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken(
    httpRequest,,
    ..#SSLCONFIG,
    ..#OAUTH2APPNAME)
 if $$$ISOK(sc) {
    set sc=httpRequest.Get(.. Service API url …)
 }

No código de amostra fornecido nas partes anteriores da nossa série, é possível ver este código no método OnPreHTTP da primeira página do aplicativo (Cache1N). Esse é o melhor local para realizar a verificação do token de acesso para a página inicial do aplicativo.

ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]
{
 set scope="openid profile scope1 scope2"
    #dim %response as %CSP.Response
 if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,
    scope,.accessToken,.idtoken,.responseProperties,.error) {
      set %response.ServerSideRedirect="Web.OAUTH2.Cache2N.cls"
 }
 quit 1
}

O método IsAuthorized da classe SYS.OAuth2.AccessToken no código acima é verificar se o token de acesso válido existe e, se não existe, permite mostrar o conteúdo da página com um botão de login/link apontando para o formulário de autenticação do servidor de autorização. Caso contrário, redireciona para a segunda página, que faz o trabalho de recuperar os dados.

Podemos, no entanto, alterar o código para que fique assim:

ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]
{
 set scope="openid profile scope1 scope2"
 set sc=##class(%SYS.OAuth2.Authorization).GetAccessTokenAuthorizationCode(
    ..#OAUTH2APPNAME,scope,..#OAUTH2CLIENTREDIRECTURI,.properties)
 quit +sc
}

Essa variante tem um efeito diferente. Ao usar o método GetAccessTokenAuthorizationCode da classe %SYS.OAuth2.Authorization, navegamos diretamente até o formulário de autenticação do servidor de autenticação, sem mostrar o conteúdo da primeira página do nosso aplicativo.

Isso pode ser útil nos casos em que o aplicativo da Web é invocado de um aplicativo nativo do dispositivo móvel, onde algumas informações do usuário já foram mostradas pelo aplicativo nativo (o launcher) e não há necessidade de exibir a página da Web com um botão apontando para o servidor de autorização.

Se você usa o token JWT assinado, então precisa validar o conteúdo dele. Isso é feito pelo seguinte método:

 set valid=##class(%SYS.OAuth2.Validation).ValidateJWT(applicationName,accessToken,scope,,.jsonObject,.securityParameters,.sc)

Veja a descrição detalhada dos parâmetros do método na documentação de Referência de Classes.

Personalização

Vamos passar algum tempo descrevendo quais opções o OAUTH oferece para a personalização da IU de autenticação/autorização.

Suponha que a política da sua empresa exija um comportamento mais restritivo de concessão de escopo. Por exemplo, você pode executar um aplicativo de home banking que se conecta a vários sistemas bancários no seu banco. O banco só concede acesso ao escopo que contém informações sobre a conta bancária real que está sendo recuperada. Como o banco administra milhões de contas, é impossível definir o escopo estático para cada conta. Em vez disso, você pode gerar o escopo em tempo real — durante o processamento da autorização, como parte do código da página de autorização personalizada.

Para o propósito da demonstração, precisamos adicionar mais um escopo à configuração do servidor — veja a imagem.

Também adicionamos a referência à classe Authenticate personalizada, chamada %OAuth2.Server.Authenticate.Bank.

Então, como é a classe de autenticação bancária? Veja uma possível variante da classe. Ela melhora os formulários de autenticação e autorização com dados fornecidos pelo usuário. As informações que fluem entre os métodos BeforeAuthenticate, DisplayPermissions e AfterAuthenticate são transmitidas pela variável de propriedades da classe %OAuth2.Server.Properties

Class %OAuth2.Server.Authenticate.Bank Extends %OAuth2.Server.Authenticate
{
/// Add CUSTOM BESTBANK support for account scope.
ClassMethod BeforeAuthenticate(scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status
{
 // If launch scope not specified, then nothing to do
 If 'scope.IsDefined("account") Quit $$$OK
 // Get the launch context from the launch query parameter.
 Set tContext=properties.RequestProperties.GetAt("accno")
 // If no context, then nothing to do
 If tContext="" Quit $$$OK
    
 try {
    // Now the BestBank context should be queried.
    Set tBankAccountNumber=tContext
    // Add scope for accno. -> dynamically modify scope (no account:<accno> scope exists in the server configuration)
    // This particular scope is used to allow the same accno to be accessed via account
    // if it was previously selected by account or account:accno when using cookie support
    Do scope.SetAt("Access data for account "_tBankAccountNumber,"account:"_tBankAccountNumber)
    // We no longer need the account scope, since it has been processed.
    // This will prevent existence of account scope from forcing call of DisplayPermissions.
    Do scope.RemoveAt("account")
    
    // Add the accno property which AfterAuthenticate will turn into a response property
    Do properties.CustomProperties.SetAt(tBankAccountNumber,"account_number")
 } catch (e) {
    s ^dk("err",$i(^dk("err")))=e.DisplayString()
 }
 Quit $$$OK
}

/// Add CUSTOM BESTBANK support for account scope.
/// If account_number custom property was added by either BeforeAuthenticate (account)
/// or DisplayPermissions (account:accno), then add the needed response property.
ClassMethod AfterAuthenticate(scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status
{
 // There is nothing to do here unless account_number (account) or accno (account:accno) property exists
 try {
    // example of custom logging
    If $$$SysLogLevel>=3 {
     Do ##class(%OAuth2.Utils).LogServerScope("log ScopeArray-CUSTOM BESTBANK",%token)
    }
    If properties.CustomProperties.GetAt("account_number")'="" {
     // Add the accno query parameter to the response.
     Do properties.ResponseProperties.SetAt(properties.CustomProperties.GetAt("account_number"),"accno")
    }
 } catch (e) {
    s ^dk("err",$i(^dk("err")))=e.DisplayString()
 }
 Quit $$$OK
}

/// DisplayPermissions modified to include a text for BEST BANK account.
ClassMethod DisplayPermissions(authorizationCode As %String, scopeArray As %ArrayOfDataTypes, currentScopeArray As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status
{
 Set uilocales = properties.RequestProperties.GetAt("ui_locales")
 Set tLang = ##class(%OAuth2.Utils).SelectLanguage(uilocales,"%OAuth2Login")
 // $$$TextHTML(Text,Domain,Language)
 Set ACCEPTHEADTITLE = $$$TextHTML("OAuth2 Permissions Page","%OAuth2Login",tLang)
 Set USER = $$$TextHTML("User:","%OAuth2Login",tLang)
 Set POLICY = $$$TextHTML("Policy","%OAuth2Login",tLang)
 Set TERM = $$$TextHTML("Terms of service","%OAuth2Login",tLang)
 Set ACCEPTCAPTION = $$$TextHTML("Accept","%OAuth2Login",tLang)
 Set CANCELCAPTION = $$$TextHTML("Cancel","%OAuth2Login",tLang)
 &html<<html>>
 Do ..DrawAcceptHead(ACCEPTHEADTITLE)
 Set divClass = "permissionForm"
 Set logo = properties.ServerProperties.GetAt("logo_uri")
 Set clientName = properties.ServerProperties.GetAt("client_name")
 Set clienturi = properties.ServerProperties.GetAt("client_uri")
 Set policyuri = properties.ServerProperties.GetAt("policy_uri")
 Set tosuri = properties.ServerProperties.GetAt("tos_uri")
 Set user = properties.GetClaimValue("preferred_username")
 If user="" {
    Set user = properties.GetClaimValue("sub")
 }
 &html<<body>>
 &html<<div id="topLabel"></div>>
 &html<<div class="#(divClass)#">>
 If user '= "" {
    &html<
     <div>
     <span id="left" class="userBox">#(USER)#<br>#(##class(%CSP.Page).EscapeHTML(user))#</span>
     >
 }
 If logo '= "" {
    Set espClientName = ##class(%CSP.Page).EscapeHTML(clientName)
   &html<<span class="logoClass"><img src="#(logo)#" alt="#(espClientName)#" title="#(espClientName)#" align="middle"></span>>
 }
 If policyuri '= "" ! (tosuri '= "") {
   &html<<span id="right" class="linkBox">>
    If policyuri '= "" {
     &html<<a href="#(policyuri)#" target="_blank">#(POLICY)#</a><br>>
    }
    If tosuri '= "" {
     &html<<a href="#(tosuri)#" target="_blank">#(TERM)#</a>>
    }
   &html<</span>>
 }
 &html<</div>>
 &html<<form>>
 Write ##class(%CSP.Page).InsertHiddenField("","AuthorizationCode",authorizationCode),!
 &html<<div>>
 If $isobject(scopeArray), scopeArray.Count() > 0 {
    Set tTitle = $$$TextHTML(" is requesting these permissions:","%OAuth2Login",tLang)
   &html<<div class="permissionTitleRequest">>
    If clienturi '= "" {
     &html<<a href="#(clienturi)#" target="_blank">#(##class(%CSP.Page).EscapeHTML(clientName))#</a>>
    } Else {
     &html<#(##class(%CSP.Page).EscapeHTML(clientName))#>
    }
   &html<#(##class(%CSP.Page).EscapeHTML(tTitle))#</div>>
    Set tCount = 0
    Set scope = ""
    For {
     Set display = scopeArray.GetNext(.scope)
     If scope = "" Quit
     Set tCount = tCount + 1
     If display = "" Set display = scope
     Write "<div class='permissionItemRequest'>"_tCount_". "_##class(%CSP.Page).EscapeHTML(display)_"</div>"
    }
 }

 If $isobject(currentScopeArray), currentScopeArray.Count() > 0 {
    Set tTitle = $$$TextHTML(" already has these permissions:","%OAuth2Login",tLang)
   &html<<div>>
   &html<<div class="permissionTitleExisting">>
    If clienturi '= "" {
     &html<<a href="#(clienturi)#" target="_blank">#(##class(%CSP.Page).EscapeHTML(clientName))#</a>>
    } Else {
     &html<#(##class(%CSP.Page).EscapeHTML(clientName))#>
    }
   &html<#(##class(%CSP.Page).EscapeHTML(tTitle))#</div>>
    Set tCount = 0
    Set scope = ""
    For {
     Set display = currentScopeArray.GetNext(.scope)
     If scope = "" Quit
     Set tCount = tCount + 1
     If display = "" Set display = scope
     Write "<div class='permissionItemExisting'>"_tCount_". "_##class(%CSP.Page).EscapeHTML(display)_"</div>"
    }
   &html<</div>>
 }

 /*********************************/
 /*  BEST BANK CUSTOMIZATION      */
 /*********************************/
 try {
    If properties.CustomProperties.GetAt("account_number")'="" {
     // Display the account number obtained from account context.
     Write "<div class='permissionItemRequest'><b>Selected account is "_properties.CustomProperties.GetAt("account_number")_"</b></div>",!

     // or, alternatively, let user add some more information at this stage (e.g. linked account number)
     //Write "<div>Account Number: <input type='text' id='accno' name='p_accno' placeholder='accno' autocomplete='off' ></div>",!
    }
 } catch (e) {
    s ^dk("err",$i(^dk("err")))=e.DisplayString()
 }

 /* original implementation code continues here... */
 &html<
   <div><input type="submit" id="btnAccept" name="Accept" value="#(ACCEPTCAPTION)#"/></div>
   <div><input type="submit" id="btnCancel" name="Cancel" value="#(CANCELCAPTION)#"/></div>
    >
 &html<</form>
 </div>>
 Do ..DrawFooter()
 &html<</body>>
 &html<<html>>
 Quit 1
}

/// For CUSTOM BESTBANK we need to validate that patient entered,
/// ! javascript in this method is only needed when we let user enter some addtional data
/// within DisplayPermissions method !
ClassMethod DrawAcceptHead(ACCEPTHEADTITLE)
{
 &html<<head><title>#(ACCEPTHEADTITLE)#</title>>
 Do ..DrawStyle()
 &html<
 <script type="text/javascript">
 function doAccept()
 {
    var accno = document.getElementById("accno").value;
    var errors = "";
    if (accno !== null) {
     if (accno.length < 1) {
       errors = "Please enter account number name";
     }
    }
    if (errors) {
     alert(errors);
     return false;
    }
    
    // submit the form
    return true;
 }
 </script>
 >
 &html<</head>>
}

}

Como você pode ver, a classe %OAuth2.Server.Properties contém várias arrays, que são passadas. São elas:

·        RequestProperties — contém parâmetros da solicitação de autorização

·        CustomProperties — contêiner para a troca de dados entre o mencionado acima

·        ResponseProperties — contêiner para as propriedades serem adicionadas ao objeto de resposta JSON a uma solicitação de token

·        ServerProperties — contém propriedades compartilhadas que o servidor de autorização expõe para o código de personalização (por exemplo, logo_uri, client_uri, etc…)

Além disso, ela contém várias propriedades de "declarações", que são usadas para especificar quais declarações devem ser retornadas pelo servidor de autorização.

Para chamar essa página de autenticação corretamente, modificamos nosso código inicial da página do cliente para que fique assim:

 set scope="openid profile scope1 scope2 account"
 // this data comes from application (a form data or so...) and sets a context for our request
 // we can, through subclassing the Authenticate class, display this data to user so he/she can decide
 // whether to grant access or not
 set properties("accno")="75-452152122-5320"
 set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(
   ..#OAUTH2APPNAME,
    scope,
   ..#OAUTH2CLIENTREDIRECTURI,
    .properties,
   .isAuthorized,
    .sc)
 if $$$ISERR(sc) {
    write "GetAuthorizationCodeEndpoint Error="
   write ..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!
 }

Como você pode ver, adicionamos o escopo da conta e o nó "accno" da array de propriedades com um valor de contexto, que pode se originar em diferentes partes do nosso aplicativo. Esse valor é transmitido dentro do token de acesso ao servidor de recursos para processamento adicional.

Existe um cenário da vida real que usa a lógica descrita acima — o padrão FHIR para a troca de históricos eletrônicos de pacientes.

Depuração

O framework do OAUTH tem depuração integrada. Isso é muito útil, pois toda a comunicação entre o cliente e os servidores é criptografada. O recurso de depuração permite capturar dados de tráfego gerados pelas classes de API antes que sejam enviados pela rede. Para depurar seu código, você pode implementar uma rotina ou classe simples de acordo com o código abaixo. Você precisa implementar este código em todas as instâncias de comunicação do InterSystems IRIS! Nesse caso, é melhor fornecer um nome de arquivo que indique a função dele dentro do processo de fluxo do OAUTH. (O código de amostra abaixo é salvo como uma rotina rr.mac, mas você decide o nome.)

 // d start^rr()
start() public {
 new $namespace
 set $namespace="%sys"
 kill ^%ISCLOG
 set ^%ISCLOG=5
 set ^%ISCLOG("Category","OAuth2")=5
 set ^%ISCLOG("Category","OAuth2Server")=5
 quit
}

 // d stop^rr()
stop() public {
 new $namespace
 set $namespace="%sys"
 set ^%ISCLOG=0
 set ^%ISCLOG("Category","OAuth2")=0
 set ^%ISCLOG("Category","OAuth2Server")=0
 quit

}

 // display^rr()
display() public {
 new $namespace
 set $namespace="%sys"
 do ##class(%OAuth2.Utils).DisplayLog("c:\temp\oauth2_auth_server.log")
 quit
}

Em seguida, antes de começar a testar, abra um terminal e invoque d start^rr() em todos os nós do InterSystems IRIS (cliente, servidor de autorização ou servidor de recursos). Depois de concluído, execute d stop^rr() e d display^rr() para preencher os arquivos de log.

Resumo

Nesta série de artigos, aprendemos a usar a implementação do OAuth 2.0 do InterSystems IRIS. Começando com a demonstração simples do aplicativo cliente na parte 1, seguido pela amostra complexa descrita na parte 2. Por fim, descrevemos as classes mais importantes da implementação do OAuth 2.0 e explicamos quando elas devem ser chamadas nos aplicativos dos usuários.

Quero agradecer em especial a Marvin Tener, pela paciência infinita ao responder às minhas perguntas, às vezes idiotas, e por revisar a série.

0
0 149
Anúncio Cristiano Silva · Dez. 14, 2022

Fala pessoal!!!

Finalmente consegui um tempinho para organizar um conjunto de classes que venho utilizando alguns anos para facilitar e acabar com o trabalho repetitivo no desenvolvimento de intragrações utilizando XDBC.

Acessem o repositório do projeto no https://github.com/cristianojs/interopway

A idéia é incrementar o projeto com mais componentes, já tenho um que a massa está crescendo e jajá vai para o forno.

Quaisquer dúvidas postem aqui.

Grande abraço.

0
0 137
Artigo Danusa Calixto · Dez. 13, 2022 5m read
  1. Resumo

Ainda há sistemas legados na área da saúde que usam PB9, Delphi7 e outras linguagens. Para acelerar o processo de desenvolvimento e permitir que aplicativos de terceiros invoquem o webservice HL7 V2 integrado fornecido pelo Ensemble ou IRIS assim que possível, apresentamos aqui vários exemplos de invocação da interface SOAP HL7 V2 do Ensemble usando Java, PB9 e Delphi7.

Presumindo que EnsLib.HL7.Service.SOAPService.CLS, um Serviço integrado, é adicionado à produção e nomeado PeiXunHl7SoapIn, o sistema externo pode acessar o webservice SOAP HL7 V2 pelo seguinte endpoint.

 

 

http://localhost:57772/csp/peixunpro/EnsLib.HL7.Service.SOAPService.CLS?CfgItem=PeiXunHl7SoapIn

CfgItem=PeiXunHl7SoapIn é um parâmetro importante, PeiXunHl7SoapIn é o nome do BS (Serviço).

Com o parâmetro CfgItem, você pode acessar interfaces SOAP implementadas pela mesma classe de Serviço, mas expostas com nomes diferentes. Por exemplo, adicionando outro Serviço SOAP com o mesmo nome V2Service e acessando pelo endpoint

http://localhost:57772/csp/peixunpro/EnsLib.HL7.Service.SOAPService.CLS?CfgItem=V2Service

levará ao recém-adicionado Serviço.

Exemplos de acesso à interface SOAP usando Java, PB9 e Delphi7 são mostrados na próxima seção. Outras linguagens sem exemplos também podem chamar o Webservice SOAP V2 por esse endpoint.

Dica: preste atenção à definição do separador de segmento HL7 V2 em diferentes linguagens, que está marcado nos exemplos de código a seguir, por exemplo, PB usa char(13)+char(10) como o retorno e Delphi usa #13+#10 para a quebra de linha.

 

  1. Amostras

         2.1 Java

  1. package hl7.send;
    

    import java.rmi.RemoteException; import javax.xml.rpc.ParameterMode; import javax.xml.rpc.ServiceException; import org.apache.axis.client.Call; import org.apache.axis.client.Service; import org.apache.axis.encoding.XMLType;

    publicclassSendHl7MSG{ public String invokeRemoteFuc(){ String endpoint ="http://localhost:57772/csp/peixunpro/EnsLib.HL7.Service.SOAPService.CLS?CfgItem=PeiXunHl7SoapIn"; String result = "no result!"; Service service = new Service(); Call call; Object[] object = new Object[1]; // Notice the line break String s1 = "\r\n";

        String str=<span class="hljs-string">"MSH|^~\\&amp;|HIS|MediInfo|MediII|MediInfo|20150118162159||SIU^S12|145d03160de54b29a74eefa761ae4e05|P|2.4"</span>+s1+<span class="hljs-string">"SCH||A1002||||(原因)正常预约|||||^^^07:11~07:22^^^^07:11~07:22|||||||||||||||112211522"</span>+s1+<span class="hljs-string">"RGS|||"</span>;
        object[<span class="hljs-number">0</span>]= str;
        <span class="hljs-keyword">try</span> {
            call = (Call) service.createCall();
            call.setTargetEndpointAddress(endpoint);<span class="hljs-comment">// Remote call path</span>
            call.setOperationName(<span class="hljs-string">"Send"</span>);<span class="hljs-comment">// Method to be invoked</span>
            <span class="hljs-comment">// Set the parameters</span>
            call.addParameter(<span class="hljs-string">"Input"</span>, <span class="hljs-comment">// Parameter name</span>
                    XMLType.XSD_STRING,<span class="hljs-comment">// Parameter type:String</span>
                    ParameterMode.IN);<span class="hljs-comment">// Parameter mode:'IN' or 'OUT'</span>
            <span class="hljs-comment">// Set the return value type:</span>
            call.setReturnType(XMLType.XSD_STRING);<span class="hljs-comment">// Return type:String          </span>
            result = (String) call.invoke(object);<span class="hljs-comment">// Remote call</span>
        } <span class="hljs-keyword">catch</span> (ServiceException e) {
            e.printStackTrace();
        } <span class="hljs-keyword">catch</span> (RemoteException e) {
            e.printStackTrace();
        }
        <span class="hljs-keyword">return</span> result;
    }
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        SendHl7MSG t = <span class="hljs-keyword">new</span> SendHl7MSG();
        String result=<span class="hljs-keyword">null</span>;
        result = t.invokeRemoteFuc();
        System.out.println(result);
    }
    

    }

  1.  

2.2 PB 9

String endPoint="http://192.168.2.64:57772/csp/jhip/EnsLib.HL7.Service.SOAPService.cls?CfgItem=HISHL7SoapIn"
soapconnection lsc_conn //Obtain the SOAP class 
hl7v2servicesoap lsrv_obj   //Get wsdl                                                                                                       
lsc_conn =create soapconnection   
// Instantiating with the EndPoint provided by Ensemble, // i.e., adding the name of the CfgItem configuration 
lsc_conn.createinstance(lsrv_obj,"hl7v2servicesoap",endPoint)   
//PB使用char(13)+char(10) as a line break// Also notice that the ~ character is an escape character in PB and is also a separator in HL7, so two ~'s are used in PB to represent one singe HL7 V2 separator
string msg=""
msg="MSH|^~~\&|HIS||JHIP||20150119163230||ADT^A01^ADT_A01|8E64C7FE-7750-482E-BC58-DC358FF0A05|P|2.4||||||UTF8"+char(13)+char(10)
msg+="EVN||20150119163230|||2341^张医生|20150119163230"
msg+=char(13)+char(10)+"PID|1||685923^^^^PI~~2^^^^VN||张三^^ZhangSan||19610524000000|M||AB|开拓路^海淀区^北京^ 北京市^100085^^B||^^^^^^13868108756|^^^^^^13868108756||M^已婚||||||12^汉族||||CHN^中国|||||||||||||北京信息技术有限公司|软件工程师NK1|1||BRO||||||||||电业局|||||||||||||||||李四^^lisi|^^^^^^13723412432|四季青路7号^海淀区^北京^北京市^100097"
msg+=char(13)+char(10)+"PV1|1|I|A30600^^306007^10108&内分泌专科|U|||225^郭四|||||||7||Y|225^郭四四|84|10030791|||||||||||||||||||||||10108||20150119162910|||8000||7000DG1|1||E11.900^2型糖尿病||20160728170353|A"
mle_2.text=lsrv_obj.send(msg)            // Method call      

 

2.3 Delphi 7

procedure TForm1.Button1Click(Sender: TObject);
var
  mHttpRIO: THTTPRIO;
  mServiceSoap: HL7v2ServiceSoap;
  msg: String;
  result:String;
begin
  mHttpRIO := THTTPRIO.Create(nil);
  try// Set the URL with the CfgItem configuration item to EndPoint
    mHttpRIO.URL := 'http://192.168.2.64:57772/csp/jhip/EnsLib.HL7.Service.SOAPService.cls?CfgItem=HISHL7SoapIn';
    mHttpRIO.HTTPWebNode.UseUTF8InHeader := true// Add this line to specify that UTF-8 code transmission is used
    mHttpRIO.Converter.Encoding:='UTF-8';
    mServiceSoap := mHttpRIO as HL7v2ServiceSoap
    // The line break in Delphi is #13+#10
    msg:='MSH|^~\&|HIS||JHIP||20150119163230||ADT^A01^ADT_A01|8E64C7FE-7750-482E-BC58-DC358FF0A05|P|2.4||||||UTF8'+#13+#10;
    msg:=msg+'EVN||20150119163230|||2341^张医生|20150119163230';
    msg:=msg+#13+#10+'PID|1||685923^^^^PI~~2^^^^VN||张三^^ZhangSan||19610524000000|M||AB|开拓路^海淀区^北京^ 北京市^100085^^B|';
    msg:=msg+'|^^^^^^13868108756|^^^^^^13868108756||M^已婚||||||12^汉族||||CHN^中国|||||||||||||北京信息技术有限公司|软件工程师NK1|1||BRO||||||||||电业局|||||||||||||||||李四^^lisi|^^^^^^13723412432|四季青路7号^海淀区^北京^北京市^100097';
    msg:=msg+#13+#10+'PV1|1|I|A30600^^306007^10108&内分泌专科|U|||225^郭四|||||||7||Y|225^郭四四si|84|10030791|||||||||||||||||||||||10108||20150119162910|||8000||7000DG1|1||E11.900^2型糖尿病||20160728170353|A';
    result:=mServiceSoap.Send(msg);// Calling methods in the webService
    showmessage(result) ;
    Memo2.Text:=result;
  except
  end;

0
0 110
Artigo Danusa Calixto · Nov. 19, 2022 20m read

Criado por Daniel Kutac, Engenheiro de vendas, InterSystems

Aviso: se você ficar confuso com os URLs usados: a série original usou telas de uma máquina chamada dk-gs2016. As novas capturas de tela foram tiradas em uma máquina diferente. Você pode tratar o URL WIN-U9J96QBJSAG como se fosse o dk-gs2016 com segurança.

Parte 2. Servidor de autorização, servidor OpenID Connect

Na parte anterior desta série curta, aprendemos sobre o caso de uso simples – atuando como um cliente OAUTH[1]. Agora, é hora de levar nossa experiência a um nível completamente novo. Vamos construir um ambiente muito mais complexo, onde o InterSystems IRIS vai desempenhar todas as funções OAUTH.

Já sabemos como fazer um cliente, então vamos nos concentrar no servidor de autorização e, ainda mais, no provedor OpenID Connect[2].

Como na parte anterior, precisamos preparar o ambiente. Desta vez, será mais complicado, pois há mais partes móveis.

Antes de entrarmos nos detalhes do nosso exemplo, precisamos falar algumas palavras sobre o OpenID Connect.

Como você deve lembrar, na parte anterior, recebemos a solicitação – para ter a autorização do Google – para nos autenticarmos primeiro com o Google. A autenticação não faz parte do framework OAUTH. Na verdade, há vários frameworks de autenticação, independentes do OAUTH. Um deles é chamado OpenID. Originalmente uma iniciativa independente, ele agora aproveita a infraestrutura fornecida pelo framework OAUTH, ou seja, as estruturas de comunicação e dados. Assim, nasceu o OpenID Connect. Na verdade, muitas pessoas o chamam de OAUTH em esteroides. De fato, com o OpenID Connect, você pode não só autorizar, mas também autenticar usando interfaces bem conhecidas do framework OAUTH.

Demonstração complexa do OpenID Connect

Aproveitaremos grande parte do código do cliente da parte 1. Isso nos poupa muito trabalho, para que possamos nos concentrar na configuração do ambiente.

Pré-requisitos

Desta vez, precisamos adicionar uma infraestrutura PKI ao servidor web já existente com SSL habilitado. Precisamos de criptografia exigida pelo OpenID Connect. Se você quer autenticar alguém, precisa ter certeza absoluta de que ninguém mais pode se passar pelo agente (cliente, servidor de autenticação...) que envia os dados confidenciais pela rede. É aqui que entra a criptografia baseada em X.509.

Observação: a partir do Cache 2017.1, não é mais necessário usar certificados X.509 para gerar JWT/JWKS (JSON Web Key Set). Devido à compatibilidade com versões anteriores e simplicidade, usamos essa opção.

PKI

A rigor, não precisamos usar a infraestrutura Caché PKI, mas é mais conveniente do que usar ferramentas como openssl diretamente para gerar todos os certificados.

Não entraremos em detalhes sobre a geração de certificados aqui, pois você pode encontrá-los na documentação do InterSystems IRIS. Como resultado da geração de certificados, criaremos 3 pares de chaves públicas/privadas e certificados associados.

Vamos chamá-los de

·        root_ca (root_ca.cer) para nossa autoridade de certificação emissora

·        auth (auth.cer e auth.key) para o servidor de autorização e OpenID

·        client (client.cer e client.key) para o servidor de aplicação do cliente

Credenciais X.509

Precisamos definir credenciais X.509 em servidores individuais para que eles possam assinar e validar JSON Web Tokens (JWT) trocados durante nossa demonstração

Configuração do servidor de autenticação e autorização

Sem entrar em detalhes sobre como definir credenciais X.509, apenas mostramos uma captura de tela das credenciais da instância AUTHSERVER.

Como a imagem indica, o AUTHSERVER possui a própria chave privada e o certificado, enquanto só possui o certificado com a chave pública de CLIENT

Configuração do servidor cliente

Da mesma forma, as credenciais definidas na instância CLIENT

Aqui o CLIENTE possui a chave privada e o certificado, mas somente o certificado com chave pública de AUTHSERVER.

Configuração do servidor de recursos

Não precisamos definir credenciais X509 na instância RESSERVER em nossa configuração de exemplo.

Configuração do OAUTH

Como a configuração descrita na parte 1 desta série, precisamos configurar nossos servidores para OAUTH. Vamos começar com a instância AUTHSERVER, pois é o componente central na configuração geral do OAUTH.

AUTHSERVER

No Portal de Gerenciamento de Sistemas, acesse System Administration (Administração do Sistema) > Security (Segurança) > OAuth 2.0 > Server Configuration (Configuração do Servidor).

Clique no link do menu e preencha os itens do formulário:

·        nome do host

·        porta (opcional)

·        prefixo (opcional) – esses três campos compõem o Issuer endpoint (endpoint do emissor)

·        especifique as condições para retornar o token de atualização

·        verifique os tipos de concessão compatíveis, para nossa demonstração basta verificar todos os quatro tipos. No entanto, apenas o código de autorização é usado.

·        opcionalmente, confira o público-alvo necessário – isso adiciona a propriedade aud no código de autorização e solicitações implícitas

·        opcionalmente, confira a sessão de usuário de suporte - isso significa que o cookie httpOnly é usado pelo servidor de autorização para manter o usuário atual deste navegador conectado.  A segunda solicitação e as solicitações subsequentes do token de acesso não pedirão o nome de usuário e a senha.

·        especifique os intervalos de endpoint

·        defina os escopos compatíveis com esse servidor

·        aceite o padrão ou insira valores de opções de personalização – observação: altere o valor da classe de token Generate de %OAuth2.Server.Generate para %OAuth2.Server.JWT para que um JWT seja usado como token de acesso em vez de um token opaco.

·        forneça o nome da configuração SSL registrada para estabelecer o SSL sobre o HTTP conforme exigido pelo OAuth 2.0

·        Preencha as configurações do JSON Web Token (JWT) 

Veja esta captura de tela da configuração de exemplo

Após definir a configuração do servidor, precisamos fornecer a configuração do cliente do servidor. Na página com o formulário de configuração do servidor, clique no botão Client Configurations e pressione Create New Configuration for your CLIENT and RESSERVER instances (Criar nova configuração para as instâncias CLIENT e RESSERVER).

Esta imagem mostra a configuração do CLIENT.

Deixe a guia JWT Token vazia — com valores padrão. Como você pode ver, nós preenchemos os campos com dados sem sentido, diferente de um caso de aplicação real.

Da mesma forma, a configuração do RESSERVER

Como você pode ver, há apenas informações muito básicas necessárias para o servidor de recursos, ou seja, você precisa definir o tipo de cliente para o servidor de recursos. Com CLIENT, você precisa fornecer mais informações, o tipo de cliente (confidencial, pois nosso cliente é executado como um web app capaz de manter o cliente em segredo no servidor, e não enviar para o agente cliente).

CLIENT

No SMP, acesse System Administration (Administração do Sistema) > Security (Segurança) > OAuth 2.0 > Client Configurations (Configurações do cliente).

Clique no botão Create Server Configuration (Criar configuração de servidor), preencha o formulário e salve.

Confira se o endpoint do emissor corresponde ao valor que definimos anteriormente na instância AUTHSERVER! Você também precisa modificar os endpoints do servidor de autorização de acordo com a configuração do seu servidor web. No nosso caso, apenas incorporamos 'authserver' em cada campo de entrada.

Agora, clique no link Client Configurations (Configurações do cliente) ao lado do Issuer Endpoint (Endpoint emissor) recém-criado e clique no botão Create Client Configuration (Criar configuração de cliente).

Ótimo! Agora, temos CLIENT e AUTHSERVER configurados. Isso pode ser suficiente para muitos casos de uso, pois o servidor de recursos pode ser apenas um namespace de AUTHSERVER, já protegido. Porém, vamos considerar que queremos cobrir um caso de uso em que um médico externo está tentando recuperar dados do nosso sistema clínico interno. Portanto, para permitir que esse médico recupere os dados, queremos armazenar as informações da conta dele DENTRO do nosso servidor de recursos para auditorias e fins forenses. Nesse caso, precisamos continuar e definir as configurações em RESSERVER.

RESSERVER

No SMP, acesse System Administration (Administração do Sistema) > Security (Segurança) > OAuth 2.0 > Client Configurations (Configurações do cliente).

Clique no botão Create Server Configuration (Criar configuração de servidor), preencha o formulário e salve.

Usamos a função de descoberta, um novo recurso implementado no Cache 2017.1

Como você pode ver, essa configuração está usando os mesmos dados que a configuração correspondente na instância CLIENT.

Agora, clique no link Client Configurations (Configurações do cliente) ao lado do Issuer Endpoint (Endpoint emissor) recém-criado e clique no botão Create Client Configuration (Criar configuração de cliente).

A criação do WT a partir das credenciais X.509 não é recomendada, mas nós as usamos para a compatibilidade.

Isso! Foi um processo tedioso, mas necessário. Agora, podemos avançar e começar a programar!

Aplicativo cliente

Para manter as coisas o mais simples possível, reciclaremos grande parte do código do nosso exemplo do Google que descrevemos na parte 1.

O aplicativo cliente tem apenas duas páginas de CSP, sendo executado no aplicativo /csp/myclient, sem segurança aplicada – ele é apenas executado como usuário não autenticado.

Página 1

Class Web.OAUTH2.Cache1N Extends %CSP.Page
{

Parameter OAUTH2CLIENTREDIRECTURI = "https://dk-gs2016/client/csp/myclient/Web.OAUTH2.Cache2N.cls";

Parameter OAUTH2APPNAME = "demo client";

ClassMethod OnPage() As %Status
{
  &html<<html>

<body>
  <h1>Authenticating and Authorizing against Cache&acute; OAuth2 provider</h1>
  <p>This page demo shows how to call Cache&acute; API functions using OAuth2 authorization.
  <p>We are going to call Cache&acute; authentication and authorization server to grant our application access to data stored at another
  Cache&acute; server.
 >

  // Get the url for authorization endpoint with appropriate redirect and scopes.
  // The returned url is used in the button below.

  // DK: use 'dankut' account to authenticate!
  set scope="openid profile scope1 scope2"
  set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(
    ..#OAUTH2APPNAME,
    scope,
    ..#OAUTH2CLIENTREDIRECTURI,
    .properties,
    .isAuthorized,
    .sc)
  if $$$ISERR(sc) {
    write "GetAuthorizationCodeEndpoint Error="
    write ..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!
  } 

  &html<
  <div class="portalLogoBox"><a class="portalLogo" href="#(url)#">Authorize for <b>ISC</b></a></div>
  </body></html>>
  Quit $$$OK
}

ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]
{
  #dim %response as %CSP.Response
  set scope="openid profile scope1 scope2"
  if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,scope,.accessToken,.idtoken,.responseProperties,.error) {
    set %response.ServerSideRedirect="Web.OAUTH2.Cache2N.cls"
  }
  quit 1
}

}

Página 2

Class Web.OAUTH2.Cache2N Extends %CSP.Page
{

Parameter OAUTH2APPNAME = "demo client";

Parameter OAUTH2ROOT = "https://dk-gs2016/resserver";

Parameter SSLCONFIG = "SSL4CLIENT";

ClassMethod OnPage() As %Status
{
    &html<<html>




<body>>
    
    // Check if we have an access token from oauth2 server
    set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,"scope1 scope2",.accessToken,.idtoken,.responseProperties,.error)
    
    // Continue with further checks if an access token exists.
    // Below are all possible tests and may not be needed in all cases.
    // The JSON object which is returned for each test is just displayed.
    if isAuthorized {
        write "<h3>Authorized!</h3>",!
        
        
        // Validate and get the details from the access token, if it is a JWT.
        set valid=##class(%SYS.OAuth2.Validation).ValidateJWT(..#OAUTH2APPNAME,accessToken,"scope1 scope2",,.jsonObject,.securityParameters,.sc)
        if $$$ISOK(sc) {
            if valid {
                write "Valid JWT"_"<br>",!    
            } else {
                write "Invalid JWT"_"<br>",!    
            }
            write "Access token="
            do jsonObject.%ToJSON()
            write "<br>",!
        } else {
            write "JWT Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
        }
        write "<br>",!

        // Call the introspection endpoint and display result -- see RFC 7662.
        set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection(..#OAUTH2APPNAME,accessToken,.jsonObject)
        if $$$ISOK(sc) {
            write "Introspection="
            do jsonObject.%ToJSON()
            write "<br>",!
        } else {
            write "Introspection Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
        }
        write "<br>",!
        
        if idtoken'="" {
            // Validate and display the IDToken -- see OpenID Connect Core specification.
            set valid=##class(%SYS.OAuth2.Validation).ValidateIDToken(
                ..#OAUTH2APPNAME,
                idtoken,
                accessToken,,,
                .jsonObject,
                .securityParameters,
                .sc)
            if $$$ISOK(sc) {
                if valid {
                    write "Valid IDToken"_"<br>",!    
                } else {
                    write "Invalid IDToken"_"<br>",!    
                }
                write "IDToken="
                do jsonObject.%ToJSON()
                write "<br>",!
            } else {
                write "IDToken Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
            }
        } else {
            write "No IDToken returned"_"<br>",!
        }
        write "<br>",!
    
        // not needed for the application logic, but provides information about user that we can pass to Delegated authentication
    
        // Call the userinfo endpoint and display the result -- see OpenID Connect Core specification.
        set sc=##class(%SYS.OAuth2.AccessToken).GetUserinfo(
            ..#OAUTH2APPNAME,
            accessToken,,
            .jsonObject)
        if $$$ISOK(sc) {
            write "Userinfo="
            do jsonObject.%ToJSON()
            write "<br>",!
        } else {
            write "Userinfo Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
        }
        write "<p>",!

        /***************************************************
        *                                                  *
        *   Call the resource server and display result.   *
        *                                                  *
        ***************************************************/
                
        // option 1 - resource server - by definition - trusts data coming from authorization server,
        //     so it serves data to whoever is asking
        //  as long as access token passed to resource server is valid
        
        // option 2 - alternatively, you can use delegated authentication (OpenID Connect) 
        //  and call into another CSP application (with delegated authentication protection)
        //  - that's what we do here in this demo
        
        
        write "<4>Call resource server (delegated auth)","</h4>",!
        set httpRequest=##class(%Net.HttpRequest).%New()
        // AddAccessToken adds the current access token to the request.
        set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken(
            httpRequest,,
            ..#SSLCONFIG,
            ..#OAUTH2APPNAME)
        if $$$ISOK(sc) {
            set sc=httpRequest.Get(..#OAUTH2ROOT_"/csp/portfolio/oauth2test.demoResource.cls")
        }
        if $$$ISOK(sc) {
            set body=httpRequest.HttpResponse.Data
            if $isobject(body) {
                do body.Rewind()
                set body=body.Read()
            }
            write body,"<br>",!
        }
        if $$$ISERR(sc) {
            write "Resource Server Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
        }
        write "<br>",!
    
        write "<h4>Call resource server - no auth, just token validity check","</h4>",!
        set httpRequest=##class(%Net.HttpRequest).%New()
        // AddAccessToken adds the current access token to the request.
        set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken(
            httpRequest,,
            ..#SSLCONFIG,
            ..#OAUTH2APPNAME)
        if $$$ISOK(sc) {
            set sc=httpRequest.Get(..#OAUTH2ROOT_"/csp/portfolio2/oauth2test.demoResource.cls")
        }
        if $$$ISOK(sc) {
            set body=httpRequest.HttpResponse.Data
            if $isobject(body) {
                do body.Rewind()
                set body=body.Read()
            }
            write body,"<br>",!
        }
        if $$$ISERR(sc) {
            write "Resource Server Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
        }
        write "<br>",!
    } else {
        write "Not Authorized!<p>",!
        write "<a href='Web.OAUTH2.Cache1N.cls'>Authorize me</a>"
    }    
    &html<</body></html>>
    Quit $$$OK
}

}

As seguintes capturas de tela retratam o processamento:

Página de login do servidor de autenticação do OpenID Connect/autorização na instância AUTHSERVER

Página de consentimento do usuário em AUTHSERVER

E, por fim, a página resultante

Como você pode ver, lendo o código, realmente quase não há diferença em relação ao código do cliente que mostramos na parte 1. Há algo novo na página 2. São algumas informações de depuração e a verificação da validade do JWT. Depois de validar o JWT retornado, podemos realizar a introspeção dos dados do AUTHSERVER sobre a identidade do usuário. Simplesmente apresentamos essas informações nos resultados da página, mas podemos fazer mais com elas. Como no caso de uso de um médico externo mencionado acima, podemos usar as informações de identidade e transmiti-las ao servidor de recursos para fins de autenticação, se necessário. Ou apenas passar essa informação como um parâmetro para a chamada da API ao servidor de recursos.

Os próximos parágrafos descrevem como usamos as informações de identidade do usuário em mais detalhes.

Aplicativo de recurso

O servidor de recursos pode ser o mesmo servidor que o servidor de autorização/autenticação e, muitas vezes, esse é o caso. No entanto, na nossa demonstração, criamos para os dois servidores instâncias do InterSystems IRIS separadas.

Então, temos dois casos possíveis, como trabalhar com o contexto de segurança no servidor de recursos.

Alternativa 1 — sem autenticação

Esse é o caso simples. O servidor de autorização/autenticação são apenas a mesma instância do Caché. Nesse caso, podemos simplesmente transmitir o token de acesso a um aplicativo CSP, que é criado especialmente para um único propósito — enviar dados a aplicativos clientes que usam o OAUTH para autorizar a solicitação de dados.

A configuração do aplicativo CSP de recurso (chamamos de /csp/portfolio2) pode parecer com a captura de tela abaixo.

Colocamos o mínimo de segurança na definição do aplicativo, permitindo que apenas a página CSP específica seja executada.

Como opção, o servidor de recursos pode fornecer uma API REST em vez de páginas da Web clássicas. Em situações reais, o refinamento do contexto de segurança depende do usuário.

Um exemplo de código-fonte:

Class oauth2test.demoResource Extends %CSP.Page
{

ClassMethod OnPage() As %Status
{
    set accessToken=##class(%SYS.OAuth2.AccessToken).GetAccessTokenFromRequest(.sc)
    if $$$ISOK(sc) {
        set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection("RESSERVER resource",accessToken,.jsonObject)
        if $$$ISOK(sc) {        
            // optionally validate against fields in jsonObject

            w "<p><h3>Hello from Cach&eacute; server: <i>/csp/portfolio2</i> application!</h3>"
            w "<p>running code as <b>$username = "_$username_"</b> with following <b>$roles = "_$roles_"</b> at node <b>"_$p($zu(86),"*",2)_"</b>."
        }
    } else {
        w "<h3>NOT AUTHORIZED!</h3>"    
        w "<pre>"
        w
        i $d(%objlasterror) d $system.OBJ.DisplayError()
        w "</pre>"
    }
    Quit $$$OK
}

}

Alternativa 2 — autenticação delegada

Esse é outro caso extrema, queremos usar a identidade do usuário no servidor de recursos o máximo possível, como se o usuário estivesse trabalhando com o mesmo contexto de segurança que os usuários internos do servidor de recursos.

Uma das opções possíveis é usar a autenticação delegada.

Para essa definição funcionar, precisamos concluir mais algumas etapas para configurar o servidor de recursos.

·        Ativar a autenticação delegada

·        Fornecer a rotina ZAUTHENTICATE

·        Configurar o Web application (no nosso caso, chamamos em /csp/portfolio)

A implementação da rotina ZAUTHENTICATE é bastante simples e direta, já que confiamos no AUTHSERVER que forneceu a identidade do usuário e o escopo (perfil de segurança) dele, então basta aceitar o nome de usuário e transmitir com o escopo ao banco de dados de usuários do servidor de recursos (com a tradução necessária entre o escopo do OAUTH e as funções do InterSystems IRIS). É isso. O resto é realizado perfeitamente pelo InterSystems IRIS.

Veja o exemplo de uma rotina ZAUTHENTICATE

#include %occErrors
#include %occInclude

ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC
{
    set tRes=$SYSTEM.Status.OK()
    try {        
        set Properties("FullName")="OAuth account "_Username
        //set Properties("Roles")=Credentials("scope")
        set Properties("Username")=Username
        //set Properties("Password")=Password
        // temporary hack as currently we can't pass Credentials array from GetCredentials() method
        set Properties("Password")="xxx"    // we don't really care about oauth2 account password
        set Properties("Roles")=Password
    } catch (ex) {
        set tRes=$SYSTEM.Status.Error($$$AccessDenied)
    }
    quit tRes
}

GetCredentials(ServiceName,Namespace,Username,Password,Credentials) Public 
{
    s ts=$zts
    set tRes=$SYSTEM.Status.Error($$$AccessDenied)        

     try {
         If ServiceName="%Service_CSP" {
            set accessToken=##class(%SYS.OAuth2.AccessToken).GetAccessTokenFromRequest(.sc)
            if $$$ISOK(sc) {
                set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection("RESSERVER resource",accessToken,.jsonObject)
                if $$$ISOK(sc) {
                    // todo: watch out for potential collision between standard account and delegated (openid) one!
                    set Username=jsonObject.username
                    set Credentials("scope")=$p(jsonObject.scope,"openid profile ",2)
                    set Credentials("namespace")=Namespace
                    // temporary hack
                    //set Password="xxx"
                    set Password=$tr(Credentials("scope")," ",",")
                    set tRes=$SYSTEM.Status.OK()
                } else {
                    set tRes=$SYSTEM.Status.Error($$$GetCredentialsFailed) 
                }
            }    
        } else {
            set tRes=$SYSTEM.Status.Error($$$AccessDenied)        
        }
     } catch (ex) {
         set tRes=$SYSTEM.Status.Error($$$GetCredentialsFailed)
    }
    Quit tRes
}

A própria página CSP pode ser bastante simples:

Class oauth2test.demoResource Extends %CSP.Page
{

ClassMethod OnPage() As %Status
{
    // access token authentication is performed by means of Delegated authentication!
    // no need to do it, again, here

    // This is a dummy resource server which just gets the access token from the request and
    // uses the introspection endpoint to ensure that the access token is valid.
    // Normally the response would not be security related, but would contain some interesting
    // data based on the request parameters.
    w "<p><h3>Hello from Cach&eacute; server: <i>/csp/portfolio</i> application!</h3>"
    w "<p>running code as <b>$username = "_$username_"</b> with following <b>$roles = "_$roles_"</b> at node <b>"_$p($zu(86),"*",2)_"</b>."
    Quit $$$OK
}

}

Por fim, a configuração do Web application para /csp/portfolio

Se você estiver muito paranoico, pode definir Classes permitidas como fizemos na primeira variante. Ou, novamente, use a API REST. No entanto, isso está muito além do escopo do nosso tema.

Na próxima vez, vamos explicar classes individuais, apresentadas pelo framework OAUTH do InterSystems IRIS. Descreveremos as APIs e quando/onde chamá-las.

 

[1] Quando mencionamos o OAUTH, queremos dizer o OAuth 2.0 conforme especificado no RFC 6749 - https://tools.ietf.org/html/rfc6749. Usamos a abreviação OAUTH apenas por simplicidade.

[2] O OpenID Connect é mantido pela OpenID Foundation – http://openid.net/connect

0
0 218
Pergunta Guilherme Koerber · Maio 5, 2022

Olá comunidade!

Estou criando um script para remover um item (componente) da produção do Ensemble, sei que existe a forma manual de fazer isso mas como são vários componentes a ideia é utilizar um script para ser mais rápido.

Tentei utilizar  o %Delete() e fazer um select na Ens.Config.Item, porém isso acaba gerando vários erros na produção. Alguém tem alguma ideia de como posso fazer isso de forma simples? 

3
0 175
Artigo Fernando Ferreira · Mar. 18, 2022 8m read

Olá comunidade! Nesta parte do artigo temos um cenário onde o nosso ambiente InterSystems Caché/Ensemble possui um ou mais servidores com Shadow e/ou Mirror.

Como comentado no início do artigo, componentes de um software possuem uma evolução natural e outros componentes são deprecados. E uma tecnologia muito utilizada pelos nossos clientes que está deprecada no InterSystems IRIS é o Shadow (esta informação está na página 18 do documento InterSystems IRIS Adoption Guide que volto a recomendar que você faça o download no WRC).

0
1 319
Job Angelo Bruno Braga · Mar. 10, 2022

Olá Desenvolvedores !

Segue uma oportunidade para bolsas de estudo remuneradas: 

Duas bolsas de estudo para área de tecnologia da informação:
•    Valor de R$ 7.373,10  durante 24 meses
•    https://inovahc.hc.fm.usp.br/oportunidades/
•    Código da vaga: HC04 Desenvolvedor
•    Código da vaga: HC05 Desenvolvedor Integrador

Desenvolvedor e Desenvolvedor Integrador

Requisitos:

0
0 240