#Segurança

0 Seguidores · 42 Postagens

Segurança em TI é a proteção de sistemas de computador contra roubo e danos ao hardware, software ou informações, bem como contra a interrupção ou direcionamento incorreto dos serviços fornecidos.

Veja a Documentação da InterSystems sobre Segurança.

InterSystems Oficial Danusa Calixto · Jul. 28, 2025

O InterSystems IRIS 2025.2 apresenta o banco de dados IRISSECURITY, o novo lar para dados de segurança. Ao contrário do IRISSYS, o antigo lar para dados de segurança, o IRISSECURITY pode ser criptografado, o que protege seus dados confidenciais em repouso. Em uma versão futura, o IRISSECURITY será espelhado.

Esta versão também apresenta a função %SecurityAdministrator para tarefas gerais de administração de segurança.

0
0 31
Artigo Larissa Prussak · Maio 23, 2025 1m read

Depois que implementamos um novo container baseado em containers.intersystems.com/intersystems/irishealth:2023.1 esta semana, percebemos que nosso FHIR Repository começou a responder com um Erro 500. Isso aconteceu devido a violações de PROTECT no novo namespace e banco de dados HSSYSLOCALTEMP utilizado por essa versão dos componentes FHIR do IRIS for Health.

A solução para isso é adicionar a permissão "%DB_HSSYSLOCALTEMP" nas Aplicações Web que processam as requisições FHIR. Você pode automatizar essa configuração executando o seguinte método de classe nos namespace(s) onde essas Aplicações Web estão definidas:

do ##class(HS.HealthConnect.FHIRServer.Upgrade.MethodsV6).AddLOCALTEMPRoleToCSP()

No nosso caso, isso não foi suficiente. Parte do nosso código customizado precisa acessar o token JWT Bearer enviado pelo cliente, o qual antes era obtido através do elemento AdditionalInfo "USER:OAuthToken", que não está mais presente na build 2023.6.1.809, conforme descrito em: https://docs.intersystems.com/upgrade/results?product=ifh&versionFrom=20...

Contornamos esse problema adicionando a seguinte lógica para buscar o token diretamente do cache de tokens:

$$$ThrowOnError(##class(HS.HC.Util.InfoCache).GetTokenInfo(pInteropRequest.Request.AdditionalInfo.GetAt("USER:TokenId"), .pTokenInfo)) set OAuthToken = pTokenInfo("token_string")

0
0 20
Artigo Heloisa Paiva · Mar. 5, 2025 6m read

O que é JWT?

JWT (JSON Web Token) é um padrão aberto (RFC 7519) que oferece um método leve, compacto e auto-contido para transmitir informações de forma segura entre duas partes. É comumente usado em aplicações web para autenticação, autorização e troca de informações.

Um JWT é tipicamente composto por três partes:

1. Cabeçalho JOSE (JSON Object Signing and Encryption) 
2. Payload (Carga útil)
3. Assinatura

Essas partes são codificadas no formato Base64Url e concatenadas com pontos (.) separando-as.

Estrutura de um JWT

Cabeçalho

{ "alg": "HS256", "typ": "JWT"}

Payload

0
0 43
Artigo Julio Esquerdo · Fev. 14, 2025 5m read

HTTP e HTTPS com API REST

Olá,

O protocolo HTTP permite a obtenção de recursos, como documentos HTML. É a base de qualquer troca de dados na Web e um protocolo cliente-servidor, o que significa que as requisições são iniciadas pelo destinatário, geralmente um navegador da Web.

As API REST se beneficiam deste protocolo para trocar mensagens entre cliente e servidor. Isso torna as APIs REST rápidas, leves e flexíveis. As API REST utilizam os verbos HTTP GET, POST, PUT, DELETE e outros para indicar as ações que desejam realizar.

0
0 74
Artigo Heloisa Paiva · Dez. 11, 2024 4m read

De acordo com o relatório OWASP Top Ten de 2021, um documento de referência na área de segurança de aplicações web, as injeções SQL ocupam a terceira posição entre os riscos mais críticos. Este relatório, disponível em OWASP Top 10: Injection, destaca a gravidade dessa ameaça e a necessidade de implementar medidas de proteção eficazes.

Uma injeção SQL ocorre quando um atacante malicioso consegue inserir código SQL não autorizado em uma consulta enviada a um banco de dados. Esse código, disfarçado nas entradas do usuário, pode então ser executado pelo banco de dados, causando ações indesejáveis como o roubo de dados confidenciais, a modificação ou a exclusão de informações sensíveis, ou ainda a interrupção do funcionamento da aplicação.

0
0 42
Artigo Heloisa Paiva · Out. 24, 2024 7m read

fastapi_logo

Descrição

Este é um modelo para um aplicativo FastAPI que pode ser implantado no IRIS como um aplicativo Web nativo.

Instalação

  1. Clone o repositório
  2. Crie um ambiente virtual
  3. Instale os requisitos
  4. Execute o arquivo docker-compose
git clone
cd iris-fastapi-template
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
docker-compose up

Uso

A URL base é http://localhost:53795/fastapi/.

Endpoints

  • /iris - Retorna um objeto JSON com as 10 principais classes presentes no namespace IRISAPP.
  • /interop - Um endpoint de ping para testar a estrutura de interoperabilidade do IRIS.
  • /posts - Um endpoint CRUD simples para um objeto Post.
  • /comments - Um endpoint CRUD simples para um objeto Comentário.

Como desenvolver a partir deste template

Veja o artigo de introdução ao WSGI: wsgi-introduction.

TL;DR: Você pode ativar ou desativar o sinalizador DEBUG no portal de segurança para que as alterações sejam refletidas no aplicativo à medida que você desenvolve.

Apresentação do código

app.py

Este é o arquivo principal do aplicativo FastAPI. Ele contém o aplicativo FastAPI e as rotas.

from fastapi import FastAPI, Request

import iris

from grongier.pex import Director

# import models
from models import Post, Comment, init_db
from sqlmodel import Session,select

app = FastAPI()

# create a database engine
url = "iris+emb://IRISAPP"
engine = init_db(url)
  • from fastapi import FastAPI, Request - Importe a classe FastAPI e a classe Request
  • import iris - Importe o módulo IRIS.
  • from grongier.pex import Director: Importe a classe Director para vincular o aplicativo Flask ao framework de interoperabilidade do IRIS.
  • from models import Post, Comment, init_db - Importe os modelos e a função init_db.
  • from sqlmodel import Session,select - Importe a classe Session e a função select do módulo sqlmodel.
  • app = FastAPI() - Crie um aplicativo FastAPI.
  • url = "iris+emb://IRISAPP" -Defina o URL do namespace IRIS.
  • engine = init_db(url) - Crie um mecanismo de banco de dados para o ORM sqlmodel..

models.py

Este arquivo contém os modelos para o aplicativo.

from sqlmodel import Field, SQLModel, Relationship, create_engine

class Comment(SQLModel, table=True):
    id: int = Field(default=None, primary_key=True)
    post_id: int = Field(foreign_key="post.id")
    content: str
    post: "Post" = Relationship(back_populates="comments")

class Post(SQLModel, table=True):
    id: int = Field(default=None, primary_key=True)
    title: str
    content: str
    comments: list["Comment"] = Relationship(back_populates="post")

Não há muito a dizer aqui, apenas a definição dos modelos com chaves estrangeiras e relacionamentos.

A função init_db é usada para criar o mecanismo de banco de dados.

def init_db(url):

    engine = create_engine(url)

    # create the tables
    SQLModel.metadata.drop_all(engine)
    SQLModel.metadata.create_all(engine)

    # initialize database with fake data
    from sqlmodel import Session

    with Session(engine) as session:
        # Create fake data
        post1 = Post(title='Post The First', content='Content for the first post')
        ...
        session.add(post1)
        ...
        session.commit()

    return engine
  • engine = create_engine(url) - Crie um mecanismo de banco de dados.
  • SQLModel.metadata.drop_all(engine) - Exclua todas as tabelas.
  • SQLModel.metadata.create_all(engine) - Crie todas as tabelas.
  • with Session(engine) as session: - Crie uma sessão para interagir com o banco de dados.
  • post1 = Post(title='Post The First', content='Content for the first post')- Crie um objeto Post..
  • session.add(post1) -Adicione o objeto Post à sessão.
  • session.commit() -Confirme as alterações no banco de dados.
  • return engine - Retorne o mecanismo de banco de dados.

/iris endpoint

######################
# IRIS Query exemplo #
######################

@app.get("/iris")
def iris_query():
    query = "SELECT top 10 * FROM %Dictionary.ClassDefinition"
    rs = iris.sql.exec(query)
    # Convert the result to a list of dictionaries
    result = []
    for row in rs:
        result.append(row)
    return result
  • @app.get("/iris") - Defina uma rota GET para o endpoint /iris .
  • query = "SELECT top 10 * FROM %Dictionary.ClassDefinition" -Defina a consulta para obter as 10 principais classes no namespace IRIS.
  • rs = iris.sql.exec(query) - Execute a consulta..
  • result = [] - Crie uma lista vazia para armazenar os resultados.
  • for row in rs: - Itere sobre o conjunto de resultados.
  • result.append(row) - Adicione a linha à lista de resultados.
  • return result - Retorne a lista de resultados.

/interop endpoint

########################
# IRIS interop exemplo #
########################
bs = Director.create_python_business_service('BS')

@app.get("/interop")
@app.post("/interop")
@app.put("/interop")
@app.delete("/interop")
def interop(request: Request):
    
    rsp = bs.on_process_input(request)

    return rsp

  • bs = Director.create_python_business_service('BS') - Crie um business service Python. -Deve ser criado fora da definição da rota para evitar múltiplas instâncias do serviço de negócios.
  • @app.get("/interop") - Define uma rota GET para o endpoint /interop.
  • @app.post("/interop") - Define uma rota POST para o endpoin /interop .
  • ...
  • def interop(request: Request): - Define o manipulador da rota.
  • rsp = bs.on_process_input(request) - Chame o método on_process_input do business service.
  • return rsp - Retorne a resposta.

/posts endpoint

############################
# operações CRUD de posts    #
############################

@app.get("/posts")
def get_posts():
    with Session(engine) as session:
        posts = session.exec(select(Post)).all()
        return posts
    
@app.get("/posts/{post_id}")
def get_post(post_id: int):
    with Session(engine) as session:
        post = session.get(Post, post_id)
        return post
    
@app.post("/posts")
def create_post(post: Post):
    with Session(engine) as session:
        session.add(post)
        session.commit()
        return post

Este endpoint é usado para realizar operações CRUD no objeto Post.

Não há muito a dizer aqui, apenas a definição das rotas para obter todos os posts, obter um post por ID e criar um post.

Tudo é feito usando o ORM sqlmodel.

/comments endpoint

############################
# operações  CRUD de comments #
############################


@app.get("/comments")
def get_comments():
    with Session(engine) as session:
        comments = session.exec(select(Comment)).all()
        return comments
    
@app.get("/comments/{comment_id}")
def get_comment(comment_id: int):
    with Session(engine) as session:
        comment = session.get(Comment, comment_id)
        return comment
    
@app.post("/comments")
def create_comment(comment: Comment):
    with Session(engine) as session:
        session.add(comment)
        session.commit()
        return comment

Este endpoint é usado para realizar operações CRUD no objeto Comment.

Não há muito a dizer aqui, apenas a definição das rotas para obter todos os comentários, obter um comentário por ID e criar um comentário.

Tudo é feito usando o ORM sqlmodel.

Solução de Problemas

Como executar o aplicativo FastAPI em modo autônomo

Você sempre pode executar um aplicativo Flask autônomo com o seguinte comando:

python3 /irisdev/app/community/app.py

Observação: você deve estar dentro do contêiner para executar este comando.

docker exec -it iris-fastapi-template-iris-1 bash

Reinicie o aplicativo no IRIS

Fique no modo DEBUG, faça várias chamadas para o aplicativo e as alterações serão refletidas no aplicativo.

Como acessar o Portal de Gerenciamento do IRIS

Você pode acessar o Portal de Gerenciamento do IRIS acessando http://localhost:53795/csp/sys/UtilHome.csp.

Execute este template localmente

Para isso, você precisa ter o IRIS instalado em sua máquina.

Em seguida, você precisa criar um namespace chamado IRISAPP.

Instale os requisitos.

Instale IoP :

#init iop
iop --init

# load production
iop -m /irisdev/app/community/interop/settings.py

# start production
iop --start Python.Production

Configure o aplicativo no portal de segurança.

0
0 43
Artigo Heloisa Paiva · Out. 22, 2024 9m read

django_logo

Descrição

Este é um modelo para um aplicativo Django que pode ser implantado no IRIS como um aplicativo Web nativo.

Instalação

  1. Clone o repositório
  2. Crie um ambiente virtual
  3. Instale os requisitos
  4. Rode o arquivo docker-compose
git clone
cd iris-django-template
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
docker-compose up

Uso

A URL base é http://localhost:53795/django/.

Endpoints

  • /iris - Retorna um objeto JSON com as 10 principais classes presentes no namespace IRISAPP.
  • /interop - Um endpoint de ping para testar a estrutura de interoperabilidade do IRIS.
  • /api/posts -Um endpoint CRUD simples para um objeto Post.
  • /api/comments - Um endpoint CRUD simples para um objeto Comment.

Como desenvolver deste modelo

Veja o artigo de introdução ao WSGI: wsgi-introduction.

TL;DR: Você pode ativar ou desativar o sinalizador DEBUG no portal de segurança para que as alterações sejam refletidas no aplicativo à medida que você desenvolve.

Apresentação do código

A aplicação Django é estruturada como se segue:

  • app - Pasta do projeto Django
    • app - Pasta da aplicação Django para configuração
      • settings.py - Arquivo de definições Django
      • urls.py - Arquivo de configuração de URL Django para conectar as visualizações às URLs
      • wsgi.py - Arquivo do Django WSGI
      • asgi.py - Arquivo do Django AGI
    • community - Pasta da aplicação Django para o aplicativo da comunidade, com CRUD nos objetos de Post e Comment
      • models.py - Arquivo de modelos do Djando para os objetos Post e Comment
      • views.py - Arquivo de visualizações Django para cessar os objetos Post e Comment
      • serializers.py - Arquivo Django de serializadores para os objetos Post e Comentário. * admin.py - Arquivo Django de administração para adicionar CRUD à interface administrativa.
      • migrations - Pasta Django de migrações para construir o banco de dados.
      • fixtures - Pasta Django de fixtures com dados de demonstração
    • sqloniris - Pasta do aplicativo Django para o aplicativo SQL no IRIS.
      • views.py - Arquivo Django de views para consultar o namespace IRISAPP.
      • apps.py - Arquivo de configuração do aplicativo Django.
    • interop - Pasta do aplicativo Django para o aplicativo de interoperabilidade.
      • views.py - Arquivo Django de views para testar a estrutura de interoperabilidade.
      • apps.py - Arquivo de configuração do aplicativo Django.
    • manage.py - Arquivo de gerenciamento Django.

app/settings.py

Este arquivo contém as configurações Django para o aplicativo.

...

# Definição de aplicação

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'community',
    'sqloniris',
    'interop',
    'rest_framework'
]

...

REST_FRAMEWORK = {
    # Use as permissões padrão do Django `django.contrib.auth` ,
    # ou permita acesso de apenas leitura para usuários não autenticados
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 20
}

...

DATABASES = {
    "default": {
        "ENGINE": "django_iris",
        "EMBEDDED": True,
        "NAMESPACE": "IRISAPP",
        "USER":"SuperUser",
        "PASSWORD":"SYS",
    }
}

Algumas definições importantes para notar:

  • INSTALLED_APPS - Contém a lista de aplicativos instalados no projeto Django.
  • community - O aplicativo Django para as operações CRUD nos objetos Post e Comentário.
  • sqloniris - TO aplicativo Django para as operações SQL no IRIS.
  • interop - O aplicativo Django para as operações de interoperabilidade.
  • rest_framework - O framework Django REST para a API REST.
  • REST_FRAMEWORK - Contém as configurações para o framework Django REST.
    • DEFAULT_PERMISSION_CLASSES - Somente usuários autenticados podem realizar operações CRUD.
    • DEFAULT_PAGINATION_CLASS - A classe de paginação para a API REST.
  • DATABASES - Contém as configurações para a conexão com o banco de dados IRIS.
    • Aqui estamos usando o mecanismo django_iris para conectar ao banco de dados IRIS.

app/urls.py

Este arquivo contém a configuração de URL para o aplicativo Django.

from django.contrib import admin
from django.urls import path,include
from rest_framework import routers
from community.views import PostViewSet, CommentViewSet
from sqloniris.views import index
from interop.views import index as interop_index

router = routers.DefaultRouter()
router.register(r'posts', PostViewSet)
router.register(r'comments', CommentViewSet)


urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(router.urls)),
    path('iris/', index),
    path('interop/', interop_index)
]
  • router - Contém o roteador padrão para a API REST.
  • routeer.register - Registra os viewsets Post e Comentário no roteador.
  • urlpatterns - Contém os padrões de URL para o aplicativo Django
    • /admin/ - A interface administrativa Django.
    • /api/ -A API REST para os objetos Post e Comentário.
    • /iris/ - O endpoint SQL no IRIS.
    • /interop/ - O endpoint de interoperabilidade.

app/wsgi.py

Este arquivo contém a configuração WSGI para o aplicativo Django.

Este é o arquivo que temos que fornecer ao IRIS para executar o aplicativo Django.

Na seção Security->Applications->Web Applications, temos que fornecer o caminho para este arquivo.

  • Application Name
    • app.wsgi
  • Callable Name
    • application
  • WSGI App directory
    • /irisdev/app/app

community/models.py

Este arquivo contém os modelos Django para os objetos Post e Comentário.

from django.db import models

# Create your models here.
class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

class Comment(models.Model):
    content = models.TextField()
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
  • Post - O modelo para o objeto Post.
    • title - O título do post.
    • content - O conteúdo do post.
  • Comment - O modelo para o objeto Comentário.
    • content - O conteúdo do comentário.
    • post - A chave estrangeira para o objeto Post.
    • related_name - O nome relacionado para os comentários.

community/seializers.py

Este arquivo contém os serializadores Django para os objetos Post e Comentário.

Usando o framework Django REST, podemos serializar os modelos Django em objetos JSON.

from rest_framework import serializers
from community.models import Post, Comment

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ('id', 'title', 'content', 'comments')

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = ('id', 'content', 'post')
  • PostSerializer -O serializador para o objeto Post.
  • CommentSerializer -O serializador para o objeto Comentário.
  • fields - Os campos a serem serializados.

community/views.py

Este arquivo contém as views Django para os objetos Post e Comentário.

Usando o framework Django REST, podemos criar operações CRUD para os modelos Django.

from django.shortcuts import render
from rest_framework import viewsets

# Import the Post and Comment models
from community.models import Post, Comment

# Import the Post and Comment serializers
from community.serializers import PostSerializer, CommentSerializer

# Create your views here.
class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

class CommentViewSet(viewsets.ModelViewSet):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer
  • PostViewSet - O viewset para o objeto Post.
  • CommentViewSet - O viewset para o objeto Comentário.
  • queryset - O queryset para o viewset.
  • serializer_class - A classe de serializador para o viewset.

sqloniris/views.py

Este arquivo contém as views Django para as operações SQL no IRIS.

from django.http import JsonResponse

import iris

def index(request):
    query = "SELECT top 10 * FROM %Dictionary.ClassDefinition"
    rs = iris.sql.exec(query)
    # Convert the result to a list of dictionaries
    result = []
    for row in rs:
        result.append(row)
    return JsonResponse(result, safe=False)
  • index - A view para a operação SQL no IRIS.
  • query - A consulta SQL a ser executada no banco de dados IRIS.
  • rs - O conjunto de resultados da consulta.
  • result - A lista de listas do conjunto de resultados.
  • JsonResponse - A resposta JSON para a view, safe é definido como False para permitir lista de listas.

interop/views.py

Este arquivo contém as views Django para as operações de interoperabilidade.

from django.http import HttpResponse

from grongier.pex import Director

bs = Director.create_python_business_service('BS')

def index(request):
    result = bs.on_process_input(request)
    return HttpResponse(result, safe=False)
  • bs - O objeto de serviço de negócios criado usando a classe Director .
  • index -A view para a operação de interoperabilidade.
  • result - A view para a operação de interoperabilidade.

Observação: não usamos JsonResponse para simplificar o código, podemos usá-lo se quisermos retornar um objeto JSON.

Solução de Problemas

Como executar o aplicativo Django em modo autônomo

Para executar o aplicativo Django em modo autônomo, podemos usar o seguinte comando:

cd /irisdev/app/app
python3 manage.py runserver 8001

Isso executará o aplicativo Django na porta padrão 8001.

Observação: você deve estar dentro do contêiner para executar este comando.

docker exec -it iris-django-template-iris-1 bash

Reiniciando o aplicativo no IRIS

Esteja no modo DEBUG, faça várias chamadas para o aplicativo e as alterações serão refletidas no aplicativo.

Como acessar o Portal de Gerenciamento do IRIS

Você pode acessar o Portal de Gerenciamento do IRIS acessando http://localhost:53795/csp/sys/UtilHome.csp.

Executar este template localmente

Para isso, você precisa ter o IRIS instalado em sua máquina.

Em seguida, você precisa criar um namespace chamado IRISAPP.

Instale os requisitos.

# Move to the app directory
cd /irisdev/app/app

# python manage.py flush --no-input
python3 manage.py migrate
# create superuser
export DJANGO_SUPERUSER_PASSWORD=SYS
python3 manage.py createsuperuser --no-input --username SuperUser --email admin@admin.fr

# load demo data
python3 manage.py loaddata community/fixtures/demo.json

# collect static files
python3 manage.py collectstatic --no-input --clear

# init iop
iop --init

# load production
iop -m /irisdev/app/app/interop/settings.py

# start production
iop --start Python.Production

Como servir arquivos estáticos

Para servir os arquivos estáticos no aplicativo Django, podemos usar o seguinte comando:

cd /irisdev/app
python3 manage.py collectstatic

Isso coletará os arquivos estáticos do aplicativo Django e os servirá no diretório /irisdev/app/static.

Para publicar os arquivos estáticos no IRIS, configure a seçãoSecurity->Applications->Web Applications.

web_applications

0
0 35
Artigo Heloisa Paiva · Out. 20, 2024 6m read

Flask_logo

Descrição

Este é um modelo para um aplicativo Flask que pode ser implantado no IRIS como um aplicativo Web nativo.

Instalação

  1. Clone o repositório
  2. Crie um ambiente virtual
  3. Instale os requisitos
  4. Rode o arquivo docker-compose
git clone
cd iris-flask-template
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
docker-compose up

Uso

A URL de base http://localhost:53795/flask/.

Endpoints

  • /iris - Retorna um objeto JSON com as 10 principais classes presentes no namespace IRISAPP.
  • /interop - Um endpoint de ping para testar o framework de interoperabilidade do IRIS
  • /posts - Um simples enpoint CRUD para um objeto de Post
  • /comments - Um enpoint simples de CRUD para o objeto de comentário

Como desenvolver deste template

Veja o artigo de introdução ao WSGI wsgi-introduction.

TL;DR: Você pode ativar ou desativar o sinalizador DEBUG no portal de segurança para que as alterações sejam refletidas no aplicativo à medida que você desenvolve.

Apresentação do código

app.py

Este é o arquivo principal do aplicativo. Ele contém o aplicativo Flask e os endpoints.

from flask import Flask, jsonify, request
from models import Comment, Post, init_db

from grongier.pex import Director

import iris

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'iris+emb://IRISAPP'

db = init_db(app)
  • from flask import Flask, jsonify, request: Importa a livraria Flask
  • from models import Comment, Post, init_db: Importa os modelos e a função de inicialização de base de dados
  • from grongier.pex import Director: Importa a classe Director para vincular o app flask à framework de interoperabilidade do IRIS
  • import iris: Importa a livraria IRIS
  • app = Flask(__name__): Cria uma aplicação Flask
  • app.config['SQLALCHEMY_DATABASE_URI'] = 'iris+emb://IRISAPP': Define o URI da base de dados ao namespace IRISAPP
    • O esquema de URI iris+emb é usado para conectar ao IRIS como uma conexão embutida (sem necessidade de uma instância IRIS separada
  • db = init_db(app): Inicialize a base de dados com aplicação Flask.

models.py

O arquivo contem os modelos SQLAlchemy para a aplicação.

from dataclasses import dataclass
from typing import List
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

@dataclass
class Comment(db.Model):
    id:int = db.Column(db.Integer, primary_key=True)
    content:str = db.Column(db.Text)
    post_id:int = db.Column(db.Integer, db.ForeignKey('post.id'))

@dataclass
class Post(db.Model):
    __allow_unmapped__ = True
    id:int = db.Column(db.Integer, primary_key=True)
    title:str = db.Column(db.String(100))
    content:str = db.Column(db.Text)
    comments:List[Comment] = db.relationship('Comment', backref='post')

Não há muito o que dizer aqui, os modelos são definidos como classes de dados e são subclasses da classe db.Model

O uso do atributo __allow_unmapped_ é necessário para permitir a criação do objeto Post sem o atributo comments

dataclasses são usadas para ajudar com a serialização de objetos ao JSON

A função init_db inicializa a base de dados com a aplicação Flask.

def init_db(app):
    db.init_app(app)

    with app.app_context():
        db.drop_all()
        db.create_all()
        # Create fake data
        post1 = Post(title='Post The First', content='Content for the first post')
        ...
        db.session.add(post1)
        ...
        db.session.commit()
    return db
  • db.init_app(app): Inicializa a base de dados com a aplicação Flask
  • with app.app_context(): Cria um contexto para a aplicação
  • db.drop_all(): Descarta todas as tabelas na base de dados
  • db.create_all(): Cria todas as tabelas na base de dados
  • Cria dados falsos para a aplicação
  • retorna o objeto de base de dados

/iris endpoint

######################
# IRIS Query exemplo#
######################

@app.route('/iris', methods=['GET'])
def iris_query():
    query = "SELECT top 10 * FROM %Dictionary.ClassDefinition"
    rs = iris.sql.exec(query)
    # Converte o resultado em uma lista de dicionários
    result = []
    for row in rs:
        result.append(row)
    return jsonify(result)

Esse endpoint executa uma query na base de dados IRIS e retorna as top 10 classes presentes no namespace IRISAPP

/interop endpoint

########################
# IRIS interop exemplo #
########################
bs = Director.create_python_business_service('BS')

@app.route('/interop', methods=['GET', 'POST', 'PUT', 'DELETE'])
def interop():
    
    rsp = bs.on_process_input(request)

    return jsonify(rsp)

Este endpoint é usado para testar a estrutura de interoperabilidade do IRIS. Ele cria um objeto de Serviço de Negócio e o vincula ao aplicativo Flask.

Observação: O objeto bs deve estar fora do escopo da solicitação para mantê-lo ativo.

  • bs = Director.create_python_business_service('BS'): Cria um objeto Business Service chamado 'BS'
  • rsp = bs.on_process_input(request): Chama o método on_process_input do objeto Business Service com o objeto de requisição como um argumento

/posts endpoint

############################
# operações CRUD para posts    #
############################

@app.route('/posts', methods=['GET'])
def get_posts():
    posts = Post.query.all()
    return jsonify(posts)

@app.route('/posts', methods=['POST'])
def create_post():
    data = request.get_json()
    post = Post(title=data['title'], content=data['content'])
    db.session.add(post)
    db.session.commit()
    return jsonify(post)

@app.route('/posts/<int:id>', methods=['GET'])
def get_post(id):
    ...

Este endpoint é usado para realizar operações CRUD no objeto Post

Graças ao módulo dataclasses, o objeto Post pode ser facilmente serializado para JSON.

Aqui, usamos o método query do sqlalchemy para obter todos os posts e os métodos add e commit para criar um novo post

/comments endpoint

############################
# operações CRUD para comentários  #
############################

@app.route('/comments', methods=['GET'])
def get_comments():
    comments = Comment.query.all()
    return jsonify(comments)

@app.route('/comments', methods=['POST'])
def create_comment():
    data = request.get_json()
    comment = Comment(content=data['content'], post_id=data['post_id'])
    db.session.add(comment)
    db.session.commit()
    return jsonify(comment)

@app.route('/comments/<int:id>', methods=['GET'])
def get_comment(id):
    ...

Este endpoint é usado para realizar operações CRUD no objeto Comment.

O objeto Comment está vinculado ao objeto Post por uma chave estrangeira.

Solução de Problemas

Como executar o aplicativo Flask em modo autônomo

Você sempre pode executar um aplicativo Flask autônomo com o seguinte comando:

python3 /irisdev/app/community/app.py

Nota: você deve estar dentro do container para rodar este comando

docker exec -it iris-flask-template-iris-1 bash

Reinicie a aplicação no IRIS

Esteja no modo DEBUG, faça várias chamadas para o aplicativo e as alterações serão refletidas no aplicativo.

Como acessar o Portal de Gerenciamento do IRIS

Você pode acessar o Portal de Gerenciamento do IRIS acessandohttp://localhost:53795/csp/sys/UtilHome.csp.

Rode este template localmente

Para isso, você precisa ter o IRIS instalado em sua máquina.

Em seguida, você precisa criar um namespace chamado IRISAPP.

Instale os requisitos.

Instale IoP :

#init iop
iop --init

# carregue a produção 
iop -m /irisdev/app/community/interop/settings.py

# iniicie a produção
iop --start Python.Production

Configure a aplicação no portal de Segurança

0
0 48
Artigo Heloisa Paiva · Out. 4, 2024 4m read

Então, se você está acompanhando do post anterior ou entrando agora, vamos passar para o mundo dos aplicativos eBPF e dar uma olhada no Parca, que se baseia em nossa breve investigação de gargalos de desempenho usando eBPF, mas coloca um aplicativo incrível no topo do seu cluster para monitorar todas as suas cargas de trabalho iris, continuamente, em todo o cluster!

Perfilamento Contínuo com Parca, Cargas de Trabalho IRIS em Todo o Cluster

0
0 37
Artigo Heloisa Paiva · Out. 2, 2024 15m read

Eu estive na Cloud Native Security Con em Seattle com total intenção de entrar no dia de OTEL, então lendo o assunto de segurança aplicada a fluxos de trabalho Cloud Native nos dias seguintes até CTF como um exercício profissional. Isso foi felizmente finalizado por um novo entendimento de eBPF, que tem minhas telas, carreira, fluxos de trabalho e atitude com muita necessidade de uma melhoria com novas abordagens para resolver problemas de fluxo de trabalho.

Então, consegui chegar à festa do eBPF e desde então tenho participado de clínica após clínica sobre o assunto. Aqui, gostaria de "desempacotar" o eBPF como uma solução técnica, mapeada diretamente para o que fazemos na prática (mesmo que esteja um pouco fora), e passar pelo eBPF através da minha experimentação em suporte a cargas de trabalho InterSystems IRIS, particularmente no Kubernetes, mas não necessariamente vazio em cargas de trabalho autônomas.

Passos eBee com eBPF e fluxos de trabalho InterSystems IRIS

0
0 31
Artigo Heloisa Paiva · Set. 30, 2024 1m read

Tem um jeito fácil de adicionar um certificado (CA - certificate authority) às suas configurações no InterSystems IRIS 2019.1 (e 2018.1.2) no Windows e Mac. Você pode pedir para o IRIS usar a loja de certificados do sistema operacional usando:

%OSCertificateStore

no campo "File containing Trusted Certificate Authority X.509 certificate(s)". Aqui está uma imagem de como fazer isso no portal:

E aqui está um  link para a documentação que descreve isto. Está na lista de opções abaixo de  "File containing trusted Certificate Authority certificate(s)".

0
0 47
Artigo Heloisa Paiva · Set. 16, 2024 1m read

[FAQ] Preguntas frecuentes de InterSystems

O seguinte código baixa https://www.intersystems.com/assets/intersystems-logo.png e salva o arquivo como c:\temp\test.png.

É necessário definir uma configuração SSL chamada SSLTEST antes de executar esse código

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

Com o lançamento do InterSystems IRIS Cloud SQL, estamos recebendo perguntas frequentes sobre como estabelecer conexões seguras por JDBC e outras tecnologias de driver. Temos um ótimo resumo e uma documentação detalhada sobre as tecnologias de driver, mas essa documentação não chega a descrever ferramentas de cliente individuais, como DBeaver, nossa favorita. Neste artigo, vamos descrever as etapas para criar uma conexão segura no DBeaver para sua implantação do Cloud SQL.

Etapa 0: crie sua implantação

Primeiro, faça login no Cloud Services Portal e crie uma implantação do Cloud SQL. Você só precisa se lembrar de marcar a caixa para ativar conexões externas. Tirando isso, todas as configurações padrão devem funcionar bem.

Etapa 1: instale o certificado

Para uma conexão segura, vamos usar certificados para criptografar tudo o que é enviado por ela. Você pode baixar o certificado na página de detalhes da implantação pelo botão "Get X.509 certificate" (Obter certificado X.509):

Em seguida, precisamos armazenar esse certificado em um keystore confiável usando o utilitário keytool. Essa é uma parte padrão da infraestrutura Java, então nada é específico ao IRIS ou ao DBeaver até aqui. Use o comando abaixo para importar o certificado. A localização do arquivo do certificado certificateSQLaaS.pem não importa após executar esse comando, então ele pode ser excluído da pasta de downloads depois. Porém, a localização do arquivo keystore.jks importa. Portanto, execute o comando a partir de uma pasta que faça sentido: onde você continuará a usá-lo para outros fins ou na pasta de instalação do DBeaver, caso seja a única finalidade desse keystore.

keytool -importcert -file path-to-cert/cert-file.pem -keystore keystore.jks

Para mais detalhes, veja a documentação.

Etapa 2: crie um arquivo SSLConfig.properties

Em seguida, precisamos dizer ao driver JDBC do IRIS como encontrar esse keystore, o que é feito através de um arquivo SSLConfig.properties. Esse arquivo de texto simples precisa ser colocado no diretório de trabalho do programa Java que abrirá a conexão JDBC. No Windows, é o %LOCALAPPDATA%\DBeaver, que se traduz em C:\Users\<you>\AppData\Local\DBeaver. No Mac, é geralmente /Applications/DBeaverEE.app/Contents/MacOS. Como alternativa, você também pode criar o arquivo em outro lugar e definir o caminho completo como uma variável de ambiente chamada com.intersystems.SSLConfigFile.

Na sua forma mais simples, esse arquivo só precisa apontar para o keystore e incluir a senha. O caminho do arquivo keystore.jks precisa ter o escape adequado para a leitura pelo Java, então você precisará usar barras invertidas duplas no Windows.

trustStore=/path/to/keystore/keystore.jks
trustStorePassword=keystore-password

Há várias configurações adicionais que você pode definir através desse arquivo descritas na documentação, incluindo configurações do named, mas a indicada acima é suficiente.

Etapa 3: crie sua conexão do DBeaver

Agora que instalamos o certificado e especificamos onde o JDBC do IRIS pode encontrá-lo, podemos criar a conexão do DBeaver. Todas as configurações da guia "Main" no diálogo de criação da conexão podem ser encontradas na tela de detalhes da implementação conforme colada acima:

Só falta dizer para o DBeaver ativar a criptografia definindo o "connection security level" (nível de segurança da conexão) como 10 na guia "Driver properties" (Propriedades do driver):

É isso! Ao clicar em "Test Connection" (Testar conexão), você deve obter um polegar para cima ou uma mensagem de erro útil. No segundo caso, confira esta documentação para solução de problemas se não for óbvio o que deve ser alterado.

Observação para usuários Mac

Se você estiver usando o Mac, parece que há um bug no DBeaver em que o exposto acima pode não ser suficiente. A solução não é convencional, mas funciona. No campo Database/Schema, onde você normalmente colocaria 'USER', insira toda esta string:

USER:sslConnection=true;sslTrustStoreLocation=/pathToTruststore/truststore.jks;sslTrustStorePassword=123456;

Dica e outras sabedorias de @Rick Guidice 
 

0
0 393
Artigo Danusa Calixto · Ago. 8, 2023 11m read

Prefácio

O InterSystems IRIS a partir da versão 2022.2 inclui uma funcionalidade reformulada para JSON web tokens (JWTs). Antes localizada no pacote da classe %OAuth2, a classe JWT, junto com as outras classes da Web JSON (JWCs), agora está em %Net.JSON. Essa migração ocorreu para modularizar as JWCs. Antes, eles estavam estreitamente ligados à implementação do framework OAuth 2.0. Agora, eles podem ser mantidos e usados separadamente.

Observação: para a compatibilidade com as versões anteriores, as classes ainda existem no pacote %OAuth2, mas a codebase agora usa %Net.JSON.

O objetivo deste artigo é servir como uma espécie de referência para as JWCs porque, ao tentar escrever uma documentação, não encontrei nenhuma fonte com informações abrangentes sobre todas elas e como se relacionam entre si. Espero que este artigo seja essa fonte.

Prólogo

O que são as JWCs?

As classes da Web JSON são os protocolos da Web que usam estruturas de dados baseadas em JSON. Elas são úteis para a autorização e a troca de informações, como OAuth 2.0 e OpenID Connect.

No momento, o InterSystems IRIS 2022.2+ é compatível com sete classes em %Net.JSON: (clique para abrir a definição)

> JSON Object Signing and Encryption (JOSE) Um conjunto de padrões para assinar e criptografar dados usando estruturas de dados baseadas em JSON. Isso inclui JWT, JWS, JWE, JWA, JWK e JWKS.
> JSON Web Token (JWT) Uma maneira compacta e segura para o URL de representar informações transferidas entre duas partes que podem ser assinadas digitalmente, criptografadas ou ambos.
> JSON Web Signature (JWS) Uma JWS representa o conteúdo assinado usando estruturas de dados baseadas em JSON. O JWA define os algoritmos de assinatura e verificação para a JWS. Ou seja, um JWT assinado.
> JSON Web Encryption (JWE) Uma JWE representa o conteúdo criptografado usando estruturas de dados baseadas em JSON. A JWA define os algoritmos de criptografia e descriptografia para a JWE. Ou seja, um JWT criptografado.
> JSON Web Algorithms (JWA) Um JWA define um conjunto de algoritmos criptográficos e identificadores usados com as classes JWS, JWE e JWK.
> JSON Web Key (JWK) Uma JWK representa uma chave criptográfica usada como entrada para os algoritmos definidos na classe JWA.
> JSON Web Key Set (JWKS) Um JWKS é um conjunto de JWKs

O diagrama a seguir demonstra a relação entre as JWCs, conforme definido abaixo: Fluxo de JWCs

Vamos analisar cada parte na seção a seguir.

Um estudo do JWT

Um JSON web token (JWT) é uma maneira compacta e segura para o URL de representar informações transferidas entre duas partes que podem ser assinadas digitalmente, criptografadas ou ambos.

Há dois tipos de JWTs:

  • JWS
  • JWE

Uma JWS é um JWT assinado e uma JWE é um JWT criptografado. Na linguagem corrente, falamos "JWT" ou "JWT criptografado". Para JWTs, o padrão é que sejam assinados, ainda que possam não ser assinados (JWTs não seguros).

Mas vamos voltar um pouco — o que é uma informação? É apenas um pedaço de informação representado em um par de chave/valor que um cliente está afirmando ser verdadeiro. Por exemplo, pode ser uma informação sobre um cliente que está tentando fazer login de um determinado local. O seguinte objeto JSON contém três informações (username, location, and admin):

{
    "username": "persephone",
    "location": "underworld",
    "admin": "true"
}

Então, os JWTs transferem informações assim entre duas partes, como cliente e servidor. No entanto, se apenas essas informações fossem transferidas, não haveria garantia de que ninguém adulterou ou viu o conteúdo, além do destinatário. Nossa mensagem não teria integridade ou confidencialidade. Felizmente, os JWTs fornecem uma maneira opcional de garantia com ambos os padrões JSON web signature (JWS) e JSON web encryption (JWE). Seja uma JWS ou JWE, um JWT tem várias partes.

JSON Web Signature (JWS)

Um JWT não criptografado e assinado (portanto, chamado de JWS) possui três partes:

  1. Header (cabeçalho)
  2. Payload (carga útil)
  3. Signature (assinatura)

Cada parte é um objeto JSON. Se você fizer a codificação Base64URL de cada parte e concatená-las com um ponto (.) entre elas, você terá um JWT (header.payload.signature).

Vamos analisar cada parte.

Cabeçalho

O cabeçalho de uma JWS consiste em metadados sobre o tipo de token e, se especificado, o JSON web algorithm (JWA) necessário para a validação da assinatura do token. Por exemplo:

{
  "alg": "HS256",
  "typ": "JWT"
}

Codifique o texto abaixo em Base64URL e você terá a primeira parte de um JWT!

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Para resumir, o cabeçalho contém o JWA e o tipo do token.

Carga útil

A segunda parte de uma JWS é a carga útil. Ela contém as informações. Usando o exemplo anterior, a carga útil pode ser o seguinte:

{
    "username": "persephone",
    "location": "underworld",
    "admin": "true"
}

Depois de codificar em Base64URL, obtemos isto:

eyJ1c2VybmFtZSI6InBlcnNlcGhvbmUiLCJsb2NhdGlvbiI6InVuZGVyd29ybGQiLCJhZG1pbiI6InRydWUifQ

Agora, nossa JWS está desta forma:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InBlcnNlcGhvbmUiLCJsb2NhdGlvbiI6InVuZGVyd29ybGQiLCJhZG1pbiI6InRydWUifQ

Para resumir, a carga útil contém as informações que você quer transmitir.

Assinatura

A terceira parte de uma JWS é a assinatura. Você pega o JWT até o momento (o cabeçalho e a carga útil criptografados), um segredo [também chamado de chave privada ou JSON web key (JWK)], o algoritmo especificado no JWA e assina isso. Desta forma:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

Usando o segredo/valor da JWK thecatsmeow, nossa JWS ficará assim no final:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InBlcnNlcGhvbmUiLCJsb2NhdGlvbiI6InVuZGVyd29ybGQiLCJhZG1pbiI6InRydWUifQ.KAZcjC9tqRV4DunI3sSma3k6fvL5ntgLXe9Gl7hKg5o

Para resumir, a assinatura contém a validação da integridade.

Abstraindo um pouco, temos:

JSON web signature

Você notou que eu falei...

Talvez você tenha percebido que eu qualifiquei nosso JWT como "pode ser assinado digitalmente, criptografado ou ambos".

Vamos falar sobre a parte da assinatura primeiro e depois abordar a criptografia na próxima seção. É possível não assinar a JWS e só ter um cabeçalho e uma carga útil (então, não é mais uma JWS, e sim um JWT não assinado e não criptografado). Isso é possível se o JWA especificado no cabeçalho para "none".

Desta forma:

{
  "alg": "none",
  "typ": "JWT"
}

Então, o JWT resultante seria apenas o cabeçalho criptografado em Base64URL + . + carga útil criptografada em Base64URL + .. A assinatura seria uma string vazia, então um JWT não seguro ficaria, por exemplo, desta forma:

eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.

Mas não envie JWTs não seguros, porque qualquer pessoa poderá adulterá-los e você não saberá se as informações são válidas.

JSON Web Encryption (JWE)

Repetindo, um JWT pode ser assinado digitalmente, criptografado ou ambos. Já discutimos as assinaturas com JWSs, então vamos seguir para a criptografia.

Como observado acima, um JWT não criptografado e assinado (JWS) tem três partes: cabeçalho, carga útil e assinatura.

Agora, um JWT criptografado (portanto, uma JWE) possui cinco partes:

  1. Cabeçalho protegido
  2. Chave criptografada
  3. Vetor de inicialização (IV)
  4. Carga útil/texto cifrado
  5. Tag de autenticação

Cabeçalho protegido

O cabeçalho protegido é a primeira parte de uma JWE. Ele não é criptografado porque o destinatário precisa saber como descriptografar o restante da JWE. Para informá-lo sobre como fazer isso, ele contém informações, como 1) o algoritmo usado para criptografar a chave de criptografia do conteúdo (CEK) e também produzir a chave criptografada e 2) o algoritmo usado para criptografar a carga útil e produzir o texto cifrado e a tag de autenticação.

Veja a seguir um exemplo de cabeçalho protegido:

{
  "alg":"RSA-OAEP",
  "enc":"A256GCM"
}

Ele é codificado em Base64URL para produzir o seguinte:

eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ

Chave criptografada

A segunda parte da JWE é a chave criptografada. A chave criptografada é a forma criptografada da CEK, que é uma chave simétrica usada para criptografar a carga útil e produzir o texto cifrado e a tag de autenticação. A CEK é criptografada usando o algoritmo especificado no valor "alg" no cabeçalho protegido e na chave pública do destinatário.

Com uma CEK de thecatsmeowmeows e uma chave pública RSA de 1024 bits gerada aleatoriamente, o valor da chave criptografada usando o algoritmo RSA-OAEP pode ser:

X6znPIKWHnO8MhHD2scnUv7PVAo8VfxHYxmZQR0J8/rqGOB+udq8DkXd93n7S2cS3LT1Inx4qQ5J8GquQyc2xfS5n2INgKjSedYac4LBCkmpYRbRyNawK2eMEUDkcdBlqBE4NlWcAhl6X0H4AiNs7r+P8ffipvyztd51JdLoTlw=

A chave criptografada é codificada em Base64URL e concatenada ao cabeçalho protegido. Portanto, temos esta JWE até o momento:

eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.WDZ6blBJS1dIbk84TWhIRDJzY25VdjdQVkFvOFZmeEhZeG1aUVIwSjgvcnFHT0IrdWRxOERrWGQ5M243UzJjUzNMVDFJbng0cVE1SjhHcXVReWMyeGZTNW4ySU5nS2pTZWRZYWM0TEJDa21wWVJiUnlOYXdLMmVNRVVEa2NkQmxxQkU0TmxXY0FobDZYMEg0QWlOczdyK1A4ZmZpcHZ5enRkNTFKZExvVGx3PQ

Vetor de inicialização (IV)

O IV é a terceira parte da JWE. Ele é gerado aleatoriamente e codificado em Base64URL. Não precisa ser secreto, portanto não é criptografado. Por exemplo, catsarefantastic. Em seguida, codificamos em Base64URL e concatenamos com o cabeçalho protegido codificado e as partes da chave criptografada para obter:

eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.WDZ6blBJS1dIbk84TWhIRDJzY25VdjdQVkFvOFZmeEhZeG1aUVIwSjgvcnFHT0IrdWRxOERrWGQ5M243UzJjUzNMVDFJbng0cVE1SjhHcXVReWMyeGZTNW4ySU5nS2pTZWRZYWM0TEJDa21wWVJiUnlOYXdLMmVNRVVEa2NkQmxxQkU0TmxXY0FobDZYMEg0QWlOczdyK1A4ZmZpcHZ5enRkNTFKZExvVGx3PQ.Y2F0c2FyZWZhbnRhc3RpYw

Carga útil/texto cifrado

A quarta parte de uma JWE é a carga útil/texto cifrado. É onde aninhamos o JWT. Até aqui, abordamos como proteger e depois ler esses dados após o recebimento, mas agora podemos falar sobre os dados. Tudo o que discutimos na seção de JWS é aplicável aqui. Temos nossa JWS de três partes com o cabeçalho, a carga útil e a assinatura. Em seguida, usando a CEK e o IV, criptografamos a JWS usando AES GCM e solicitamos uma saída de tag de autenticação de 128 bits.

Um exemplo de um possível texto cifrado codificado em Base64URL:

NjI0YjZkZTlmMGEzNjk0MTgyMTYyNTc3MmEyMjM4ZWY0MTJhMzljMzJiOGVjNzVjMzU4MGIzNTVhMGUyN2M1MWYyY2Y2OGIyYmNkODM2YmNiZjBkOGIzZjMzMmQyODBlZWZhNjBkYTQ5M2VlNjRhMTg4NmMzYTFlY2E2OGQ0NjkyOGQzNTFjOWFjODdhY2QzZDc0ZTY4OTc1MTA4NzQ0NTEyNTJhOGM5N2U3OGFkNjJhMmNmMWQwNzM5MmQwYzcyM2EzMjg5MWI2YjlmMzRkNmYxMDU5YTVlMTljZThjMTNkNzFlMjgzZWY1ZGM0ZDdmZTNhMzk1YmM2MDE5NjRmZmMwYmZlMDM2YWY1MzZmYTdiYTYzNWU3NTJmMzk1OTBhY2Y2ZWM4YjlmZjBmYzY1ZTM0M2U5YzE4OTk0ZjAyYTZlNDA0NjEzNDM1ZTVhMQ

Temos esta JWE até o momento:

eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.WDZ6blBJS1dIbk84TWhIRDJzY25VdjdQVkFvOFZmeEhZeG1aUVIwSjgvcnFHT0IrdWRxOERrWGQ5M243UzJjUzNMVDFJbng0cVE1SjhHcXVReWMyeGZTNW4ySU5nS2pTZWRZYWM0TEJDa21wWVJiUnlOYXdLMmVNRVVEa2NkQmxxQkU0TmxXY0FobDZYMEg0QWlOczdyK1A4ZmZpcHZ5enRkNTFKZExvVGx3PQ.Y2F0c2FyZWZhbnRhc3RpYw.NjI0YjZkZTlmMGEzNjk0MTgyMTYyNTc3MmEyMjM4ZWY0MTJhMzljMzJiOGVjNzVjMzU4MGIzNTVhMGUyN2M1MWYyY2Y2OGIyYmNkODM2YmNiZjBkOGIzZjMzMmQyODBlZWZhNjBkYTQ5M2VlNjRhMTg4NmMzYTFlY2E2OGQ0NjkyOGQzNTFjOWFjODdhY2QzZDc0ZTY4OTc1MTA4NzQ0NTEyNTJhOGM5N2U3OGFkNjJhMmNmMWQwNzM5MmQwYzcyM2EzMjg5MWI2YjlmMzRkNmYxMDU5YTVlMTljZThjMTNkNzFlMjgzZWY1ZGM0ZDdmZTNhMzk1YmM2MDE5NjRmZmMwYmZlMDM2YWY1MzZmYTdiYTYzNWU3NTJmMzk1OTBhY2Y2ZWM4YjlmZjBmYzY1ZTM0M2U5YzE4OTk0ZjAyYTZlNDA0NjEzNDM1ZTVhMQ

Tag de autenticação

A tag de autenticação é a parte final da JWE. É um resultado do texto cifrado (JWS aninhada criptografada). A tag de autenticação recebida ao criptografar o texto cifrado na última seção é:

9f19e30efeddf20f5232b76f07c755ac

Codificamos em Base64URL e concatenamos com a JWE para obter a JWE finalizada como:

eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.WDZ6blBJS1dIbk84TWhIRDJzY25VdjdQVkFvOFZmeEhZeG1aUVIwSjgvcnFHT0IrdWRxOERrWGQ5M243UzJjUzNMVDFJbng0cVE1SjhHcXVReWMyeGZTNW4ySU5nS2pTZWRZYWM0TEJDa21wWVJiUnlOYXdLMmVNRVVEa2NkQmxxQkU0TmxXY0FobDZYMEg0QWlOczdyK1A4ZmZpcHZ5enRkNTFKZExvVGx3PQ.Y2F0c2FyZWZhbnRhc3RpYw.NjI0YjZkZTlmMGEzNjk0MTgyMTYyNTc3MmEyMjM4ZWY0MTJhMzljMzJiOGVjNzVjMzU4MGIzNTVhMGUyN2M1MWYyY2Y2OGIyYmNkODM2YmNiZjBkOGIzZjMzMmQyODBlZWZhNjBkYTQ5M2VlNjRhMTg4NmMzYTFlY2E2OGQ0NjkyOGQzNTFjOWFjODdhY2QzZDc0ZTY4OTc1MTA4NzQ0NTEyNTJhOGM5N2U3OGFkNjJhMmNmMWQwNzM5MmQwYzcyM2EzMjg5MWI2YjlmMzRkNmYxMDU5YTVlMTljZThjMTNkNzFlMjgzZWY1ZGM0ZDdmZTNhMzk1YmM2MDE5NjRmZmMwYmZlMDM2YWY1MzZmYTdiYTYzNWU3NTJmMzk1OTBhY2Y2ZWM4YjlmZjBmYzY1ZTM0M2U5YzE4OTk0ZjAyYTZlNDA0NjEzNDM1ZTVhMQ.OWYxOWUzMGVmZWRkZjIwZjUyMzJiNzZmMDdjNzU1YWM

Vamos abstrair a codificação em Base64URL e analisar a visão geral de alto nível de uma JWE:

JSON web encryption

Observações sobre os termos

Em busca de clareza, em vez de usar alguns nomes de classes da Web JSON, dei preferência para os nomes descritivos. Especificamente, JWK/JWKS em vez de chaves públicas, criptográficas, de criptografia, etc. A chave usada para a criptografia ou assinatura é uma JSON web key (JWK) e o conjunto delas (a chave simétrica e os pares de chaves assimétricas) é um JSON web key set (JWKS).

Queria fazer uma pausa para mencionar isso e retomar a terminologia. Um JWT é uma JWS ou JWE. Os algoritmos usados em uma JWS/JWE são definidos no JWA. As chaves usadas como entrada para os algoritmos no JWA são JWKs, armazenadas como um conjunto JWKS.

E o que é JOSE? JOSE é a coleção de padrões. Assim como temos bando para corvos ou gataria para gatos, temos JOSE para os padrões da Web JSON.

Conclusão

Espero que este artigo seja um bom ponto de referência para quem quer trabalhar com as classes da Web JSON (JWCs).

As JWCs têm três casos de uso diferentes:

  1. Autenticação
  2. Autorização
  3. Troca de informações

No InterSystems IRIS 2022.2+, você pode configurar o OAuth 2.0 para usar JWTs. Isso está descrito no OAuth 2.0 e OpenID Connect. Seu código personalizado pode usar as JWCs conforme definido no %Net.JSON para esses casos de uso.

Me avise se você achou este artigo útil!

0
0 82
Artigo Vitor Oliveira · Maio 13, 2023 5m read

De acordo com o relatório Global Fraud and Identity Report 2020 da Experian, as fraudes no setor financeiro globalmente ultrapassaram a marca de US$ 42 bilhões em 2020, com destaque para fraudes de identidade, bancárias, em cartões de crédito e débito, em empréstimos e em aplicativos móveis bancários. A pandemia do COVID-19 impulsionou o crescimento de fraudes relacionadas à saúde, como fraudes em benefícios de seguro-saúde e em programas de ajuda financeira do governo. Considerando tais dados, fica claro que é essencial que as instituições financeiras adotem soluções eficientes para detectar

3
14 232
Artigo Danusa Calixto · Maio 9, 2023 10m read

Prefácio

O InterSystems IRIS a partir da versão 2022.2 inclui a capacidade de autenticar uma API REST usando JSON web tokens (JWTs). Esse recurso aprimora a segurança ao limitar quando e com que frequência as senhas são transferidas pela rede, além de definir um tempo de expiração para o acesso.

O objetivo deste artigo é servir como um tutorial de como implementar uma API REST simulada usando o InterSystems IRIS e bloquear o acesso a ela com JWTs.

OBSERVAÇÃO NÃO sou uma desenvolvedora. Não faço alegações sobre a eficiência, escalabilidade ou qualidade das amostras de código que uso neste artigo. Estes exemplos são APENAS para fins educacionais. Eles NÃO se destinam a código de produção.

Prólogo

Depois de fazer esse aviso, vamos explorar os conceitos que serão analisados aqui.

O que é REST?

REST é um acrônimo para "REpresentational State Transfer". É uma arquitetura para os programas se comunicarem com os aplicativos da Web e acessarem as funções publicadas por esses aplicativos.

O que é um JWT?

Um JSON web token (JWT) é uma maneira compacta e segura para o URL de representar informações transferidas entre duas partes que podem ser assinadas digitalmente, criptografadas ou ambos. Se você quiser saber mais sobre os JWTs e outras classes da Web JSON compatíveis com o InterSystems IRIS, leia esta postagem.

Colocando a mão na massa

De acordo com a especificação

Para consumir uma API REST, primeiro precisamos ter uma API REST. Disponibilizei uma amostra de especificação da OpenAPI 2.0 para um RPG de mesa (TTRPG). Ela será usada nos exemplos deste artigo. Há vários exemplos de como escrever a sua online, então fique à vontade para se aprofundar nisso, mas a especificação é apenas um modelo. A única coisa que faz é informar como usar a API.

Geração da API REST

O InterSystems IRIS oferece uma maneira bastante organizada de gerar stubs de código da API REST. Esta documentação oferece uma maneira completa de gerar stubs de código. Fique à vontade para usar a especificação da OpenAPI 2.0 que forneci na seção anterior.

Implementação

É aqui que vamos ir a fundo. A seção de geração criará três arquivos .cls para você:

  1. impl.cls
  2. disp.cls
  3. spec.cls

Vamos passar a maior parte do nosso tempo no impl.cls, talvez mexer no disp.cls para depurar e não encostar no spec.cls.

No impl.cls, há stubs de código para os métodos que disp.cls chamará quando receber uma solicitação da API. A especificação da OpenAPI definida nessas assinaturas. Ela informa o que você quer que seja feito, mas é você quem precisa implementar isso no final. Então, vamos fazer isso!

Criação

Uma das maneiras que usamos um banco de dados é adicionando objetos a ele. Esses objetos servem como uma base para outras funções. Sem objetos existentes, não haverá nada para ver, então vamos começar com nosso modelo de objeto: um Character (personagem)!

Um Character precisa ter nome e, como opção, especificar a classe, a raça e o nível. Veja abaixo um exemplo de implementação da classe TTRPG.Character

Class TTRPG.Character Extends %Persistent
{

Property Name As %String [ Required ];

Property Race As %String;

Property Class As %String;

Property Level As %String;

Index IndexName On Name [ IdKey ];

ClassMethod GetCharByName(name As %String) As TTRPG.Character
{
    set character = ##class(TTRPG.Character).%OpenId(name)

    Quit character
}
}

Já que queremos armazenar objetos Character no banco de dados, precisamos herdar a classe %Persistent. Queremos que seja possível procurar personagens pelo nome, em vez de atribuir uma chave de ID arbitrária. Portanto, definimos o atributo [ IdKey ] no Index para a propriedade Character.Name. Isso também garante a exclusividade do nome do personagem.

Com nosso modelo de objeto base definido, podemos analisar a implementação da API REST. O primeiro método que vamos explorar é o PostCharacter.

Como visão geral, esta parte consome uma solicitação HTTP POST para o endpoint /characters com nossas propriedades de personagem definidas no corpo. Ela deve pegar os argumentos fornecidos e criar um objeto TTRPG.Character a partir deles, salvá-lo no banco de dados e informar se teve êxito ou não.

ClassMethod PostCharacter(name As %String, class As %String, race As %String, level As %String) As %DynamicObject
{
    set results = {} // cria o retorno %DynamicObject

    //cria o objeto character
    set char = ##class(TTRPG.Character).%New()

    set char.Name = name
    set char.Class = class
    set char.Race = race
    set char.Level = level
    set st = char.%Save()

    if st {
        set charInfo = {}
        set charInfo.Name = char.Name
        set charInfo.Class = char.Class
        set charInfo.Race = char.Race
        set charInfo.Level = char.Level
        set results.Character = charInfo
        Set results.Status = "success"
    }
    else {
        Set results.Status = "error"
        Set results.Message = "Unable to create the character"
    }
    Quit results
}

Agora que podemos criar personagens, como buscamos aquele que acabamos de criar? De acordo com a especificação da OpenAPI, o endpoint /characters/{charName} permite a busca de um personagem pelo nome. Buscamos a instância do personagem, se ela existir. Se não existir, retornamos um erro para informar ao usuário que não existe um personagem com o nome fornecido. Isso é implementado no método GetCharacterByName.

ClassMethod GetCharacterByName(charName As %String) As %DynamicObject
{
   // Cria um novo objeto dinâmico para armazenar os resultados
        Set results = {}

        set char = ##class(TTRPG.Character).GetCharByName(charName)

        if char {
           set charInfo = {}
            set charInfo.Name = char.Name
            set charInfo.Class = char.Class
            set charInfo.Race = char.Race
            set charInfo.Level = char.Level
            set results.Character = charInfo
            Set results.Status = "success"
        }
        // Se nenhum character foi encontrado, define uma mensagem de erro no objeto dos resultados
        else {
            Set results.Status = "error"
            Set results.Message = "No characters found"
        }

        // Retorna o objeto dos resultados
        Quit results
}

Mas isso é só o seu personagem. E todos os personagens que as outras pessoas criaram? Podemos ver esses personagens usando o método GetCharacterList. Ele consome uma solicitação HTTP GET para o endpoint /characters para compilar uma lista de todos os personagens no banco de dados e retorna essa lista.

ClassMethod GetCharacterList() As %DynamicObject
{
    // Cria um novo objeto dinâmico para armazenar os resultados
        Set results = {}
        set query = "SELECT Name, Class, Race, ""Level"" FROM TTRPG.""Character"""
        set tStatement = ##class(%SQL.Statement).%New()
        set qstatus = tStatement.%Prepare(query)
        if qstatus '= 1 { Do ##class(TTRPG.impl).%WriteResponse("Error: " _ $SYSTEM.Status.DisplayError(qstatus)) }
        set rset = tStatement.%Execute()
        Set characterList = []
        while rset.%Next(){
            Set characterInfo = {}
            Set characterInfo.Name = rset.Name
            set characterInfo.Race = rset.Race
            Set characterInfo.Class = rset.Class
            Set characterInfo.Level = rset.Level 

            Do characterList.%Push(characterInfo)

        }
        if (rset.%SQLCODE < 0) {write "%Next failed:", !, "SQLCODE ", rset.%SQLCODE, ": ", rset.%Message quit}

        set totalCount = rset.%ROWCOUNT

            // Define as propriedades status, totalCount e characterList no objeto dos resultados
            Set results.Status = "success"
            Set results.TotalCount = totalCount
            Set results.CharacterList = characterList


        // Retorna o objeto dos resultados
        Quit results
}

E essa é nossa API! A especificação atual não oferece uma maneira de atualizar ou excluir personagens do banco de dados, e isso fica como um exercício para o leitor!

Configuração do IRIS

Agora que implementamos nossa API REST, como fazemos a comunicação com o IRIS? No Portal de Gerenciamento, se você acessar a página System Administration > Security > Applications > Web Applications, poderá criar um novo aplicativo da Web. O nome do aplicativo é o endpoint que você usará ao fazer solicitações. Por exemplo, se o nome for /api/TTRPG/, as solicitações da API vão para http://{IRISServer}:{host}/api/TTRPG/{endpoint}. Para uma instalação do IRIS padrão local com segurança normal, é assim: http://localhost:52773/api/TTRPG/{endpoint}. Adicione uma boa descrição, defina o namespace desejado e clique no botão de opção para REST. Para ativar a autenticação JWT, selecione a caixa "Use JWT Authentication". O JWT Access Token Timeout determina a frequência com que o usuário precisará receber um novo JWT. Se você planeja testar a API por um longo período, recomendo definir esse valor como uma hora (3600 segundos) e o JWT Refresh Token Timeout (o período de renovação antes que o token expire para sempre) como 900 segundos.

configuração do web app

Agora que o aplicativo foi configurado, precisamos configurar o próprio IRIS para permitir a autenticação de JWT. É possível configurar essa opção em System Administration > Security > System Security > Authentication/Web Session Options. Na parte inferior, está o campo do emissor de JWT e o algoritmo de assinatura que será usado para assinar e validar os JWTs. O campo do emissor aparecerá na seção de informações do JWT e a finalidade é informar quem forneceu o token a você. Você pode defini-lo como "InterSystems".

configuração da autenticação de JWT

Hora de testar

Está tudo configurado e implementado, então vamos testar! Carregue sua ferramenta favorita para criar solicitações de API (vou usar uma extensão do Firefox chamada RESTer nos exemplos) e vamos começar a construir solicitações da API REST.

Primeiro, vamos tentar listar os personagens existentes.

lista sem token

Recebemos um erro 401 Unauthorized. Isso ocorreu porque não fizemos login. Você talvez esteja pensando: Elliott, não implementamos funcionalidade de login nessa API REST. Não tem problema, porque o InterSystems IRIS cuida disso para nós quando usamos a autenticação de JWT. Ele oferece quatro endpoints que podemos usar para gerenciar nossa sessão. São eles: /login, /logout/revoke e /refresh. Eles podem ser personalizados no disp.cls conforme o exemplo abaixo:

Parameter TokenLoginEndpoint = "mylogin";
Parameter TokenLogoutEndpoint = "mylogout";
Parameter TokenRevokeEndpoint = "myrevoke";
Parameter TokenRefreshEndpoint = "myrefresh";

Vamos acessar o endpoint /login agora.

fazendo login

O corpo dessa solicitação não é exibido por medidas de segurança, mas ele segue esta estrutura JSON:

{"user":"{YOURUSER}", "password":"{YOURPASSWORD}"}

Em troca da senha, recebemos um JWT! Esse é o valor de "access_token". Vamos copiar isso e usar nas nossas solicitações futuras para não precisar sempre transmitir a senha.

Agora que temos um JWT para autenticação, vamos tentar criar um personagem!

Formatamos nossa solicitação conforme abaixo:

criação de personagem

Usando o bearer token como cabeçalho no formato de " Authorization: Bearer {JWTValue}". Em uma solicitação curl, você pode escrever isso com -H "Authorization: Bearer {JWTValue}"

Vamos criar outro personagem por diversão, usando os valores que você quiser.

Agora, vamos tentar listar todos os personagens que existem no banco de dados.

listando os personagens

Obtemos os dois personagens que criamos! E se só quisermos acessar um? Implementamos isso com o endpoint /characters/{charName}. Podemos formatar essa solicitação desta forma:

buscando personagem específico

Essa é a nossa API REST em ação, pessoal! Quando concluir sua sessão, é possível sair no endpoint /logout usando seu JWT. Isso revogará e bloqueará o JWT para que não seja possível usá-lo novamente.

Conclusão

O InterSystems IRIS a partir da versão 2022.2 inclui a capacidade de autenticar uma API REST usando JSON web tokens (JWTs). Esse recurso aprimora a segurança ao limitar o uso da senha e definir uma data de expiração para o acesso à API.

Espero que este manual sobre como gerar uma API REST e protegê-la com JWTs pelo IRIS tenha sido útil. Me avise se foi! Agradeço qualquer feedback.

0
0 189
Artigo Fabiano Sanches · Abr. 26, 2023 1m read

Mantenha contato com a InterSystems e receba alertas, avisos e outras novidades sobre os produtos rapidamente. O processo é realmente simples:

Como você pode ver, leva menos de um minuto pra ficar informado sobre as novidades!

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

Criado por Daniel Kutac, Engenheiro de vendas, InterSystems

Parte 3. Apêndice

Explicação sobre as classes OAUTH do InterSystems IRIS

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

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

Classes internas

Estas classes pertencem ao pacote OAuth2.

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

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

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

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

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

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

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

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

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

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

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

Classes de personalização do servidor

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

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

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

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

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

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

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

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

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

Classes de API públicas

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

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

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

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

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

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

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

Este é o fragmento de código:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Personalização

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

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

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

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

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

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

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

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

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

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

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

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

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

}

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

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

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

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

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

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

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

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

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

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

Depuração

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

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

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

}

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

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

Resumo

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

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

0
0 149
Anúncio Danusa Calixto · Dez. 22, 2022

Se quiser experimentar o novo processo de instalação do projeto NoPWS, você pode obter acesso ao Early Access Program (EAP) aqui. (https://evaluation.intersystems.com/Eval/)

Depois de se registrar, envie à InterSystems o endereço de e-mail que você usou para se registrar no EAP para nopws@intersystems.com.

Veja aqui: Post Original

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

Criado por Daniel Kutac, Engenheiro de vendas, InterSystems

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

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

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

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

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

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

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

Demonstração complexa do OpenID Connect

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

Pré-requisitos

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

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

PKI

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

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

Vamos chamá-los de

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

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

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

Credenciais X.509

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

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

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

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

Configuração do servidor cliente

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

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

Configuração do servidor de recursos

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

Configuração do OAUTH

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

AUTHSERVER

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

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

·        nome do host

·        porta (opcional)

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

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

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

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

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

·        especifique os intervalos de endpoint

·        defina os escopos compatíveis com esse servidor

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

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

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

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

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

Esta imagem mostra a configuração do CLIENT.

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

Da mesma forma, a configuração do RESSERVER

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

CLIENT

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

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

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

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

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

RESSERVER

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

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

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

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

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

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

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

Aplicativo cliente

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

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

Página 1

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

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

Parameter OAUTH2APPNAME = "demo client";

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

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

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

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

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

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

}

Página 2

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

Parameter OAUTH2APPNAME = "demo client";

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

Parameter SSLCONFIG = "SSL4CLIENT";

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




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

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

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

}

As seguintes capturas de tela retratam o processamento:

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

Página de consentimento do usuário em AUTHSERVER

E, por fim, a página resultante

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

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

Aplicativo de recurso

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

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

Alternativa 1 — sem autenticação

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

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

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

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

Um exemplo de código-fonte:

Class oauth2test.demoResource Extends %CSP.Page
{

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

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

}

Alternativa 2 — autenticação delegada

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

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

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

·        Ativar a autenticação delegada

·        Fornecer a rotina ZAUTHENTICATE

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

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

Veja o exemplo de uma rotina ZAUTHENTICATE

#include %occErrors
#include %occInclude

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

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

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

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

Class oauth2test.demoResource Extends %CSP.Page
{

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

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

}

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

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

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

 

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

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

0
0 218
Artigo Daniel Kutac · Nov. 19, 2022 14m read

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

Histórico de correções e alterações após a publicação

  • 3 de agosto de 2016 – Correção da captura de tela da configuração do Google Client; atualização da captura de tela das APIs do Google para refletir a nova versão das páginas.
  • 28 de agosto de 2016 – Alterações do código JSON devido às mudanças no suporte ao JSON do Caché 2016.2.
  • 3 de maio de 2017 – Atualizações do texto e imagens para refletir a nova interface gráfica e os novos recursos lançados no Caché 2017.1. 
  • 19 de fevereiro de 2018 – Alteração de Caché para InterSystems IRIS para refletir os desenvolvimentos mais recentes. Porém, é importante salientar que, apesar da alteração do nome do produto, o artigo aborda todos os produtos da InterSystems: InterSystems IRIS Data Platform, Ensemble e Caché.
  • 17 de agosto de 2020 – Tudo muda, especialmente o software. Consulte o URL do OAuth2 do Google atualizado na resposta do Micholai Mitchko.

Parte 1. Cliente

Introdução

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.

Sobre o Open Authorization Framework[1]

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

Demonstração simples do Google Drive

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).

Pré-requisitos

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.

Configuração do servidor web

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.

Configuração do Google

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

Configuração do Caché

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.

Aplicação cliente

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.

Servidor cliente

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

Página 1

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.

Página 2

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.

0
0 240
Anúncio Angelo Bruno Braga · Out. 24, 2022

Certifique-se em Administração de Sistema InterSystems IRIS!

Olá Comunidade,

Após o teste beta do novo exame de Certificação de Especialista em Administração de Sistemas InterSystems IRIS, a equipe de certificação dos Serviços de Aprendizagem InterSystems realizou a calibração e os ajustes necessários para liberá-lo para nossa comunidade. Já está pronto para compra e agendamento no catálogo de exames de certificação da InterSystems. Os candidatos em potencial podem analisar os tópicos do exame e as questões práticas para ajudar a orientá-los quanto às abordagens e conteúdo das questões do exame. A aprovação no exame permite que você reivindique um selo de certificação eletrônico que pode ser incorporado a contas de mídia social, como o Linkedin..  

0
0 73
Artigo Danusa Calixto · Jul. 11, 2022 10m read


O InterSystems IRIS tem um suporte excelente para operações de criptografia, descriptografia e hashing. Na classe %SYSTEM.Encryption (https://docs.intersystems.com/iris20212/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&PRIVATE=1&CLASSNAME=%25SYSTEM.Encryption), há métodos de classes para os principais algoritmos no mercado.

Algoritmos IRIS e tipos de criptografia/descriptografia

Como você pode ver, as operações são baseadas em chaves e incluem 3 opções:

  • Chaves simétricas: as partes que executam as operações de criptografia e descriptografia compartilham a mesma chave secreta.
  • Chaves assimétricas: as partes que conduzem as operações de criptografia e descriptografia compartilham a mesma chave secreta para a criptografia. No entanto, para a descriptografia, cada parceiro tem uma chave privada. Essa chave não pode ser compartilhada com outras pessoas, pois é uma prova de identidade.
  • Hash: é usado quando você só precisa criptografar, e não descriptografar. É uma abordagem comum para o armazenamento das senhas dos usuários.

Diferenças entre a criptografia simétrica e assimétrica

  • A criptografia simétrica usa uma única chave, compartilhada entre as pessoas que precisam receber a mensagem, enquanto a criptografia assimétrica usa um par de chaves públicas e uma chave privada para criptografar e descriptografar as mensagens durante a comunicação.
  • A criptografia simétrica é uma técnica antiga, enquanto a criptografia assimétrica é relativamente nova.
  • A criptografia assimétrica foi criada para complementar o problema inerente da necessidade de compartilhar a chave em um modelo de criptografia simétrica, sem precisar compartilhar a chave usando um par de chaves pública-privada.
  • A criptografia assimétrica leva mais tempo do que a criptografia simétrica.
< width="172">
  <th>
    Criptografia simétrica
  </th>
  
  <th>
    Criptografia assimétrica
  </th>
</tr>

<tr>
  <td>
    Tamanho do texto cifrado
  </td>
  
  <td>
    Texto cifrado menor do que o arquivo de texto simples original.
  </td>
  
  <td>
    Texto cifrado maior do que o arquivo de texto simples original.
  </td>
</tr>

<tr>
  <td>
    Tamanho dos dados
  </td>
  
  <td>
    Usada para transmitir dados grandes.
  </td>
  
  <td>
    Usada para transmitir dados pequenos.
  </td>
</tr>

<tr>
  <td>
    Uso de recursos
  </td>
  
  <td>
    A criptografia simétrica de chaves funciona com baixo uso de recursos.
  </td>
  
  <td>
    A criptografia assimétrica requer alto consumo de recursos.
  </td>
</tr>

<tr>
  <td>
    Comprimento da chave
  </td>
  
  <td>
    Tamanho de 128 ou 256-bit.
  </td>
  
  <td>
    Tamanho de RSA 2048-bit ou superior.
  </td>
</tr>

<tr>
  <td>
    Segurança
  </td>
  
  <td>
    Menos segura devido ao uso de uma única chave para criptografia.
  </td>
  
  <td>
    Muito mais segura já que duas chaves diferentes estão envolvidas na criptografia e descriptografia.
  </td>
</tr>

<tr>
  <td>
    Número de chaves
  </td>
  
  <td>
    A criptografia simétrica usa uma única chave para a criptografia e descriptografia.
  </td>
  
  <td>
    A criptografia assimétrica usa duas chaves diferentes para a criptografia e descriptografia
  </td>
</tr>

<tr>
  <td>
    Técnicas
  </td>
  
  <td>
    É uma técnica antiga.
  </td>
  
  <td>
    É uma técnica moderna.
  </td>
</tr>

<tr>
  <td>
    Confidencialidade
  </td>
  
  <td>
    Uma única chave para a criptografia e descriptografia tem o risco de ser comprometida.
  </td>
  
  <td>
    Duas chaves são criadas separadamente para a criptografia e descriptografia, o que elimina a necessidade de compartilhar uma chave.
  </td>
</tr>

<tr>
  <td>
    Velocidade
  </td>
  
  <td>
    A criptografia simétrica é uma técnica rápida.
  </td>
  
  <td>
    A criptografia assimétrica é mais lenta em termos de velocidade.
  </td>
</tr>

<tr>
  <td>
    Algoritmos
  </td>
  
  <td>
    RC4, AES, DES, 3DES e QUAD.
  </td>
  
  <td>
    RSA, Diffie-Hellman e ECC.
  </td>
</tr>
Principais diferenças

Fonte: https://www.ssl2buy.com/wiki/symmetric-vs-asymmetric-encryption-what-are-differences 

Usando a classe %SYSTEM.Encryption para criptografia, descriptografia e hash

Para treinar o suporte do IRIS para as operações de criptografia, descriptografia e hash, acesse https://github.com/yurimarx/cryptography-sample e siga estas etapas:

  1. Use o git pull ou clone o repositório em qualquer diretório local
$ git clone https://github.com/yurimarx/cryptography-samples.git
  1. Abra um terminal Docker nesse diretório e execute:
$ docker-compose build
  1. Execute o contêiner IRIS:
$ docker-compose up -d
  1. Abra o terminal IRIS:
$ docker-compose exec iris iris session iris -U IRISAPP

IRISAPP>
  1. Para a criptografia assimétrica com RSA, execute:
IRISAPP>Set ciphertext = ##class(dc.cryptosamples.Samples).DoRSAEncrypt("InterSystems")
IRISAPP>Write ciphertext
Ms/eR7pPmE39KBJu75EOYIxpFEd7qqoji61EfahJE1r9mGZX1NYuw5i2cPS5YwE3Aw6vPAeiEKXF
rYW++WtzMeRIRdCMbLG9PrCHD3iQHfZobBnuzx/JMXVc6a4TssbY9gk7qJ5BmlqRTU8zNJiiVmd8
pCFpJgwKzKkNrIgaQn48EgnwblmVkxSFnF2jwXpBt/naNudBguFUBthef2wfULl4uY00aZzHHNxA
bi15mzTdlSJu1vRtCQaEahng9ug7BZ6dyWCHOv74O/L5NEHI+jU+kHQeF2DJneE2yWNESzqhSECa
ZbRjjxNxiRn/HVAKyZdAjkGQVKUkyG8vjnc3Jw==
  1. Para a descriptografia assimétrica com RSA, execute:
IRISAPP>Set plaintext = ##class(dc.cryptosamples.Samples).DoRSADecrypt(ciphertext)
IRISAPP>Write plaintext
InterSystems
  1. Para a criptografia simétrica com AES CBC, execute:
IRISAPP>Do ##class(dc.cryptosamples.Samples).DoAESCBCEncrypt("InterSystems")
8sGVUikDZaJF+Z9UljFVAA==
  1. Para a descriptografia simétrica com AES CBC, execute:
IRISAPP>Do ##class(dc.cryptosamples.Samples).DoAESCBCDecrypt("8sGVUikDZaJF+Z9UljFVAA==")
InterSystems
  1. Para uma abordagem antiga com hash MD5, execute:
IRISAPP>Do ##class(dc.cryptosamples.Samples).DoHash("InterSystems")
rOs6HXfrnbEY5+JBdUJ8hw==
  1. Para uma abordagem recomendada com hash SHA, execute:
IRISAPP>Do ##class(dc.cryptosamples.Samples).DoSHAHash("InterSystems")
+X0hDlyoViPlWOm/825KvN3rRKB5cTU5EQTDLvPWM+E=
  1. Para sair do terminal:
Digite HALT ou H (não diferencia maiúsculas de minúsculas)

 

Sobre o código-fonte

1. Sobre a chave simétrica

# para usar com criptografia/descriptografia simétrica
ENVSECRETKEY=InterSystemsIRIS

No Dockerfile, foi criada uma chave do ambiente para ser usada como chave secreta em operações simétricas.

2. Sobre a chave assimétrica

# para usar com criptografia/descriptografia assimétrica, execute openssl req  -new -x509 -sha256 -config example-com.conf -newkey rsa:2048 -nodes -keyout example-com.key.pem  -days 365 -out example-com.cert.pem

No Dockerfile, foram geradas uma chave privada e uma chave pública para serem usadas com operações assimétricas.

3. Criptografia simétrica

//Amostra de chaves simétricas para criptografia
 
ClassMethodDoAESCBCEncrypt(plaintextAs%String)As%Status
{
    // converter para utf-8
    Settext=$ZCONVERT(plaintext,"O","UTF8")
   
    // definir uma chave secreta
    Setsecretkey=$system.Util.GetEnviron("SECRETKEY")
    SetIV=$system.Util.GetEnviron("SECRETKEY")
   
    // criptografar um texto
    Settext=$SYSTEM.Encryption.AESCBCEncrypt(text,secretkey,IV)
    Setciphertext=$SYSTEM.Encryption.Base64Encode(text)
   
    Writeciphertext
}

A operação AES CBC Encrypt foi usada para criptografar textos.
A codificação base64 retorna os resultados como um texto organizado/legível para o usuário.

4. Descriptografia simétrica

//Amostra de chaves simétricas para descriptografia
 
ClassMethodDoAESCBCDecrypt(ciphertextAs%String)As%Status
{
    // definir uma chave secreta
    Setsecretkey=$system.Util.GetEnviron("SECRETKEY")
    SetIV=$system.Util.GetEnviron("SECRETKEY")
   
    // descriptografar um texto
    Settext=$SYSTEM.Encryption.Base64Decode(ciphertext)
    Settext=$SYSTEM.Encryption.AESCBCDecrypt(text,secretkey,IV)
   
    Setplaintext=$ZCONVERT(text,"I","UTF8")
    Writeplaintext
}

A operação AES CBC Decrypt foi usada para descriptografar textos.
A decodificação base64 retorna o texto criptografado a um binário, para ser usado na descriptografia.

5. Criptografia assimétrica

//Amostra de chaves assimétricas para criptografia
 
ClassMethodDoRSAEncrypt(plaintextAs%String)As%Status
{
    // obter certificado público
    SetpubKeyFileName="/opt/irisbuild/example-com.cert.pem"
    SetobjCharFile=##class(%Stream.FileCharacter).%New()
    SetobjCharFile.Filename=pubKeyFileName
    SetpubKey=objCharFile.Read()
 
    // criptografar usando RSA
    Setbinarytext=$System.Encryption.RSAEncrypt(plaintext,pubKey)
    Setciphertext=$SYSTEM.Encryption.Base64Encode(binarytext)
   
    Returnciphertext
}

É preciso obter o conteúdo do arquivo da chave pública para criptografar com RSA.
A operação RSA Encrypt foi usada para criptografar textos.

6. Descriptografia assimétrica

//Amostra de chaves assimétricas para descriptografia
 
ClassMethodDoRSADecrypt(ciphertextAs%String)As%Status
{
    // obter chave pública
    SetprivKeyFileName="/opt/irisbuild/example-com.key.pem"
    SetprivobjCharFile=##class(%Stream.FileCharacter).%New()
    SetprivobjCharFile.Filename=privKeyFileName
    SetprivKey=privobjCharFile.Read()
 
    // obter ciphertext em formato binário
    Settext=$SYSTEM.Encryption.Base64Decode(ciphertext)
 
    // descriptografar texto usando RSA
    Setplaintext=$System.Encryption.RSADecrypt(text,privKey)
 
    Returnplaintext
}

É preciso obter o conteúdo do arquivo da chave privada para descriptografar com RSA.
A operação RSA Decrypt foi usada para descriptografar textos.

7. Texto com hash usando MD5 (abordagem antiga)

//Amostra de hash
 
ClassMethodDoHash(plaintextAs%String)As%Status
{
    // converter para utf-8
    Settext=$ZCONVERT(plaintext,"O","UTF8")
   
    // hash de um texto
    Sethashtext=$SYSTEM.Encryption.MD5Hash(text)
   
    Setbase64text=$SYSTEM.Encryption.Base64Encode(hashtext)
 
    // converter para texto hex para seguir as melhores práticas
    Sethextext=..GetHexText(base64text)
 
    // retornar usando minúsculas
    Write$ZCONVERT(hextext,"L")
}

A operação MD5 Hash fará a criptografia do texto e não será possível descriptografar.
O hash com MD5 não é recomendado para novos projetos por não ser considerado seguro. Por isso, foi substituído por SHA. O InterSystems IRIS oferece compatibilidade com SHA (veja nosso próximo exemplo).

8. Texto com hash usando SHA (abordagem recomendada)

Usaremos o método de hash SHA-3 para esta amostra. De acordo com a documentação do InterSystems, esse método gera um hash usando um dos Algoritmos de Hash Seguro dos EUA - 3. Consulte a Publicação 202 dos Federal Information Processing Standards para saber mais. 

//Hash usando SHA
 
ClassMethodDoSHAHash(plaintextAs%String)As%Status
{
    // converter para utf-8
    Settext=$ZCONVERT(plaintext,"O","UTF8")
   
    // hash de um texto
    Sethashtext=$SYSTEM.Encryption.SHA3Hash(256,text)
   
    Setbase64text=$SYSTEM.Encryption.Base64Encode(hashtext)
 
    // converter para texto hex para seguir as melhores práticas
    Sethextext=..GetHexText(base64text)
 
    // retornar usando minúsculas
    Write$ZCONVERT(hextext,"L")
}

No método SHA, é possível definir o comprimento do bit usado em uma operação de hash. Quanto maior o número de bits, mais difícil é decifrar o hash. No entanto, o processo de hashing também fica mais lento. Na amostra, usamos 256 bits. Você pode escolher estas opções de comprimento de bit:

  • 224 (SHA-224)
  • 256 (SHA-256)
  • 384 (SHA-384)
  • 512 (SHA-512)
0
0 273
Anúncio Angelo Bruno Braga · Dez. 6, 2021

Olá Comunidade,

Estamos felizes em convidá-los para o Encontro Online com os Ganhadores do Concurso de Segurança InterSystems!

Data & Horário: Sexta-feira, 10 de Dezembro de 2021 – 12:00 horário de Brasília

O que lhe espera neste encontro online? 

  • A biografia de nossos ganhadores.
  • Pequenas demonstrações de suas aplicações.
  • Uma discussão aberta sobre as tecnologias utilizadas, Perguntas e Respostas e planos para os próximos concursos.

0
0 65