Skip to content

Commit

Permalink
feat(auth): Connexion-compatible auth handling (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
uniqueg authored Nov 14, 2020
1 parent 3002953 commit 7b35ec3
Show file tree
Hide file tree
Showing 20 changed files with 1,106 additions and 1,633 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,4 @@ pip-selfcheck.json
.vscode
examples/petstore/petstore.modified.yaml
examples/petstore/data/
*.modified.yaml
97 changes: 56 additions & 41 deletions foca/api/register_openapi.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"""Register OpenAPI specs with a Connexion app instance.
"""
"""Tests for modifying/registering OpenAPI specs."""

import logging
from typing import List

from connexion import App
from connexion.exceptions import InvalidSpecification
import yaml

from foca.models.config import SpecConfig
Expand All @@ -32,64 +30,81 @@ def register_openapi(
Raises:
OSError: File cannot be read from or written to.
InvalidSpecification: Specification file is not valid OpenAPI 2.x or
3.x.
yaml.YAMLError: YAML cannot be (de)serialized.
"""
# Iterate over OpenAPI specs
for spec in specs:
spec_modified = False

spec_parsed = dict()

if len(spec.path) > 1:
spec_modified = True
# Merge specs
spec_parsed = ConfigParser.merge_yaml(*spec.path)
logger.debug(f"Parsed spec: {spec.path}")

# Add/replace root objects
if spec.append is not None:
for item in spec.append:
spec_parsed.update(item)
spec_modified = True
logger.debug(f"Appended spec: {spec.append}")

# Add/replace fields to Operation Objects
if spec.add_operation_fields is not None:
for key, val in spec.add_operation_fields.items():
try:
for path_item_object in spec_parsed['paths'].values():
for operation_object in path_item_object.values():
operation_object[key] = val
except KeyError:
raise InvalidSpecification("invalid Operation Object")
spec_modified = True
for path_item_object in spec_parsed.get('paths', {}).values():
for operation_object in path_item_object.values():
operation_object[key] = val
logger.debug(
f"Added operation fields: {spec.add_operation_fields}"
)

# Add fields to security definitions/schemes
if not spec.disable_auth and spec.add_security_fields is not None:
for key, val in spec.add_security_fields.items():
# OpenAPI 2
sec_defs = spec_parsed.get('securityDefinitions', {})
for sec_def in sec_defs.values():
sec_def[key] = val
# OpenAPI 3
sec_schemes = spec_parsed.get(
'components', {'securitySchemes': {}}
).get('securitySchemes', {}) # type: ignore
for sec_scheme in sec_schemes.values():
sec_scheme[key] = val
logger.debug(f"Added security fields: {spec.add_security_fields}")

# Remove security definitions/schemes and fields
elif spec.disable_auth:
# Open API 2
spec_parsed.pop('securityDefinitions', None)
# Open API 3
spec_parsed.get('components', {}).pop('securitySchemes', None)
# Open API 2/3
spec_parsed.pop('security', None)
for path_item_object in spec_parsed.get('paths', {}).values():
for operation_object in path_item_object.values():
operation_object.pop('security', None)
logger.debug("Removed security fields")

# Write modified specs
if spec_modified:
try:
with open(spec.path_out, 'w') as out_file: # type: ignore
try:
yaml.safe_dump(spec_parsed, out_file)
except yaml.YAMLError as e:
raise yaml.YAMLError(
"could not encode modified specification"
) from e
except OSError as e:
raise OSError(
"modified specification could not be written to file "
f"'{spec.path_out}'"
) from e
spec_use = spec.path_out
else:
spec_use = spec.path[0]
try:
with open(spec.path_out, 'w') as out_file: # type: ignore
yaml.safe_dump(spec_parsed, out_file)
except OSError as e:
raise OSError(
"Modified specification could not be written to file "
f"'{spec.path_out}'"
) from e
except yaml.YAMLError as e:
raise yaml.YAMLError(
"Could not encode modified specification"
) from e
logger.debug(f"Wrote specs to file: {spec.path_out}")

# Attach specs to connexion App
if spec.connexion is None:
spec.connexion = {}
logger.debug(f"Modified specs: {spec_parsed}")
spec.connexion = {} if spec.connexion is None else spec.connexion
app.add_api(
specification=spec_use,
**spec.dict()['connexion'],
specification=spec.path_out,
**spec.dict().get('connexion', {}),
)

logger.info(f"API endpoints specified in '{spec.path_out}' added.")
logger.info(f"API endpoints added from spec: {spec.path_out}")

return app
5 changes: 5 additions & 0 deletions foca/errors/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from connexion.exceptions import (
ExtraParameterProblem,
Forbidden,
OAuthProblem,
Unauthorized,
)
from flask import (current_app, Response)
Expand Down Expand Up @@ -45,6 +46,10 @@
"title": "Unauthorized",
"status": 401,
},
OAuthProblem: {
"title": "Unauthorized",
"status": 401,
},
Forbidden: {
"title": "Forbidden",
"status": 403,
Expand Down
Loading

0 comments on commit 7b35ec3

Please sign in to comment.