Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: digital business card #2290

Merged
merged 37 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
58e63e2
feat: devcontainer configuraton for vscode
amanji Sep 7, 2023
107604e
feat: hard code digital business card schema
amanji Sep 7, 2023
3793db1
feat: hard code digital business card schema
amanji Sep 7, 2023
d0c51ce
feat: issue credentials through Traction tenant
amanji Sep 11, 2023
7b88982
refactor: app initialization workflow
amanji Sep 12, 2023
ad0fa01
feat: use out-of-band invitation for connecting
amanji Sep 20, 2023
2e75d5f
feat: use v2.0 for issuing credential
amanji Sep 21, 2023
79a2631
feat: web socket implmentation with flask-socketio
amanji Oct 11, 2023
fc8edc4
feat: db migration script to enable revocation
amanji Oct 16, 2023
4fe8406
feat: revocation endpoint
amanji Oct 18, 2023
aec27f2
feat: replace endpoints
amanji Oct 19, 2023
99e54fe
chore: fix linting errors
amanji Oct 19, 2023
88e3ccc
chore: update requirements
amanji Oct 19, 2023
fd96a6e
chore: update tests
amanji Oct 25, 2023
e397e04
feat: traction token exchanger
amanji Oct 26, 2023
1f2fe2b
chore: update workflow variables
amanji Oct 26, 2023
4090b35
chore: update workflow variables
amanji Oct 26, 2023
7f1f546
refactor: ws cors setting is a config option
amanji Oct 26, 2023
a259818
chore: fix linting errors
amanji Oct 26, 2023
080825f
refactor: clean up init in digital credential service
amanji Oct 27, 2023
6beb1b1
18284: digital credentials (#2260)
amanji Oct 27, 2023
87d0854
feat: endpoints to reset credential offers
amanji Oct 28, 2023
4042676
Merge remote-tracking branch 'upstream/feature-digital-credentials' i…
amanji Oct 30, 2023
26a01e7
feat: credential id lookup table
amanji Oct 30, 2023
32a7d7a
feat: add business roles
amanji Oct 30, 2023
205dd68
18284 Add pre-fork server hook to gunicorn config (#2285)
argush3 Oct 30, 2023
c55cebc
chore: fix tests and linting
amanji Oct 30, 2023
8ad8cb9
chore: fix tests
amanji Oct 31, 2023
65a9a4d
18284 feat: digital credentials (#2281)
amanji Oct 31, 2023
2fc5cb3
Merge remote-tracking branch 'upstream/feature-digital-credentials' i…
amanji Oct 31, 2023
d368673
refactor: remove records from Traction on deletion
amanji Nov 1, 2023
0b3b60b
Revert "feat: web socket implmentation with flask-socketio"
amanji Nov 1, 2023
9763a17
fix: port so it doesnt overlap with airplay server on OSX
amanji Nov 1, 2023
633e3d9
Revert "fix: port so it doesnt overlap with airplay server on OSX"
amanji Nov 1, 2023
2976d11
feat: digital credentials (#2287)
amanji Nov 1, 2023
b22e419
Merge remote-tracking branch 'upstream/feature-digital-credentials' i…
amanji Nov 2, 2023
fc6c34a
Merge branch 'feature-digital-credentials'
amanji Nov 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM mcr.microsoft.com/devcontainers/python:1-3.8-bookworm

ENV PYTHONUNBUFFERED 1

# [Optional] If your requirements rarely change, uncomment this section to add them to the image.
# COPY requirements.txt /tmp/pip-tmp/
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
# && rm -rf /tmp/pip-tmp

# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>



31 changes: 31 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/postgres
{
"name": "Python 3 & PostgreSQL",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
"ghcr.io/itsmechlark/features/postgresql:1": {}
},

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// This can be used to network with other containers or the host.
// "forwardPorts": [5000, 5432],

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "pip install --user -r requirements.txt",

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root",

// Enable this on OSX to add ssh key to agent inside container
"initializeCommand": "find ~/.ssh/ -type f -exec grep -l 'PRIVATE' {} \\; | xargs ssh-add"
}
35 changes: 35 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
version: '3.8'

services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile

volumes:
- ../..:/workspaces:cached

# Overrides default command so things don't shut down after the process ends.
command: sleep infinity

# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db

# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)

db:
image: postgres:latest
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_DB: postgres
POSTGRES_PASSWORD: postgres

# Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)

volumes:
postgres-data:
4 changes: 4 additions & 0 deletions .github/workflows/legal-api-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ jobs:
NATS_QUEUE: entity-filer-worker
JWT_OIDC_JWKS_CACHE_TIMEOUT: 300
GO_LIVE_DATE: 2019-08-12
BUSINESS_SCHEMA_ID: test_business_schema_id
BUSINESS_CRED_DEF_ID: test_credential_definition_id
BUSINESS_SCHEMA_NAME: digital_business_card
BUSINESS_SCHEMA_VERSION: "1.0.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have the BUSINESS_SCHEMA_ID and BUSINESS_CRED_DEF_ID values for Test and Prod yet? If so, please email them to me and I can put them in 1Password. If not, how do we get them?

Copy link
Collaborator

@severinbeauvais severinbeauvais Nov 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, there are 4 5 TRACTION_xxx keys that are not yet configured in Test and Prod.


runs-on: ubuntu-20.04

Expand Down
4 changes: 4 additions & 0 deletions legal-api/gunicorn_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
"""

import os
import gunicorn_server

workers = int(os.environ.get('GUNICORN_PROCESSES', '1')) # pylint: disable=invalid-name
threads = int(os.environ.get('GUNICORN_THREADS', '1')) # pylint: disable=invalid-name

forwarded_allow_ips = '*' # pylint: disable=invalid-name
secure_scheme_headers = {'X-Forwarded-Proto': 'https'} # pylint: disable=invalid-name

# Server Hooks
pre_fork = gunicorn_server.pre_fork
10 changes: 10 additions & 0 deletions legal-api/gunicorn_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

import time


def pre_fork(server, worker):
# Delay loading of each worker by 5 seconds
# This is done to work around an issue where the Traction API is returning an invalid token. The issue happens
# when successive token retrieval calls are made with less than 2-3 seconds between the calls.
time.sleep(5)

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""add revocation to dc_credentials

Revision ID: 6b65b40a5164
Revises: 9a9ac165365e
Create Date: 2023-10-11 22:20:14.023687

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '6b65b40a5164'
down_revision = '9a9ac165365e'
branch_labels = None
depends_on = None


def upgrade():
op.add_column('dc_issued_credentials', sa.Column('credential_revocation_id', sa.String(length=10), nullable=True))
op.add_column('dc_issued_credentials', sa.Column('revocation_registry_id', sa.String(length=200), nullable=True))


def downgrade():
op.drop_column('dc_issued_credentials', 'credential_revocation_id')
op.drop_column('dc_issued_credentials', 'revocation_registry_id')
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""create issued business user credentials table

Revision ID: 6e28f267db2a
Revises: 8148a25d695e
Create Date: 2023-10-17 02:17:08.232290

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '6e28f267db2a'
down_revision = '8148a25d695e'
branch_labels = None
depends_on = None


def upgrade():
op.create_table('dc_issued_business_user_credentials',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('business_id', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.ForeignKeyConstraint(['business_id'], ['businesses.id']),
sa.ForeignKeyConstraint(['user_id'], ['users.id']))


def downgrade():
op.drop_table('dc_issued_business_user_credentials')
28 changes: 28 additions & 0 deletions legal-api/migrations/versions/8148a25d695e_change_field_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""change field type

Revision ID: 8148a25d695e
Revises: 6b65b40a5164
Create Date: 2023-10-17 01:05:30.977475

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '8148a25d695e'
down_revision = '6b65b40a5164'
branch_labels = None
depends_on = None


def upgrade():
op.alter_column('dc_issued_credentials', 'credential_id',
existing_type=sa.String(length=100),
type_=sa.String(length=10))


def downgrade():
op.alter_column('dc_issued_credentials', 'credential_id',
existing_type=sa.String(length=10),
type_=sa.String(length=100))
1 change: 1 addition & 0 deletions legal-api/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pyRFC3339==1.1
pyasn1==0.4.8
pycountry==20.7.3
pydantic==1.10.2
pyjwt==2.8.0
pyrsistent==0.17.3
python-dateutil==2.8.1
python-dotenv==0.17.1
Expand Down
52 changes: 0 additions & 52 deletions legal-api/requirements.txt.1

This file was deleted.

18 changes: 15 additions & 3 deletions legal-api/src/legal_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,21 @@ class _Config(): # pylint: disable=too-few-public-methods

NAICS_API_URL = os.getenv('NAICS_API_URL', 'https://NAICS_API_URL/api/v2/naics')

ACA_PY_ADMIN_API_URL = os.getenv('ACA_PY_ADMIN_API_URL')
ACA_PY_ADMIN_API_KEY = os.getenv('ACA_PY_ADMIN_API_KEY')
ACA_PY_ENTITY_DID = os.getenv('ACA_PY_ENTITY_DID')
# Traction ACA-Py tenant settings to issue credentials from
TRACTION_API_URL = os.getenv('TRACTION_API_URL')
TRACTION_TENANT_ID = os.getenv('TRACTION_TENANT_ID')
TRACTION_API_KEY = os.getenv('TRACTION_API_KEY')
TRACTION_PUBLIC_SCHEMA_DID = os.getenv('TRACTION_PUBLIC_SCHEMA_DID')
TRACTION_PUBLIC_ISSUER_DID = os.getenv('TRACTION_PUBLIC_ISSUER_DID')

# Web socket settings
WS_ALLOWED_ORIGINS = os.getenv('WS_ALLOWED_ORIGINS')

# Digital Business Card configuration values (required to issue credentials)
BUSINESS_SCHEMA_NAME = os.getenv('BUSINESS_SCHEMA_NAME')
BUSINESS_SCHEMA_VERSION = os.getenv('BUSINESS_SCHEMA_VERSION')
BUSINESS_SCHEMA_ID = os.getenv('BUSINESS_SCHEMA_ID')
BUSINESS_CRED_DEF_ID = os.getenv('BUSINESS_CRED_DEF_ID')

TESTING = False
DEBUG = False
Expand Down
56 changes: 56 additions & 0 deletions legal-api/src/legal_api/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright © 2023 Province of British Columbia
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This module holds function decorators."""

import json
from functools import wraps

import jwt
import requests
from flask import current_app
from jwt import ExpiredSignatureError


def requires_traction_auth(f):
"""Check for a valid Traction token and refresh if needed."""
@wraps(f)
def decorated_function(*args, **kwargs):
traction_api_url = current_app.config['TRACTION_API_URL']
traction_tenant_id = current_app.config['TRACTION_TENANT_ID']
traction_api_key = current_app.config['TRACTION_API_KEY']

if traction_api_url is None:
raise EnvironmentError('TRACTION_API_URL environment vairable is not set')

if traction_tenant_id is None:
raise EnvironmentError('TRACTION_TENANT_ID environment vairable is not set')

if traction_api_key is None:
raise EnvironmentError('TRACTION_API_KEY environment vairable is not set')

try:
if not hasattr(current_app, 'api_token'):
raise jwt.ExpiredSignatureError

jwt.decode(current_app.api_token, options={'verify_signature': False})
except ExpiredSignatureError:
current_app.logger.info('JWT token expired or is missing, requesting new token')
response = requests.post(f'{traction_api_url}/multitenancy/tenant/{traction_tenant_id}/token',
headers={'Content-Type': 'application/json'},
data=json.dumps({'api_key': traction_api_key}))
response.raise_for_status()
current_app.api_token = response.json()['token']

return f(*args, **kwargs)
return decorated_function
8 changes: 5 additions & 3 deletions legal-api/src/legal_api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .corp_type import CorpType
from .dc_connection import DCConnection
from .dc_definition import DCDefinition
from .dc_issued_business_user_credential import DCIssuedBusinessUserCredential
from .dc_issued_credential import DCIssuedCredential
from .document import Document, DocumentType
from .filing import Filing
Expand All @@ -40,6 +41,7 @@

__all__ = ('db',
'Address', 'Alias', 'Business', 'ColinLastUpdate', 'Comment', 'ConsentContinuationOut', 'CorpType',
'DCConnection', 'DCDefinition', 'DCIssuedCredential', 'Document', 'DocumentType',
'Filing', 'Office', 'OfficeType', 'Party', 'RegistrationBootstrap', 'RequestTracker', 'Resolution',
'PartyRole', 'ShareClass', 'ShareSeries', 'User', 'UserRoles', 'NaicsStructure', 'NaicsElement')
'DCConnection', 'DCDefinition', 'DCIssuedCredential', 'DCIssuedBusinessUserCredential', 'Document',
'DocumentType', 'Filing', 'Office', 'OfficeType', 'Party', 'RegistrationBootstrap', 'RequestTracker',
'Resolution', 'PartyRole', 'ShareClass', 'ShareSeries', 'User', 'UserRoles', 'NaicsStructure',
'NaicsElement')
5 changes: 5 additions & 0 deletions legal-api/src/legal_api/models/dc_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ def save(self):
db.session.add(self)
db.session.commit()

def delete(self):
"""Delete the object from the database immediately."""
db.session.delete(self)
db.session.commit()

@classmethod
def find_by_id(cls, dc_connection_id: str) -> DCConnection:
"""Return the digital credential connection matching the id."""
Expand Down
Loading
Loading