Dears, good morning
O que vem a seguir
<p>
No próximo artigo, vamos falar sobre a criação da configuração de CD que usa o contêiner Docker do InterSystems IRIS.
</p>
</div>
</div>
</div>
InterSystems Caché é um sistema de gerenciamento de banco de dados (DBMS) multimodelo e servidor de aplicações. Veja mais detalhes aqui.
Dears, good morning
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() 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:
Pessoal, alguém indica um conector de dados do caché com o google data studio?
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.
Nesta série de artigos, quero apresentar e discutir várias abordagens possíveis para o desenvolvimento de software com tecnologias da InterSystems e do GitLab. Vou cobrir tópicos como:
Nessa série de artigos, discuti abordagens gerais de entrega contínua. É um tema extremamente vasto e essa série de artigos precisa ser vista mais como uma coleção de receitas do que algo definitivo. Se você deseja automatizar o desenvolvimento, os testes e a entrega do seu aplicativo, a entrega contínua em geral e o GitLab em particular é o melhor caminho. A entrega contínua e os contêineres permitem que você personalize seu fluxo de trabalho conforme necessário.
Nesta série de artigos, quero apresentar e discutir várias abordagens possíveis para o desenvolvimento de software com tecnologias da InterSystems e do GitLab. Vou cobrir tópicos como:
No primeiro artigo, abordamos os fundamentos do Git, por que um entendimento de alto nível dos conceitos do Git é importante para o desenvolvimento de software moderno e como o Git pode ser usado para desenvolver software.
No segundo artigo, abordamos o fluxo de trabalho do GitLab: um processo inteiro do ciclo de vida do software e a entrega contínua.
No terceiro artigo, abordamos a instalação e configuração do GitLab e a conexão dos seus ambientes a ele
No quarto artigo, escrevemos uma configuração de CD.
No quinto artigo, falamos sobre contêineres e como (e por que) eles podem ser usados.
No sexto artigo, vamos discutir os principais componentes necessários para executar um pipeline de entrega contínua com contêineres e como eles trabalham juntos.
Neste artigo, criaremos a configuração de entrega contínua discutida nos artigos anteriores.
Na nossa configuração de entrega contínua:
Ou em formato gráfico:

Vamos começar.
Primeiro, precisamos criar nossa imagem.
Nosso código seria, como sempre, armazenado no repositório, a configuração de CD em gitlab-ci.yml. No entanto, além disso (para aumentar a segurança), armazenaríamos vários arquivos específicos do servidor em um servidor de compilação.
Contém o código dos hooks de CD. Foi desenvolvido no artigo anterior e disponibilizado no GitHub. É uma pequena biblioteca para carregar código, executar vários hooks e testar código. Como alternativa preferencial, você pode usar submódulos git para incluir este projeto ou algo semelhante no seu repositório. Os submódulos são melhores porque é mais fácil mantê-los atualizados. Uma outra alternativa seria marcar as versões no GitLab e carregá-las com o comando ADD.
Chave de licença. Como alternativa, ela pode ser baixada durante a compilação do contêiner em vez de armazenada em um servidor. É bastante arriscado armazenar no repositório.
Arquivo contendo a senha padrão. Novamente, é bastante arriscado armazená-lo no repositório. Além disso, se você estiver hospedando um ambiente de produção em um servidor separado, ele poderá ter uma senha padrão diferente.
O script inicial:
Ativa a autenticação do SO
Carrega GitLab.xml
Inicializa as configurações do utilitário GitLab
Carrega o código
set sc = ##Class(Security.System).Get("SYSTEM",.Properties) write:('sc) $System.Status.GetErrorText(sc) set AutheEnabled = Properties("AutheEnabled") set AutheEnabled = $zb(+AutheEnabled,16,7) set Properties("AutheEnabled") = AutheEnabled set sc = ##Class(Security.System).Modify("SYSTEM",.Properties) write:('sc) $System.Status.GetErrorText(sc) zn "USER" do ##class(%SYSTEM.OBJ).Load(##class(%File).ManagerDirectory() _ "GitLab.xml","cdk") do ##class(isc.git.Settings).setSetting("hooks", "MyApp/Hooks/") do ##class(isc.git.Settings).setSetting("tests", "MyApp/Tests/") do ##class(isc.git.GitLab).load() halt
Observe que a primeira linha é deixada em branco de maneira intencional.
Como algumas configurações podem ser específicas do servidor, elas não são armazenadas no repositório, mas separadamente. Se o hook inicial for sempre o mesmo, você pode simplesmente armazená-lo no repositório.
Agora, para a configuração da entrega contínua:
build image:
stage: build
tags:
- test
script:
- cp -r /InterSystems/mount ci
- cd ci
- echo 'SuperUser' | cat - pwd.txt load_ci.script > temp.txt
- mv temp.txt load_ci.script
- cd ..
- docker build --build-arg CI_PROJECT_DIR=$CI_PROJECT_DIR -t docker.domain.com/test/docker:$CI_COMMIT_REF_NAME .O que está acontecendo aqui?
Primeiro, como o docker build pode acessar apenas subdiretórios de um diretório de compilação base — na raiz do repositório do nosso caso, precisamos copiar nosso diretório "secreto" (aquele com GitLab.xml, iris.key, pwd.txt e load_ci.script) no repositório clonado.
Em seguida, o primeiro acesso ao terminal requer um usuário/senha, então nós os adicionamos a load_ci.script (por isso a linha vazia no início de load_ci.script).
Por fim, criamos a imagem do docker e a marcamos adequadamente: docker.domain.com/test/docker:$CI_COMMIT_REF_NAME
onde $CI_COMMIT_REF_NAME é o nome de um branch atual. Observe que a primeira parte da tag de imagem deve ter o mesmo nome do nome do projeto no GitLab, para que possa ser vista na guia GitLab Registry (instruções sobre a marcação estão disponíveis na guia Registry).
A criação da imagem docker é feita usando o Dockerfile:
FROM docker.intersystems.com/intersystems/iris:2018.1.1.611.0 ENV SRC_DIR=/tmp/src ENV CI_DIR=$SRC_DIR/ci ENV CI_PROJECT_DIR=$SRC_DIR COPY ./ $SRC_DIR RUN cp $CI_DIR/iris.key $ISC_PACKAGE_INSTALLDIR/mgr/ \ && cp $CI_DIR/GitLab.xml $ISC_PACKAGE_INSTALLDIR/mgr/ \ && $ISC_PACKAGE_INSTALLDIR/dev/Cloud/ICM/changePassword.sh $CI_DIR/pwd.txt \ && iris start $ISC_PACKAGE_INSTANCENAME \ && irissession $ISC_PACKAGE_INSTANCENAME -U%SYS < $CI_DIR/load_ci.script \ && iris stop $ISC_PACKAGE_INSTANCENAME quietly
Começamos a partir do contêiner básico iris.
Primeiro, copiamos nosso repositório (e diretório "secreto") dentro do contêiner.
Em seguida, copiamos a chave de licença e GitLab.xml para o diretório mgr.
Em seguida, alteramos a senha para o valor de pwd.txt. Observe que pwd.txt é excluído nessa operação.
Depois disso, a instância é iniciada e load_ci.script é executado.
Por fim, a instância iris é interrompida.
Veja o registro do job (parcial, os registros de carregamento/compilação foram ignorados):
Running with gitlab-runner 10.6.0 (a3543a27) on docker 7b21e0c4 Using Shell executor... Running on docker... Fetching changes... Removing ci/ Removing temp.txt HEAD is now at 5ef9904 Build load_ci.script From http://gitlab.eduard.win/test/docker 5ef9904..9753a8d master -> origin/master Checking out 9753a8db as master... Skipping Git submodules setup $ cp -r /InterSystems/mount ci $ cd ci $ echo 'SuperUser' | cat - pwd.txt load_ci.script > temp.txt $ mv temp.txt load_ci.script $ cd .. $ docker build --build-arg CI_PROJECT_DIR=$CI_PROJECT_DIR -t docker.eduard.win/test/docker:$CI_COMMIT_REF_NAME . Sending build context to Docker daemon 401.4kB Step 1/6 : FROM docker.intersystems.com/intersystems/iris:2018.1.1.611.0 ---> cd2e53e7f850 Step 2/6 : ENV SRC_DIR=/tmp/src ---> Using cache ---> 68ba1cb00aff Step 3/6 : ENV CI_DIR=$SRC_DIR/ci ---> Using cache ---> 6784c34a9ee6 Step 4/6 : ENV CI_PROJECT_DIR=$SRC_DIR ---> Using cache ---> 3757fa88a28a Step 5/6 : COPY ./ $SRC_DIR ---> 5515e13741b0 Step 6/6 : RUN cp $CI_DIR/iris.key $ISC_PACKAGE_INSTALLDIR/mgr/ && cp $CI_DIR/GitLab.xml $ISC_PACKAGE_INSTALLDIR/mgr/ && $ISC_PACKAGE_INSTALLDIR/dev/Cloud/ICM/changePassword.sh $CI_DIR/pwd.txt && iris start $ISC_PACKAGE_INSTANCENAME && irissession $ISC_PACKAGE_INSTANCENAME -U%SYS < $CI_DIR/load_ci.script && iris stop $ISC_PACKAGE_INSTANCENAME quietly ---> Running in 86526183cf7c . Waited 1 seconds for InterSystems IRIS to start This copy of InterSystems IRIS has been licensed for use exclusively by: ISC Internal Container Sharding Copyright (c) 1986-2018 by InterSystems Corporation Any other use is a violation of your license agreement %SYS> 1 %SYS> Using 'iris.cpf' configuration file This copy of InterSystems IRIS has been licensed for use exclusively by: ISC Internal Container Sharding Copyright (c) 1986-2018 by InterSystems Corporation Any other use is a violation of your license agreement 1 alert(s) during startup. See messages.log for details. Starting IRIS Node: 39702b122ab6, Instance: IRIS Username: Password: Load started on 04/06/2018 17:38:21 Loading file /usr/irissys/mgr/GitLab.xml as xml Load finished successfully. USER> USER> [2018-04-06 17:38:22.017] Running init hooks: before [2018-04-06 17:38:22.017] Importing hooks dir /tmp/src/MyApp/Hooks/ [2018-04-06 17:38:22.374] Executing hook class: MyApp.Hooks.Global [2018-04-06 17:38:22.375] Executing hook class: MyApp.Hooks.Local [2018-04-06 17:38:22.375] Importing dir /tmp/src/ Loading file /tmp/src/MyApp/Tests/TestSuite.cls as udl Compilation started on 04/06/2018 17:38:22 with qualifiers 'c' Compilation finished successfully in 0.194s. Load finished successfully. [2018-04-06 17:38:22.876] Running init hooks: after [2018-04-06 17:38:22.878] Executing hook class: MyApp.Hooks.Local [2018-04-06 17:38:22.921] Executing hook class: MyApp.Hooks.Global Removing intermediate container 39702b122ab6 ---> dea6b2123165 [Warning] One or more build-args [CI_PROJECT_DIR] were not consumed Successfully built dea6b2123165 Successfully tagged docker.domain.com/test/docker:master Job succeeded
Estou usando o executor GitLab Shell, e não o executor Docker. O executor Docker é usado quando você precisa de algo de dentro da imagem, por exemplo, você está criando um aplicativo Android em um contêiner java e precisa apenas de um apk. No nosso caso, precisamos de um contêiner inteiro e, para isso, precisamos do executor Shell. Então, estamos executando comandos do Docker pelo executor GitLab Shell.
Temos nossa imagem, agora vamos executá-la. No caso de ramificações de recursos, podemos simplesmente destruir o contêiner antigo e iniciar o novo. No caso do ambiente, podemos executar um container temporário e substituir o contêiner do ambiente caso os testes tenham êxito (isso fica como um exercício para o leitor).
Aqui está o script.
destroy old:
stage: destroy
tags:
- test
script:
- docker stop iris-$CI_COMMIT_REF_NAME || true
- docker rm -f iris-$CI_COMMIT_REF_NAME || trueEsse script destrói o contêiner em execução no momento e é sempre bem-sucedido (por padrão, o docker falha se tentar parar/remover um contêiner inexistente).
Em seguida, iniciamos a nova imagem e a registramos como um ambiente. Contêiner Nginx faz proxy automático de solicitações usando a variável de ambiente VIRTUAL_HOST e a diretiva de exposição (para saber em qual porta fazer o proxy).
run image:
stage: run
environment:
name: $CI_COMMIT_REF_NAME
url: http://$CI_COMMIT_REF_SLUG. docker.domain.com/index.html
tags:
- test
script:
- docker run -d
--expose 52773
--env VIRTUAL_HOST=$CI_COMMIT_REF_SLUG.docker.eduard.win
--name iris-$CI_COMMIT_REF_NAME
docker.domain.com/test/docker:$CI_COMMIT_REF_NAME
--log $ISC_PACKAGE_INSTALLDIR/mgr/messages.logVamos fazer alguns testes.
test image:
stage: test
tags:
- test
script:
- docker exec iris-$CI_COMMIT_REF_NAME irissession iris -U USER "##class(isc.git.GitLab).test()"Por fim, vamos publicar nossa imagem no registro
publish image:
stage: publish
tags:
- test
script:
- docker login docker.domain.com -u dev -p 123
- docker push docker.domain.com/test/docker:$CI_COMMIT_REF_NAMEO usuário/código pode ser transmitido usando variáveis secretas do GitLab.
Agora, podemos ver a imagem no GitLab:

E outros desenvolvedores podem a extrair do registro. Na guia de ambientes, todos os ambientes estão disponíveis para a fácil navegação:

Nessa série de artigos, discuti abordagens gerais de entrega contínua. É um tema extremamente vasto e essa série de artigos precisa ser vista mais como uma coleção de receitas do que algo definitivo. Se você deseja automatizar o desenvolvimento, os testes e a entrega do seu aplicativo, a entrega contínua em geral e o GitLab em particular é o melhor caminho. A entrega contínua e os contêineres permitem que você personalize seu fluxo de trabalho conforme necessário.
É isso. Espero que eu tenha abordado os conceitos básicos da entrega contínua e dos contêineres.
Há vários tópicos sobre os quais não falei (talvez mais tarde), especialmente em relação a contêineres:
Nesta série de artigos, quero apresentar e discutir várias abordagens possíveis para o desenvolvimento de software com tecnologias da InterSystems e do GitLab. Vou cobrir tópicos como:
No primeiro artigo, abordamos os fundamentos do Git, por que um entendimento de alto nível dos conceitos do Git é importante para o desenvolvimento de software moderno e como o Git pode ser usado para desenvolver software.
No segundo artigo, abordamos o fluxo de trabalho do GitLab: um processo inteiro do ciclo de vida do software e a entrega contínua.
No terceiro artigo, abordamos a instalação e configuração do GitLab e a conexão dos seus ambientes a ele
No quarto artigo, escrevemos uma configuração de CD.
No quinto artigo, falamos sobre contêineres e como (e por que) eles podem ser usados.
Neste artigo, vamos discutir os principais componentes necessários para executar um pipeline de entrega contínua com contêineres e como eles trabalham juntos.
A configuração deve ser assim:

Aqui podemos ver a separação de três etapas principais:
Nas partes anteriores, a criação era frequentemente incremental — calculamos a diferença entre o ambiente e a codebase atuais e modificamos nosso ambiente para corresponder à codebase. Com contêineres, cada build é completo. O resultado de um build é uma imagem que pode ser executada em qualquer lugar com dependências.
Depois que nossa imagem é criada e aprovada nos testes, ela é carregada no registro — servidor especializado para hospedar imagens docker. Então, é possível substituir a imagem anterior pela mesma tag. Por exemplo, devido ao novo commit para o master branch, nós construímos a nova imagem (project/version:master) e, se os testes funcionarem, podemos substituir a imagem no registro pela nova com a mesma tag, então todos que extraírem project/version:master obtêm uma nova versão.
Por fim, nossas imagens são implantadas. Uma solução de CI, como o GitLab, pode controlar isso ou um orquestrador especializado, mas o ponto é o mesmo: algumas imagens são executadas, verificadas periodicamente quanto à integridade e atualizadas se uma nova versão estiver disponível.
Confira o webinar docker explicando esses diferentes estágios.
Como alternativa, do ponto de vista do commit:

Na nossa configuração de entrega:
Para fazer isso, precisamos do seguinte:
Primeiro de tudo, precisamos executar o docker em algum lugar. Recomendo começar com um servidor mais convencional tipo o Linux, como Ubuntu, RHEL ou Suse. Não use distribuições voltadas para a nuvem, como CoreOS, RancherOS etc. — elas não são destinadas a iniciantes. Não se esqueça de trocar o driver de armazenamento para devicemapper.
Em caso de grandes implantações, usar ferramentas de orquestração de contêineres, como Kubernetes, Rancher ou Swarm, pode automatizar a maioria das tarefas, mas não vamos discuti-las (pelo menos nesta parte).
Esse é o primeiro contêiner que precisamos executar e é um aplicativo do lado do servidor escalável e sem estado que armazena e permite distribuir imagens Docker.
Use o registro se quiser:
Veja a documentação do registro.
Observação: o GitLab inclui registro integrado. Você pode executá-lo em vez do registro externo. Leia os documentos do GitLab vinculados neste parágrafo.
Para conectar seu registro ao GitLab, você precisará executar seu registro com suporte HTTPS — eu uso o Let's Encrypt para obter os certificados e segui este Gist para obter e transmitir os certificados a um contêiner. Depois de garantir a disponibilidade do registro em HTTPS (você pode verificar no navegador), siga estas instruções sobre como conectar o registro ao GitLab. Essas instruções diferem com base no que você precisa e na sua instalação do GitLab. No meu caso, a configuração foi adicionar o certificado do registro e a chave (com o nome adequado e as permissões corretas) a /etc/gitlab/ssl e estas linhas a /etc/gitlab/gitlab.rb:
registry_external_url 'https://docker.domain.com' gitlab_rails['registry_api_url'] = "https://docker.domain.com"
Depois de reconfigurar o GitLab, pude ver a nova guia do Registro, com informações sobre como marcar corretamente as imagens recém-criadas para que elas apareçam aqui.

Na nossa configuração de Entrega Contínua, construímos automaticamente uma imagem por branch e, se a imagem passar nos testes, ela é publicada no registro e executada automaticamente. Para que nosso aplicativo fique disponível em todos os "estados" de maneira automática, por exemplo, podemos acessar:
Para isso, precisamos de um nome de domínio e adicionamos um registro DNS curinga que aponta *.docker.domain.com ao endereço IP de docker.domain.com. Outra opção seria usar portas diferentes.
Como temos várias ramificações de recursos, precisamos redirecionar os subdomínios automaticamente para o contêiner correto. Para fazer isso, podemos usar o Nginx como proxy reverso. Veja aqui um guia.
Para começar a trabalhar com contêineres, você pode usar a linha de comando ou uma das interfaces GUI. Há várias disponíveis, por exemplo:
Eles permitem que você crie contêineres e os gerencie a partir da GUI em vez da CLI. Veja como o Rancher aparenta:

Como antes, para executar scripts em outros servidores, precisaremos instalar o runner do GitLab. Discuti isso no terceiro artigo.
Você precisará usar o executor Shell, e não o executor Docker. O executor Docker é usado quando você precisa de algo de dentro da imagem, por exemplo, você está criando um aplicativo Android em um contêiner java e precisa apenas de um apk. No nosso caso, precisamos de um contêiner inteiro e, para isso, precisamos do executor Shell.
É fácil começar a executar contêineres e há muitas ferramentas disponíveis.
A entrega contínua usando contêineres difere da configuração usual de várias maneiras:
<p>
No próximo artigo, vamos falar sobre a criação da configuração de CD que usa o contêiner Docker do InterSystems IRIS.
</p>
</div>
</div>
</div>
Quando tento exportar uma global (arquivo .gof), os dados do nó não vêm (esses circulados em vermelho), somente o nó. Gostaria de saber o motivo e como faço para conseguir exportar esses dados. Obrigado.
Boa tarde,
Utilizo o Caché COS e estou com dificuldade para fazer um POP3 no servidor de e-mail da Microsoft, utilizando a autenticação OAuth 2.0.
Estou utilizando o seguinte programa para realizar essa tarefa:
Acabamos de lançar uma pequena atualização no gerenciador de pacotes, que foi renomeado de ZPM para IPM, conforme expliquei em Novembro. É puramente uma versão de correção de bug, interpretando corretamente os códigos de retorno ROBOCOPY e corrigindo uma regressão que impedia a instalação de determinados pacotes.
Obtenha aqui:
Criado por Daniel Kutac, Engenheiro de vendas, InterSystems
Parte 3. Apêndice
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.
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.
| 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.
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
| %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.
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.
| %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.
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.
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.
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.
Nesta série de artigos, quero apresentar e discutir várias abordagens possíveis para o desenvolvimento de software com tecnologias da InterSystems e do GitLab. Vou cobrir tópicos como:
No primeiro artigo, abordamos os fundamentos do Git, por que um entendimento de alto nível dos conceitos do Git é importante para o desenvolvimento de software moderno e como o Git pode ser usado para desenvolver software.
No segundo artigo, abordamos o fluxo de trabalho do GitLab: um processo inteiro do ciclo de vida do software e a entrega contínua.
No terceiro artigo, abordamos a instalação e configuração do GitLab e a conexão dos seus ambientes a ele
No quarto artigo, escrevemos uma configuração de CD.
Neste artigo, falaremos sobre os contêineres e como (e por que) podem ser usados.
Este artigo presume a familiaridade com os conceitos de docker e contêiner. Confira estes artigos do @Luca Ravazzolo se quiser ler sobre contêineres e imagens.
Há muitas vantagens em usar contêineres:
Vamos falar sobre cada uma em detalhes.
Um contêiner embrulha um aplicativo com tudo o que ele precisa para ser executado, como arquivos de configuração e dependências. Isso permite que você execute aplicativos de maneira fácil e confiável em diferentes ambientes, como seu desktop local, servidores físicos, servidores virtuais, testes, staging, ambientes de produção e nuvens públicas ou privadas.
Outro ponto da portabilidade é que, depois de criar sua imagem do Docker e verificar que ela é executada corretamente, ela pode ser executada em qualquer outro lugar que execute o docker, que atualmente são os servidores Windows, Linux e MacOS.
Você só precisa que o processo do aplicativo seja executado, e não todo o sistema, etc. E os contêineres oferecem exatamente isso: eles executam apenas os processos de que você precisa explicitamente e nada mais. Como os contêineres não exigem um sistema operacional separado, eles consomem menos recursos. Enquanto uma VM costuma ter vários gigabytes de tamanho, um contêiner geralmente tem apenas algumas centenas de megabytes, tornando possível executar muito mais contêineres do que VMs em um único servidor. Como os contêineres têm um nível de uso mais alto em relação ao hardware subjacente, você precisa de menos hardware, resultando em uma redução nos custos dos servidores bare metal, bem como dos centros de processamento de dados.
Os contêineres isolam seu aplicativo de todo o resto e, embora vários contêineres possam ser executados no mesmo servidor, eles podem ser completamente independentes uns dos outros. Qualquer interação entre contêineres deve ser explicitamente declarada como tal. Se um contêiner falhar, ele não afetará os outros e poderá ser reiniciado rapidamente. A segurança também se beneficia desse isolamento. Por exemplo, explorar a vulnerabilidade do servidor web em um servidor bare metal pode dar a um invasor acesso a todo o servidor, mas, no caso dos contêineres, o invasor só teria acesso ao contêiner do servidor web.
Como os contêineres não exigem um sistema operacional separado, eles podem ser iniciados, interrompidos ou reinicializados em questão de segundos, o que acelera todos os pipelines de desenvolvimento relacionados e o tempo de produção. Você pode começar a trabalhar antes e não gastar nenhum tempo na configuração.
A infraestrutura imutável é composta por componentes imutáveis que são substituídos a cada implantação, em vez de serem atualizados no local. Esses componentes são inicializados a partir de uma imagem comum que é criada uma vez por implantação e pode ser testada e validada. A imutabilidade reduz a inconsistência e permite a replicação e a movimentação entre diferentes estados do seu aplicativo com facilidade. Mais sobre a imutabilidade.
Todas essas vantagens nos permitem gerenciar a infraestrutura e o fluxo de trabalho de maneiras totalmente novas.
Há um problema com ambientes bare metal ou VM, eles ganham individualidade, o que traz várias surpresas depois, geralmente desagradáveis. A resposta para isso é a infraestrutura como código, o gerenciamento da infraestrutura em um modelo descritivo, usando as mesmas versões que a equipe de DevOps usa para o código-fonte.
Com a infraestrutura como código, um comando de implantação sempre coloca o ambiente de destino na mesma configuração, não importa o estado inicial do ambiente. Isso é alcançado ao configurar automaticamente um destino existente ou descartar o destino existente e recriar um novo ambiente.
Assim, com a infraestrutura como código, as equipes fazem alterações na descrição do ambiente e na versão do modelo de configuração, que normalmente está em formatos de código bem documentados, como JSON. O pipeline de lançamento executa o modelo para configurar ambientes de destino. Se a equipe precisar fazer alterações, ela editará a fonte, e não o destino.
Tudo isso é possível e muito mais fácil de fazer com contêineres. Leva poucos segundos para desativar um contêiner e iniciar outro, enquanto o provisionamento de uma nova VM leva alguns minutos. E nem estou falando em reverter um servidor para um estado limpo.
Do ponto anterior, você pode ter uma ideia de que a infraestrutura como código é estática por si só. Não é, pois as ferramentas de orquestração também podem fornecer escalonamento horizontal (provisionando mais do mesmo) com base na carga de trabalho atual. Você só deve executar o que é necessário no momento e escalonar seu aplicativo de acordo. Isso também pode reduzir custos.
Os contêineres podem otimizar seu pipeline de desenvolvimento. A eliminação de inconsistências entre ambientes permite testes e depurações mais fáceis. A orquestração permite que você crie aplicativos escalonáveis. A implantação ou reversão para qualquer ponto do histórico imutável é possível e fácil.
As organizações querem trabalhar em um nível mais alto, onde todos os problemas listados acima já estejam resolvidos e onde encontramos agendadores e orquestradores lidando com mais coisas de maneira automatizada.
<p>
No próximo artigo, vamos falar sobre o provisionamento com contêineres e a criação da configuração de CD que usa o contêiner Docker do InterSystems IRIS.
</p>
</div>
</div>
</div>
Nesta série de artigos, quero apresentar e discutir várias abordagens possíveis para o desenvolvimento de software com tecnologias da InterSystems e do GitLab. Vou cobrir tópicos como:
No primeiro artigo, abordamos os fundamentos do Git, por que um entendimento de alto nível dos conceitos do Git é importante para o desenvolvimento de software moderno e como o Git pode ser usado para desenvolver software.
No segundo artigo, abordamos o fluxo de trabalho do GitLab: um processo inteiro do ciclo de vida do software e a entrega contínua.
No terceiro artigo, abordamos a instalação e configuração do GitLab e a conexão dos seus ambientes a ele
Neste artigo, finalmente, vamos escrever uma configuração de CD.
Em primeiro lugar, precisamos de vários ambientes e branches que correspondam a eles:
| Environment | Branch | Delivery | Who can commit | Who can merge |
|---|---|---|---|---|
| Test | master | Automatic | Developers Owners | Developers Owners |
| Preprod | preprod | Automatic | No one | Owners |
| Prod | prod | Semiautomatic (press button to deliver) | No one | Owners |
E, como exemplo, desenvolveremos um novo recurso usando o fluxo do GitLab e o entregaremos usando a CD do GitLab.
Veja como isso ficaria (marquei as partes que precisamos desenvolver para o CD em itálico):
Ou o mesmo, mas em formato de gráfico:

Nosso aplicativo consiste em duas partes:
Com o plano acima, podemos determinar as etapas que precisamos definir na nossa configuração de entrega contínua:
Veja como isso fica no arquivo de configuração gitlab-ci.yml:
stages: - load - test - package - deploy
Em seguida, vamos definir os scripts. Documentos de scripts. Primeiro, vamos definir um script load server que carrega o código do lado do servidor:
load server:
environment:
name: test
url: http://test.hostname.com
only:
- master
tags:
- test
stage: load
script: csession IRIS "##class(isc.git.GitLab).load()"O que acontece aqui?
Observação importante
Para InterSystems IRIS, troque csession por iris session.
Para Windows, use: irisdb -s ../mgr -U TEST "##class(isc.git.GitLab).load()
Agora, vamos escrever a classe isc.git.GitLab correspondente. Todos os pontos de entrada nessa classe ficam desta forma:
ClassMethod method()
{
try {
// code
halt
} catch ex {
write !,$System.Status.GetErrorText(ex.AsStatus()),!
do $system.Process.Terminate(, 1)
}
}
Observe que esse método pode terminar de duas maneiras:
Dito isso, aqui está nosso código de carregamento:
/// Do a full load
/// do ##class(isc.git.GitLab).load()
ClassMethod load()
{
try {
set dir = ..getDir()
do ..log("Importing dir " _ dir)
do $system.OBJ.ImportDir(dir, ..getExtWildcard(), "c", .errors, 1)
throw:$get(errors,0)'=0 ##class(%Exception.General).%New("Load error")
halt
} catch ex {
write !,$System.Status.GetErrorText(ex.AsStatus()),!
do $system.Process.Terminate(, 1)
}
}
Dois métodos de utilitários são chamados:
Como podemos obter o diretório?
Quando o GitLab executa um script, primeiro, ele especifica várias variáveis de ambiente. Uma delas é a CI_PROJECT_DIR — o caminho completo onde o repositório é clonado e onde o job é executado. Ele pode ser obtido facilmente no nosso método getDir :
ClassMethod getDir() [ CodeMode = expression ]
{
##class(%File).NormalizeDirectory($system.Util.GetEnviron("CI_PROJECT_DIR"))
}
####Testes
Aqui está o script de teste:
load test:
environment:
name: test
url: http://test.hostname.com
only:
- master
tags:
- test
stage: test
script: csession IRIS "##class(isc.git.GitLab).test()"
artifacts:
paths:
- tests.htmlO que mudou? O nome e o código do script, é claro, mas o artefato também foi adicionado. Um artefato é uma lista de arquivos e diretórios que são anexados a um job depois que ele é concluído com sucesso. No nosso caso, depois que os testes forem concluídos, podemos gerar a página HTML redirecionando para os resultados dos testes e disponibilizá-la a partir do GitLab.
Observe que há bastante copiar e colar do estágio de carregamento — o ambiente é o mesmo, partes do script, como ambientes, podem ser rotuladas separadamente e anexadas a um script. Vamos definir o ambiente de teste:
.env_test: &env_test
environment:
name: test
url: http://test.hostname.com
only:
- master
tags:
- testAgora, nosso script de teste fica assim:
load test:
<<: *env_test
script: csession IRIS "##class(isc.git.GitLab).test()"
artifacts:
paths:
- tests.htmlEm seguida, vamos executar os testes usando o framework UnitTest.
/// do ##class(isc.git.GitLab).test()
ClassMethod test()
{
try {
set tests = ##class(isc.git.Settings).getSetting("tests")
if (tests'="") {
set dir = ..getDir()
set ^UnitTestRoot = dir
$$$TOE(sc, ##class(%UnitTest.Manager).RunTest(tests, "/nodelete"))
$$$TOE(sc, ..writeTestHTML())
throw:'..isLastTestOk() ##class(%Exception.General).%New("Tests error")
}
halt
} catch ex {
do ..logException(ex)
do $system.Process.Terminate(, 1)
}
}
A definição do teste, nesse caso, é um caminho relativo à raiz do repositório onde os testes de unidade são armazenados. Se estiver vazio, pulamos testes. O método writeTestHTML é usado para gerar o html com um redirecionamento para os resultados dos testes:
ClassMethod writeTestHTML()
{
set text = ##class(%Dictionary.XDataDefinition).IDKEYOpen($classname(), "html").Data.Read()
set text = $replace(text, "!!!", ..getURL())
set file = ##class(%Stream.FileCharacter).%New()
set name = ..getDir() _ "tests.html"
do file.LinkToFile(name)
do file.Write(text)
quit file.%Save()
}
ClassMethod getURL()
{
set url = ##class(isc.git.Settings).getSetting("url")
set url = url _ $system.CSP.GetDefaultApp("%SYS")
set url = url_"/%25UnitTest.Portal.Indices.cls?Index="_ $g(^UnitTest.Result, 1) _ "&$NAMESPACE=" _ $zconvert($namespace,"O","URL")
quit url
}
ClassMethod isLastTestOk() As %Boolean
{
set in = ##class(%UnitTest.Result.TestInstance).%OpenId(^UnitTest.Result)
for i=1:1:in.TestSuites.Count() {
#dim suite As %UnitTest.Result.TestSuite
set suite = in.TestSuites.GetAt(i)
return:suite.Status=0 $$$NO
}
quit $$$YES
}
XData html
{
<html lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="refresh" content="0; url=!!!"/>
<script type="text/javascript">
window.location.href = "!!!"
</script>
</head>
<body>
If you are not redirected automatically, follow this <a href='!!!'>link to tests</a>.
</body>
</html>
}Nosso cliente é uma página HTML simples:
<html>
<head>
<script type="text/javascript">
function initializePage() {
var xhr = new XMLHttpRequest();
var url = "${CI_ENVIRONMENT_URL}:57772/MyApp/version";
xhr.open("GET", url, true);
xhr.send();
xhr.onloadend = function (data) {
document.getElementById("version").innerHTML = "Version: " + this.response;
};
var xhr = new XMLHttpRequest();
var url = "${CI_ENVIRONMENT_URL}:57772/MyApp/author";
xhr.open("GET", url, true);
xhr.send();
xhr.onloadend = function (data) {
document.getElementById("author").innerHTML = "Author: " + this.response;
};
}
</script>
</head>
<body onload="initializePage()">
<div id = "version"></div>
<div id = "author"></div>
</body>
</html>E, para criá-la, precisamos substituir ${CI_ENVIRONMENT_URL} pelo seu valor. Claro, um aplicativo real provavelmente exigiria npm, mas esse é apenas um exemplo. Aqui está o script:
package client:
<<: *env_test
stage: package
script: envsubst < client/index.html > index.html
artifacts:
paths:
- index.htmlPor fim, implantamos nosso cliente ao copiar index.html para o diretório raiz do servidor web.
deploy client: <<: *env_test stage: deploy script: cp -f index.html /var/www/html/index.html
É isso!
O que fazer se você precisar executar o mesmo script (semelhante) em vários ambientes? Partes do script também podem ser rótulos, então aqui está uma configuração de exemplo que carrega o código em ambientes de teste e pré-produção:
stages:
- load
- test
.env_test: &env_test
environment:
name: test
url: http://test.hostname.com
only:
- master
tags:
- test
.env_preprod: &env_preprod
environment:
name: preprod
url: http://preprod.hostname.com
only:
- preprod
tags:
- preprod
.script_load: &script_load
stage: load
script: csession IRIS "##class(isc.git.GitLab).loadDiff()"
load test:
<<: *env_test
<<: *script_load
load preprod:
<<: *env_preprod
<<: *script_loadAssim, podemos fugir de copiar e colar o código.
Veja a configuração de CD completa aqui. Ela segue o plano original de mover código entre os ambientes de teste, pré-produção e produção.
A entrega contínua pode ser configurada para automatizar qualquer fluxo de trabalho de desenvolvimento necessário.
No próximo artigo, vamos criar a configuração de CD que usa o contêiner Docker do InterSystems IRIS.
Nesta série de artigos, quero apresentar e discutir várias abordagens possíveis para o desenvolvimento de software com tecnologias da InterSystems e do GitLab. Vou cobrir tópicos como:
No primeiro artigo, abordamos os fundamentos do Git, por que um entendimento de alto nível dos conceitos do Git é importante para o desenvolvimento de software moderno e como o Git pode ser usado para desenvolver software.
No segundo artigo, abordamos o fluxo de trabalho do GitLab: um processo inteiro do ciclo de vida do software e a entrega contínua.
Neste artigo, vamos discutir:
Vamos instalar o GitLab no local. Há várias maneiras de instalar o GitLab — da fonte, pacote, em um contêiner. Não descreverei todos os passos aqui, há um guia para isso. Ainda assim, algumas observações.
Pré-requisitos:
Primeiro de tudo, você provavelmente precisa enviar e-mails com notificações.
Em seguida, recomendo instalar Páginas. Como discutido no artigo anterior — artefatos do script podem ser enviados para o GitLab. O usuário pode fazer o download deles, mas é útil poder abri-los diretamente no navegador e, para isso, precisamos de páginas.
Por que você precisa de páginas:
Como as páginas html podem ter um redirecionamento onload, elas podem ser usadas para enviar o usuário para onde precisamos. Por exemplo, veja este código que gera uma página html que envia um usuário para o último teste de unidade executado (no momento da geração do html):
ClassMethod writeTestHTML()
{
set text = ##class(%Dictionary.XDataDefinition).IDKEYOpen($classname(), "html").Data.Read()
set text = $replace(text, "!!!", ..getURL())
set file = ##class(%Stream.FileCharacter).%New()
set name = "tests.html"
do file.LinkToFile(name)
do file.Write(text)
quit file.%Save()
}
ClassMethod getURL()
{
set url = "http://host:57772"
set url = url _ $system.CSP.GetDefaultApp("%SYS")
set url = url_"/%25UnitTest.Portal.Indices.cls?Index="_ $g(^UnitTest.Result, 1) _ "&$NAMESPACE=" _ $zconvert($namespace,"O","URL")
quit url
}
XData html
{
If you are not redirected automatically, follow this link to tests.
}Encontrei um bug usando as páginas (erro 502 ao procurar artefatos), veja aqui a correção.
Para executar scripts de CD, você precisa de ambientes, servidores configurados para executar seu aplicativo. Presumindo que você tem um servidor Linux com o produto InterSystems instalado (digamos InterSystems IRIS, mas funciona também com o Caché e Ensemble), estas etapas conectam o ambiente ao GitLab:
Observação importante sobre a instalação do runner GitLab, NÃO clone servidores após instalar o runner do GitLab. Os resultados são imprevisíveis e muito indesejados.
Após executar o inicial:
sudo gitlab-runner register
você verá vários prompts e, embora a maioria das etapas seja bastante direta, várias não são:
Insira o token gitlab-ci para este runner
Há vários tokens disponíveis:
Conforme você conecta um runner para executar a CD para um projeto específico, você precisa especificar um token para este projeto.
Insira as tags gitlab-ci para este runner (separadas por vírgulas):
Na configuração de CD, você pode filtrar quais scripts vão ser executados em quais tags. Então, no caso mais simples, especifique uma tag, que seria o nome do ambiente.
Insira o executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:
docker
Se você estiver usando o servidor habitual sem docker, escolha shell. O Docker será discutido nas partes posteriores.
Depois de conectar o runner ao GitLab, precisamos permitir que ele interaja com o InterSystems IRIS, para isso:
Para 2 e 3, outras abordagens podem ser usadas, como a transmissão de usuário/código, mas acho que a autenticação de SO é preferível.
Nesta parte:
Na próxima parte, escrevemos nossa configuração de entrega contínua.
Bom dia Comunidade!
Estou montando uma API REST dentro do Cache e queria saber como eu coloco um middleware para fazer algumas validações antes de chamar a função de fato, estive procurando na documentação e não consegui localizar, quais as estratégias que vocês recomendam?!
Desde Já agradeço a ajuda!
Boa tarde,
Estou contratando um programador pleno com ótima experiência com a ferramenta Caché/Zen, bem como também conhecimento de negócios.
Interessado me envie currículo no e-mail hothenys@gmail.com, preferência como PJ, podemos estudar ser remoto com reuniões virtuais.
Obrigado
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.
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.
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.
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
Precisamos definir credenciais X.509 em servidores individuais para que eles possam assinar e validar JSON Web Tokens (JWT) trocados durante nossa demonstraçã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
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.
Não precisamos definir credenciais X509 na instância RESSERVER em nossa configuração de exemplo.
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.
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).
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.
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!
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.
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´ OAuth2 provider</h1>
<p>This page demo shows how to call Cache´ API functions using OAuth2 authorization.
<p>We are going to call Cache´ authentication and authorization server to grant our application access to data stored at another
Cache´ 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
}
}
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.
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.
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é 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
}
}
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é 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
Este artigo e os próximos dois artigos da série são um guia do usuário para desenvolvedores ou administradores de sistema que precisam usar o framework OAuth 2.0 (chamado de OAUTH para simplificar) em suas aplicações baseadas no produto InterSystems.
Criado por Daniel Kutac, Engenheiro de vendas sênior, InterSystems
Parte 1. Cliente
Esta é a primeira parte de uma série de três artigos sobre a implementação do Open Authorization Framework na InterSystems.
Nesta primeira parte, apresentamos uma breve introdução do tópico e mostramos um cenário simples em que a aplicação InterSystems IRIS atua como cliente de um servidor de autorização, solicitando alguns recursos protegidos.
A segunda parte descreverá um cenário mais complexo, em que a InterSystems IRIS atua como servidor de autorização e também como servidor de autenticação via OpenID Connect.
A última parte da série descreverá partes individuais das classes do framework OAUTH conforme implementadas pela InterSystems IRIS.
Muitos de vocês já ouviram falar do Open Authorization Framework e para que ele pode ser usado. Vamos resumir para quem ainda não tiver ouvido falar dele.
O Open Authorization Framework, OAUTH, atualmente na versão 2.0, é um protocolo que permite principalmente que aplicações web troquem informações de forma segura estabelecendo uma confiança indireta entre um cliente (aplicação que solicita dados) e o proprietário dos recursos (aplicação que detém os dados solicitados). A confiança é fornecida por uma entidade que tanto o cliente quanto o servidor de recursos reconhecem e na qual confiam. Essa entidade é chamada de servidor de autorização.
Veja um caso de uso simples:
Vamos supor que Jenny (na terminologia do OAUTH, é o proprietário dos recursos) esteja trabalhando em um projeto na empresa JennyCorp. Ela cria um plano de projeto para um possível negócio maior e convida seu parceiro comercial John (usuário cliente) da empresa JohnInc para revisar o documento. Mas ela não está contente de dar ao John acesso à VPN de sua empresa, então ela coloca o documento no Google Drive (o servidor de recursos) ou outra ferramenta de armazenamento em nuvem similar. Ao fazer isso, ela estabeleceu uma confiança entre ela e o Google (o servidor de autorização). Ela compartilha o documento com John (John já usa o serviço Google Drive, e Jenny sabe qual é o e-mail dele).
Quando John deseja ler o documento, ele faz a autenticação em sua conta do Google e, em seu dispositivo móvel (tablet, notebook, etc.), abre um editor de documentos (o servidor cliente) e carrega o arquivo do projeto da Jenny.
Embora pareça bem simples, há muita comunicação entre as duas pessoas e o Google. Todas as comunicações seguem a especificação do OAuth 2.0, então o cliente de John (o leitor de documentos) precisa primeiro fazer a autenticação no Google (essa etapa não é coberta pelo OAUTH) e, após John consentir autorização no formulário fornecido pelo Google, o Google autoriza que o leitor de documentos acesse o documento emitindo um token de acesso. O leitor de documentos usa o token de acesso para emitir uma solicitação ao serviço Google Drive para obter o arquivo de Jenny.
O diagrama abaixo mostra a comunicação entre todas as partes

Nota: embora todas as comunicações do OAUTH 2.0 sejam feitas por solicitações HTTP, os servidores não precisam ser aplicações web.
Vamos ilustrar esse cenário simples com a InterSystems IRIS.
Nesta demonstração, vamos criar uma aplicação de pequeno porte baseada em Cloud Solution Provider (CSP) que solicita recursos (lista de arquivos) armazenados no serviço Google Drive com nossa própria conta (e também uma lista de nossos calendários, como bônus).
Antes de começarmos a programar a aplicação, precisamos preparar o ambiente. Precisaremos de um servidor web com SSL ativado e um perfil do Google.
Conforme informado acima, precisamos estabelecer comunicação com o servidor de autorização com SSL, pois isso é exigido pelo OAuth 2.0 por padrão. Queremos manter nossos dados seguros, certo?
Está fora do escopo deste artigo descrever como configurar um servidor web com suporte ao SSL, então consulte os manuais de usuário do servidor web de sua preferência. Usaremos o servidor IIS da Microsoft neste exemplo específico.
Para nos registrarmos no Google, precisamos usar o Google API Manager: https://console.developers.google.com/apis/library?project=globalsummit2016demo
Para o propósito da demonstração, criamos uma conta GlobalSummit2016Demo. É preciso confirmar se a API do Drive está ativada

Agora, está na hora de definir as credenciais

Observe o seguinte:
_Authorized JavaScript (JavaScript autorizado) – permitimos somente scripts originados localmente em relação à página chamadora
_Authorized redirect URIs (URIs de redirecionamento autorizados) – teoricamente, podemos redirecionar nossa aplicação cliente para qualquer site, mas, ao usar a implementação do OAUTH da InterSystems IRIS, precisamos redirecioná-la para https://localhost/csp/sys/oauth2/OAuth2.Response.cls. É possível definir vários URIs de redirecionamento autorizados, conforme mostrado na captura de tela, mas, para esta demonstração, só precisamos da segunda entrada.
Por último, precisamos configurar a InterSystems IRIS como cliente do servidor de autorização do Google
A configuração do cliente OAUTH2 da InterSystems IRIS é um processo de duas etapas. Primeiro, precisamos criar uma configuração de servidor.
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.

Todas as informações inseridas no formulário estão disponíveis no site do console de desenvolvedores do Google. A InterSystems IRIS tem suporte à descoberta automática do Open ID. Entretanto, não estamos usando esse recurso. Inserimos todas as informações manualmente
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).

Deixe as abas Client Information (Informações do cliente) e JWT Settings (Configurações do JWT) em branco (com os valores padrão) e preencha a aba Client credentials (Credenciais do cliente).

Nota: estamos criando um Confidential Client (Cliente confidencial – é mais seguro que o público e significa que o segredo do cliente nunca deixa a aplicação do servidor cliente – nunca é transmitido ao navegador)
Além disso, confirme se Use SSL/TLS (Usar SSL/TLS) está marcado e forneça o nome do host (localhost, já que estamos redirecionando localmente para a aplicação cliente) e, posteriormente, a porta e o prefixo (útil quando há várias instâncias da InterSystems IRIS na mesma máquina). Com base nas informações preenchidas, o URL de redirecionamento do cliente é computado e exibido na linha acima.
Na captura de tela acima, fornecemos uma configuração SSL chamada GOOGLE. O nome é usado somente para ajudar a determinar qual configuração SSL dentre várias é usada por esse canal de comunicação específico. O Caché está usando configurações SSL/TLS para armazenar todas as informações necessárias para receber/enviar tráfego seguro ao servidor (neste caso, os URIs OAuth 2.0 do Google).
Consulte mais detalhes na documentação.
Preencha os valores Client ID (ID do cliente) e Client Secret (Segredo do cliente) obtidos pelo formulário de definição das credenciais do Google (ao fazer a configuração manual).
Agora, concluímos todas as etapas de configuração e podemos prosseguir para a programação de uma aplicação CSP.
A aplicação cliente é uma aplicação CSP web simples. Ela é composta por um código fonte no servidor, definido e executado pelo servidor web, e uma interface do usuário, exibida ao usuário por um navegador. O exemplo de código fornecido espera que a aplicação cliente seja executada no namespace GOOGLE. Modifique o caminho /csp/google/ para o seu namespace.
O servidor cliente é uma aplicação simples de duas páginas. Dentro da aplicação, nós vamos:
· Montar o URL de redirecionamento para o servidor de autorização do Google
· Fazer solicitações à API do Google Drive e à API do Google Agenda e exibir o resultado
Esta é uma página da aplicação, na qual decidimos fazer uma chamada aos recursos do Google.
Veja abaixo um código minimalista, mas completamente funcional, que representa a página.
Class Web.OAUTH2.Google1N Extends %CSP.Page{Parameter OAUTH2CLIENTREDIRECTURI = "https://localhost/csp/google/Web.OAUTH2.Google2N.cls";Parameter OAUTH2APPNAME = "Google";ClassMethod OnPage() As %Status{&html<<html><head></head><body style="text-align: center;"><!-- insert the page content here --><h1>Google OAuth2 API</h1><p>This page demo shows how to call Google API functions using OAuth2 authorization.<p>We are going to retrieve information about user and his/her Google Drive files as well as calendar entries.>// we need to supply openid scope to authenticate to Googleset scope="openid https://www.googleapis.com/auth/userinfo.email "_"https://www.googleapis.com/auth/userinfo.profile "_"https://www.googleapis.com/auth/drive.metadata.readonly "_"https://www.googleapis.com/auth/calendar.readonly"set properties("approval_prompt")="force"set properties("include_granted_scopes")="true"set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(..#OAUTH2APPNAME,scope,..#OAUTH2CLIENTREDIRECTURI,.properties,.isAuthorized,.sc) w !,"<p><a href='"_url_"'><img border='0' alt='Google Sign In' src='images/google-signin-button.png' ></a>" &html<</body></html>>Quit $$$OK}ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]{#dim %response as %CSP.Responseset scope="openid https://www.googleapis.com/auth/userinfo.email "_"https://www.googleapis.com/auth/userinfo.profile "_"https://www.googleapis.com/auth/drive.metadata.readonly "_"https://www.googleapis.com/auth/calendar.readonly"if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,scope,.accessToken,.idtoken,.responseProperties,.error) {set %response.ServerSideRedirect="Web.OAUTH2.Google2N.cls"}quit 1}}Veja abaixo uma breve explicação do código:
1. Método OnPreHTTP: primeiro, verificamos se, por acaso, já obtivemos um token de acesso válido como resultado de uma autorização do Google. Isso pode acontecer, por exemplo, quando simplesmente atualizamos a página. Caso não tenhamos, precisamos fazer a autorização. Se já tivermos o token, apenas redirecionamos para a página que mostra os resultados
2. Método OnPage: só chegamos a este método se não tivermos um token de acesso válido disponível. Então, precisamos iniciar a comunicação: fazer a autenticação e autorização no Google para que ele nos conceda o token de acesso.
3. Definimos uma string de escopo e uma array de propriedades que modificam o comportamento da janela de autenticação do Google (precisamos fazer a autenticação no Google antes que ele possa nos autorizar com base em nossa identidade).
4. Por último, recebemos o URL de uma página de login do Google e a mostramos ao usuário, seguida pela página de consentimento.
Mais uma nota:
Especificamos a verdadeira página de redirecionamento em https://www.localhost/csp/google/Web.OAUTH2.Google2N.cls no parâmetro OAUTH2CLIENTREDIRECTURI. Entretanto, usamos a página de sistema do framework OAUTH da InterSystems IRIS na definição das credenciais do Google! O redirecionamento é tratado internamente por nossa classe manipuladora OAUTH.
Esta página mostra os resultados da autorização do Google e, em caso de êxito, fazemos chamadas à API do Google para obter os dados. Novamente, o código abaixo é minimalista, mas completamente funcional. Deixamos a exibição dos dados recebidos de uma maneira mais estruturada para a imaginação dos leitores.
Include %occIncludeClass Web.OAUTH2.Google2N Extends %CSP.Page{Parameter OAUTH2APPNAME = "Google";Parameter OAUTH2ROOT = "https://www.googleapis.com";ClassMethod OnPage() As %Status{&html<<html><head></head><body>>// Check if we have an access tokenset scope="openid https://www.googleapis.com/auth/userinfo.email "_"https://www.googleapis.com/auth/userinfo.profile "_"https://www.googleapis.com/auth/drive.metadata.readonly "_"https://www.googleapis.com/auth/calendar.readonly"set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,scope,.accessToken,.idtoken,.responseProperties,.error)if isAuthorized { // Google has no introspection endpoint - nothing to call - the introspection endpoint and display result -- see RFC 7662.w "<h3>Data from <span style='color:red;'>GetUserInfo API</span></h3>"// userinfo has special API, but could be also retrieved by just calling Get() method with appropriate urltry {set tHttpRequest=##class(%Net.HttpRequest).%New()$$$THROWONERROR(sc,##class(%SYS.OAuth2.AccessToken).AddAccessToken(tHttpRequest,"query","GOOGLE",..#OAUTH2APPNAME))$$$THROWONERROR(sc,##class(%SYS.OAuth2.AccessToken).GetUserinfo(..#OAUTH2APPNAME,accessToken,,.jsonObject))w jsonObject.%ToJSON()} catch (e) {w "<h3><span style='color: red;'>ERROR: ",$zcvt(e.DisplayString(),"O","HTML")_"</span></h3>"}/*********************************************Retrieve info from other APIs*********************************************/w "<hr>"do ..RetrieveAPIInfo("/drive/v3/files")do ..RetrieveAPIInfo("/calendar/v3/users/me/calendarList")} else {w "<h1>Not authorized!</h1>"}&html<</body></html>>Quit $$$OK}ClassMethod RetrieveAPIInfo(api As %String){w "<h3>Data from <span style='color:red;'>"_api_"</span></h3><p>"try {set tHttpRequest=##class(%Net.HttpRequest).%New()$$$THROWONERROR(sc,##class(%SYS.OAuth2.AccessToken).AddAccessToken(tHttpRequest,"query","GOOGLE",..#OAUTH2APPNAME))$$$THROWONERROR(sc,tHttpRequest.Get(..#OAUTH2ROOT_api))set tHttpResponse=tHttpRequest.HttpResponses tJSONString=tHttpResponse.Data.Read()if $e(tJSONString)'="{" {// not a JSONd tHttpResponse.OutputToDevice()} else {w tJSONStringw "<hr/>"/*// new JSON API&html<<table border=1 style='border-collapse: collapse'>>s tJSONObject={}.%FromJSON(tJSONString)set iterator=tJSONObject.%GetIterator()while iterator.%GetNext(.key,.value) {if $isobject(value) {set iterator1=value.%GetIterator()w "<tr><td>",key,"</td><td><table border=1 style='border-collapse: collapse'>"while iterator1.%GetNext(.key1,.value1) {if $isobject(value1) {set iterator2=value1.%GetIterator()w "<tr><td>",key1,"</td><td><table border=0 style='border-collapse: collapse'>"while iterator2.%GetNext(.key2,.value2) {write !, "<tr><td>",key2, "</td><td>",value2,"</td></tr>"}// this way we can go on and on into the embedded objects/arraysw "</table></td></tr>"} else {write !, "<tr><td>",key1, "</td><td>",value1,"</td></tr>"}}w "</table></td></tr>"} else {write !, "<tr><td>",key, "</td><td>",value,"</td></tr>"}}&html<</table><hr/>>*/}} catch (e) {w "<h3><span style='color: red;'>ERROR: ",$zcvt(e.DisplayString(),"O","HTML")_"</span></h3>"}}}
Vamos dar uma olhada no código:
1. Antes de tudo, precisamos verificar se temos um token de acesso válido (para verificarmos se fomos autorizados)
2. Caso afirmativo, podemos enviar solicitações às APIs oferecidas pelo Google usando o token de acesso emitido
3. Para isso, usamos a classe padrão %Net.HttpRequest, mas adicionamos o token de acesso ao método GET ou POST de acordo com a especificação da API chamada
4. Como é possível ver, o framework OAUTH implementou o método GetUserInfo() para sua comodidade, mas você pode obter as informações do usuário diretamente usando a especificação da API do Google da mesma maneira como feito no método auxiliar RetrieveAPIInfo()
5. Como é comum no mundo do OAUTH trocar dados no formato JSON, apenas lemos os dados recebidos e os colocamos no navegador. Cabe ao desenvolvedor da aplicação analisar e formatar os dados recebidos para apresentá-los ao usuário de alguma forma que faça sentido. Mas isso está além do escopo desta demonstração. (Embora tenhamos colocado um código comentado que mostra como a análise pode ser feita.)
Veja abaixo uma captura de tela da saída exibindo os dados JSON não tratados.

Prossiga para a parte 2, que descreve como a InterSystems IRIS atua como servidor de autorização e provedor do OpenID Connect.
Olá a todos,
Tenho 15 anos de experiência em InterSystems e estou aberto a oportunidades de serviços como freelancer.
Meu contato: fabio2wf@gmail.com
Obrigado.
Entendo que os testes unitários em cache podem ser feitos com frameworks.
Podemos automatizar o teste de unidade em cache?
Tenho o prazer de anunciar um marco no ciclo de vida do gerenciador de pacotes ObjectScript, ZPM. O gerenciador de pacotes oferece aos desenvolvedores a capacidade de empacotar o código ObjectScript e as configurações de implantação e as informações de versão de maneira conveniente. Ao longo dos últimos anos, evoluiu muito para uma parte integrante de muitos fluxos de trabalho de desenvolvimento.
Nesta série de artigos, quero apresentar e discutir várias abordagens possíveis para o desenvolvimento de software com tecnologias da InterSystems e do GitLab. Vou cobrir tópicos como:
No artigo anterior, abordamos os fundamentos do Git, por que um entendimento de alto nível dos conceitos do Git é importante para o desenvolvimento de software moderno e como o Git pode ser usado para desenvolver software. Ainda assim, nosso foco foi na parte da implementação do desenvolvimento de software, mas esta parte apresenta:
O fluxo de trabalho do GitLab é uma sequência lógica de possíveis ações a serem tomadas durante todo o ciclo de vida do processo de desenvolvimento de software.
O fluxo de trabalho do GitLab leva em consideração o fluxo do GitLab, que discutimos em um artigo anterior. Veja como funciona:

Novamente, o processo em si não é novo (ou exclusivo do GitLab) e pode ser alcançado com outras ferramentas da sua escolha.
Vamos discutir várias dessas etapas e o que elas implicam. Também há documentação disponível.
As etapas iniciais do fluxo de trabalho do GitLab são centradas em um problema: um recurso, bug ou outro tipo de trabalho semanticamente separado.
O problema tem várias finalidades, como:
A etapa de planejamento nos permite agrupar os problemas por prioridade, marco, quadro kanban e ter uma visão geral disso.

O desenvolvimento foi discutido na parte anterior, basta seguir qualquer fluxo git que quiser. Depois que desenvolvemos nosso novo recurso e o mesclamos no master: o que vem depois?
A entrega contínua é uma abordagem de engenharia de software em que as equipes produzem software em ciclos curtos, garantindo que o software possa ser lançado de forma confiável a qualquer momento. Seu objetivo é construir, testar e lançar software com mais rapidez e frequência. A abordagem ajuda a reduzir o custo, o tempo e o risco da entrega de alterações, permitindo mais atualizações incrementais para aplicativos em produção. Um processo de implantação simples e repetível é importante para a entrega contínua.
No GitLab, a configuração da entrega contínua é definida por repositório como um arquivo de configuração YAML.
O script define uma ação e quais condições devem ser atendidas para executá-la:
Ambiente - é um servidor ou contêiner configurado no qual você pode executar seus scripts.
Runners executam scripts em ambientes específicos. Eles são conectados ao GitLab e executam scripts conforme necessário.
O runner pode ser implantado em um servidor, contêiner ou até mesmo na sua máquina local.
Como acontece a entrega contínua?
Por exemplo, aqui está um pipeline executado após um commit em um branch master:
Ele consiste em quatro etapas, executadas consecutivamente
Como podemos ver, todos os scripts foram executados com sucesso. Se um dos scripts falhar, por padrão, os scripts posteriores não são executados (mas podemos alterar esse comportamento):

Se abrirmos o script, podemos ver o log e determinar por que ele falhou:
Running with gitlab-runner 10.4.0 (857480b6)
on test runner (ab34a8c5)
Using Shell executor...
Running on gitlab-test...
<span class="term-fg-l-green term-bold">Fetching changes...</span>
Removing diff.xml
Removing full.xml
Removing index.html
Removing tests.html
HEAD is now at a5bf3e8 Merge branch '4-versiya-1-0' into 'master'
From http://gitlab.eduard.win/test/testProject
* [new branch] 5-versiya-1-1 -> origin/5-versiya-1-1
a5bf3e8..442a4db master -> origin/master
d28295a..42a10aa preprod -> origin/preprod
3ac4b21..7edf7f4 prod -> origin/prod
<span class="term-fg-l-green term-bold">Checking out 442a4db1 as master...</span>
<span class="term-fg-l-green term-bold">Skipping Git submodules setup</span>
<span class="term-fg-l-green term-bold">$ csession ensemble "##class(isc.git.GitLab).loadDiff()"</span>
[2018-03-06 13:58:19.188] Importing dir /home/gitlab-runner/builds/ab34a8c5/0/test/testProject/
[2018-03-06 13:58:19.188] Loading diff between a5bf3e8596d842c5cc3da7819409ed81e62c31e3 and 442a4db170aa58f2129e5889a4bb79261aa0cad0
[2018-03-06 13:58:19.192] Variable modified
var=$lb("MyApp/Info.cls")
Load started on 03/06/2018 13:58:19
Loading file /home/gitlab-runner/builds/ab34a8c5/0/test/testProject/MyApp/Info.cls as udl
Load finished successfully.
[2018-03-06 13:58:19.241] Variable items
var="MyApp.Info.cls"
var("MyApp.Info.cls")=""
Compilation started on 03/06/2018 13:58:19 with qualifiers 'cuk /checkuptodate=expandedonly'
Compiling class MyApp.Info
Compiling routine MyApp.Info.1
ERROR: MyApp.Info.cls(version+2) #1003: Expected space : '}' : Offset:14 [zversion+1^MyApp.Info.1]
TEXT: quit, "1.0" }
Detected 1 errors during compilation in 0.010s.
[2018-03-06 13:58:19.252] ERROR #5475: Error compiling routine: MyApp.Info.1. Errors: ERROR: MyApp.Info.cls(version+2) #1003: Expected space : '}' : Offset:14 [zversion+1^MyApp.Info.1]
> ERROR #5030: An error occurred while compiling class 'MyApp.Info'
<span class="term-fg-l-red term-bold">ERROR: Job failed: exit status 1
</span>O erro de compilação causou a falha do nosso script.
No próximo artigo, vamos:
Vamos discutir como a entrega contínua deve funcionar.
Em primeiro lugar, precisamos de vários ambientes e branches que correspondam a eles. O código entra nesse branch e é entregue ao ambiente de destino:
| Ambiente | Branch | Entrega | Quem pode fazer envios | Quem pode mesclar |
|---|---|---|---|---|
| Teste | master | Automático | Desenvolvedores Proprietários | Desenvolvedores Proprietários |
| Preprod | preprod | Automático | Ninguém | Proprietários |
| Prod | prod | Semiautomático (pressionar botão para entregar) | Ninguém | Proprietários |
E, como exemplo, desenvolveremos um novo recurso usando o fluxo do GitLab e o entregaremos usando a CD do GitLab.
Veja como ficaria:
Ou o mesmo, mas em formato gráfico:
Recentemente, houve um thread de e-mail interno no qual o servidor SMTP deve ser usado em demonstrações. Eu pensei em compartilhar os comentários desse tópico:
Eu gostaria de anunciar o lançamento de algo realmente bastante interessante - revolucionário na verdade. Isso pode soar exagerado, mas acho que você não viu nada parecido com isso, ou mesmo pensou que fosse possível!
Lançamos um novo módulo JavaScript/Node.js chamado glsdb, mais informações:
https://github.com/robtweed/glsdb
No entanto, para os propósitos deste anúncio aqui, quero apenas focar em uma parte do glsdb: suas APIs que abstraem classes IRIS (ou Caché) como objetos JavaScript equivalentes.
Olá a todos,
Existe algum comando de cache que possa fornecer todos que compilaram uma classe a partir do dia em que foi criada. Eu quero, especificamente, as 2 informações. O nome do usuário e hora de compilação da classe.
Eu tentei com $$DATE^%R("TEST.1.INT"), mas só retorna o tempa da última compilação.
Agradeço desde já !!
tem este wsdl
https://apphom.correios.com.br/SigepMasterJPA/AtendeClienteService/Aten…
<xs:element name="consultaCEP" type="tns:consultaCEP"/>
tem um method consultaCEP
quero passar um Cep e receber o retorno
<script language=cache runat=server>
// instancia a classe cliente SOAP
Set cliente=##class(AtendeClienteService.AtendeClientePort).%New()
set cep="",cep="88133150"
set resp=cliente.consultaCEP(cep)
w resp
</script>
Alguém no grupo já conseguiu fazer isso
teria como ajudar
Se estiver manipulando XML, muitas vezes ele pode ser não formatado para exibição humana.
Usando um pouco de magia XSLT você pode formatar o XML em apenas duas linhas de código...
Olá,
De acordo com a Wikipédia, o kafka é:
"uma plataforma open-source de processamento de streams desenvolvida pela Apache Software Foundationescrita em Scalae JavaO projeto tem como objetivo fornecer uma plataforma unificada, de alta capacidade e baixa latência para tratamento de dados em tempo real. Sua camada de armazenamento é, essencialmente, uma "fila de mensagens de publishers/subscribers maciçamente escalável projetada como um log de transações distribuído", tornando-o altamente valioso para infra-estruturas corporativas que processam transmissão de dados."