Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions .github/workflows/mypy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Check mypy

on:
- pull_request
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install deps
run: pip install -r requirements.txt -r requirements-dev.txt
- name: Run mypy
run: mypy
7 changes: 4 additions & 3 deletions docs/installation/docker.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,12 @@ TAXII 2 instance with Compose

Checkout the configuration at: :github-file:`examples/docker-compose-taxii2.yml <examples/docker-compose-taxii2.yml>`.

To add dummy data, you can execute:

.. code-block:: shell

# while the compose project is running
# Start
docker compose -f examples/docker-compose-taxii2.yml up

# To add dummy data, run this while the compose project is running
docker exec -i examples-opentaxii-1 bash < examples/taxii2/data-setup.sh

Full Example with Compose
Expand Down
5 changes: 5 additions & 0 deletions opentaxii/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# flake8: noqa
from .api import OpenTAXIIAuthAPI
from .manager import AuthManager

__all__ = (
"AuthManager",
"OpenTAXIIAuthAPI",
)
42 changes: 23 additions & 19 deletions opentaxii/auth/sqldb/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime, timedelta
from typing import Optional

import jwt
import structlog
Expand All @@ -10,40 +11,41 @@

from .models import Account, Base

__all__ = ['SQLDatabaseAPI']
__all__ = ["SQLDatabaseAPI"]


log = structlog.getLogger(__name__)


class SQLDatabaseAPI(BaseSQLDatabaseAPI, OpenTAXIIAuthAPI):
"""Naive SQL database implementation of OpenTAXII Auth API.

Implementation will work with any DB supported by SQLAlchemy package.

:param str db_connection: a string that indicates database dialect and
connection arguments that will be passed directly
to :func:`~sqlalchemy.engine.create_engine` method.
:param bool create_tables=False: if True, tables will be created in the DB.
:param str secret: secret string used for token generation
:param int token_ttl_secs: TTL for JWT token, in seconds.
:param engine_parameters=None: if defined, these arguments would be passed to sqlalchemy.create_engine
"""

BASEMODEL = Base

def __init__(
self,
db_connection,
create_tables=False,
secret=None,
token_ttl_secs=None,
db_connection: str,
create_tables: bool = False,
secret: Optional[str] = None,
token_ttl_secs: Optional[int] = None,
**engine_parameters,
):
"""Naive SQL database implementation of OpenTAXII Auth API.

Implementation will work with any DB supported by SQLAlchemy package.

:param db_connection: a string that indicates database dialect and
connection arguments that will be passed directly to
:func:`~sqlalchemy.engine.create_engine` method.
:param create_tables=False: if True, tables will be created in the DB.
:param secret: secret string used for token generation
:param token_ttl_secs: TTL for JWT token, in seconds.
:param engine_parameters=None: if defined, these arguments would be passed
to sqlalchemy.create_engine
"""
super().__init__(db_connection, create_tables, **engine_parameters)
if not secret:
raise ValueError(
'Secret is not defined for %s.%s'
"Secret is not defined for %s.%s"
% (self.__module__, self.__class__.__name__)
)
self.secret = secret
Expand All @@ -59,7 +61,9 @@ def authenticate(self, username, password):
return self._generate_token(account.id, ttl=self.token_ttl_secs)

def create_account(self, username, password, is_admin=False):
account = Account(username=username, is_admin=is_admin, permissions={})
account = Account( # type: ignore[misc]
username=username, is_admin=is_admin, permissions={}
)
account.set_password(password)
self.db.session.add(account)
self.db.session.commit()
Expand Down
1 change: 1 addition & 0 deletions opentaxii/auth/sqldb/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def set_password(self, password):
self.password_hash = generate_password_hash(password)

def is_password_valid(self, password):
assert self.password_hash is not None
return check_password_hash(self.password_hash, password)

@property
Expand Down
2 changes: 1 addition & 1 deletion opentaxii/common/sqldb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from opentaxii.sqldb_helper import SQLAlchemyDB

try:
from sqlalchemy.orm import DeclarativeMeta
from sqlalchemy.orm import DeclarativeMeta # type: ignore[attr-defined]
except ImportError:
from sqlalchemy.ext.declarative import DeclarativeMeta

Expand Down
2 changes: 1 addition & 1 deletion opentaxii/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def _get_env_config(env=os.environ, optional_env_var=None):

@classmethod
def _load_configs(cls, *configs):
result = dict()
result: dict = dict()
for config in configs:
# read content from path-like object
if not isinstance(config, dict):
Expand Down
2 changes: 1 addition & 1 deletion opentaxii/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def create_app(server):
"""

app = Flask(__name__)
app.taxii_server = server
app.taxii_server = server # type: ignore[attr-defined]

server.init_app(app)

Expand Down
11 changes: 7 additions & 4 deletions opentaxii/persistence/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# flake8: noqa
from .api import OpenTAXII2PersistenceAPI, OpenTAXIIPersistenceAPI
from .manager import (
BasePersistenceManager,
Taxii1PersistenceManager,
Taxii2PersistenceManager,
from .manager import Taxii1PersistenceManager, Taxii2PersistenceManager

__all__ = (
"OpenTAXII2PersistenceAPI",
"OpenTAXIIPersistenceAPI",
"Taxii1PersistenceManager",
"Taxii2PersistenceManager",
)
42 changes: 30 additions & 12 deletions opentaxii/persistence/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
import uuid
from typing import Dict, List, Optional, Tuple

from opentaxii.taxii2.entities import (
Expand Down Expand Up @@ -34,6 +35,18 @@ def create_service(self, service_entity):
"""
raise NotImplementedError()

def update_service(self, obj):
"""Update service. To implement in subclass"""
raise NotImplementedError()

def delete_service(self, service_id):
"""Delete service. To implement in subclass"""
raise NotImplementedError()

def set_collection_services(self, collection_id, service_ids):
"""Set collection's services. To implement in subclass"""
raise NotImplementedError()

def create_collection(self, collection_entity):
"""Create a collection.

Expand Down Expand Up @@ -273,8 +286,11 @@ class OpenTAXII2PersistenceAPI:
Stub, pending implementation.
"""

def init_app(self, app):
pass

@staticmethod
def get_next_param(self, kwargs: Dict) -> str:
def get_next_param(kwargs: Dict) -> str:
"""
Get value for `next` based on :class:`Dict` instance.

Expand All @@ -298,23 +314,25 @@ def get_api_roots(self) -> List[ApiRoot]:
"""
raise NotImplementedError

def get_api_root(self, api_root_id: str) -> Optional[ApiRoot]:
def get_api_root(self, api_root_id: uuid.UUID) -> Optional[ApiRoot]:
raise NotImplementedError

def get_job_and_details(self, api_root_id: str, job_id: str) -> Optional[Job]:
def get_job_and_details(
self, api_root_id: uuid.UUID, job_id: uuid.UUID
) -> Optional[Job]:
raise NotImplementedError

def get_collections(self, api_root_id: str) -> List[Collection]:
def get_collections(self, api_root_id: uuid.UUID) -> List[Collection]:
raise NotImplementedError

def get_collection(
self, api_root_id: str, collection_id_or_alias: str
self, api_root_id: uuid.UUID, collection_id_or_alias: str
) -> Optional[Collection]:
raise NotImplementedError

def get_manifest(
self,
collection_id: str,
collection_id: uuid.UUID,
limit: Optional[int] = None,
added_after: Optional[datetime.datetime] = None,
next_kwargs: Optional[Dict] = None,
Expand All @@ -327,7 +345,7 @@ def get_manifest(

def get_objects(
self,
collection_id: str,
collection_id: uuid.UUID,
limit: Optional[int] = None,
added_after: Optional[datetime.datetime] = None,
next_kwargs: Optional[Dict] = None,
Expand All @@ -339,13 +357,13 @@ def get_objects(
raise NotImplementedError

def add_objects(
self, api_root_id: str, collection_id: str, objects: List[Dict]
self, api_root_id: uuid.UUID, collection_id: uuid.UUID, objects: List[Dict]
) -> Job:
raise NotImplementedError

def get_object(
self,
collection_id: str,
collection_id: uuid.UUID,
object_id: str,
limit: Optional[int] = None,
added_after: Optional[datetime.datetime] = None,
Expand All @@ -362,7 +380,7 @@ def get_object(

def delete_object(
self,
collection_id: str,
collection_id: uuid.UUID,
object_id: str,
match_version: Optional[List[str]] = None,
match_spec_version: Optional[List[str]] = None,
Expand All @@ -371,13 +389,13 @@ def delete_object(

def get_versions(
self,
collection_id: str,
collection_id: uuid.UUID,
object_id: str,
limit: Optional[int] = None,
added_after: Optional[datetime.datetime] = None,
next_kwargs: Optional[Dict] = None,
match_spec_version: Optional[List[str]] = None,
) -> Tuple[List[VersionRecord], bool]:
) -> Tuple[Optional[List[VersionRecord]], bool]:
"""
Get all versions of single object from database.

Expand Down
Loading