Skip to content
This repository has been archived by the owner on Aug 4, 2021. It is now read-only.

Commit

Permalink
Merge pull request #16 from CSCfi/devel
Browse files Browse the repository at this point in the history
Merge signature enhancements
  • Loading branch information
blankdots authored May 3, 2020
2 parents ba81a46 + 8b5a08e commit d415cca
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 26 deletions.
2 changes: 1 addition & 1 deletion bindings/js/swift_sharing_request_bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ class SwiftSharingRequest {
},
);
return deleted;
}
}
}

export default SwiftSharingRequest;
2 changes: 1 addition & 1 deletion swift_sharing_request/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@


__name__ = "swift_sharing_request"
__version__ = "0.4.1"
__version__ = "0.4.2"
__author__ = "CSC Developers"
__license__ = "MIT License"
86 changes: 66 additions & 20 deletions swift_sharing_request/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ def handle_dropped_connection(request):

async def handle_share_request_post(request):
"""Handle query for posting a new share request."""
# Future authorization check here

# Check for incorrect client query here

container = request.match_info["container"]
user = request.match_info["user"]
owner = request.query["owner"]
Expand All @@ -55,10 +51,6 @@ async def handle_share_request_post(request):

async def handle_user_owned_request_listing(request):
"""Handle query for listing the requests owned by the user."""
# Future authorization check here

# Check for incorrect client query here

user = request.match_info["user"]

try:
Expand All @@ -71,10 +63,6 @@ async def handle_user_owned_request_listing(request):

async def handle_user_made_request_listing(request):
"""Handle query listing for the requests created by the user."""
# Future authorization check here

# Check for incorrect client query here

user = request.match_info["user"]

try:
Expand All @@ -87,10 +75,6 @@ async def handle_user_made_request_listing(request):

async def handle_container_request_listing(request):
"""Handle query for listing the container share requests."""
# Future authorization check here

# Check for incorrect client query here

container = request.match_info["container"]

try:
Expand All @@ -103,10 +87,6 @@ async def handle_container_request_listing(request):

async def handle_user_share_request_delete(request):
"""Delete container share request or requests."""
# Future authorizaion check here

# Check for incorrect client query here

container = request.match_info["container"]
user = request.match_info["user"]
owner = request.query["owner"]
Expand All @@ -120,3 +100,69 @@ async def handle_user_share_request_delete(request):
status=200,
body="OK"
)


async def handle_user_add_token(
request: aiohttp.web.Request
) -> aiohttp.web.Response:
"""Add a token to the user."""
project = request.match_info["project"]
identifier = request.match_info["id"]

try:
token = request.query["token"]
except KeyError:
try:
formdata = await request.post()
token = formdata["token"]
except KeyError:
raise aiohttp.web.HTTPBadRequest(
reason="No token present"
)

try:
await request.app["db_conn"].add_token(
project,
token,
identifier
)
except InterfaceError:
handle_dropped_connection(request)

return aiohttp.web.Response(status=200)


async def handle_user_delete_token(
request: aiohttp.web.Request
) -> aiohttp.web.Response:
"""Delete a token from the user."""
project = request.match_info["project"]
identifier = request.match_info["id"]

try:
await request.app["db_conn"].revoke_token(
project,
identifier
)
except InterfaceError:
handle_dropped_connection(request)

return aiohttp.web.Response(status=200)


async def handle_user_list_tokens(
request: aiohttp.web.Request
) -> aiohttp.web.Response:
"""Get project token listing."""
project = request.match_info["project"]

try:
tokens = await request.app["db_conn"].get_tokens(project)
except InterfaceError:
handle_dropped_connection(request)

# Return only the identifiers
return aiohttp.web.json_response([
rec["identifier"]
for rec in tokens
])
51 changes: 47 additions & 4 deletions swift_sharing_request/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,23 @@
import os
import typing
import hmac
import time
import logging

import aiohttp.web


AiohttpHandler = typing.Callable[[aiohttp.web.Request], aiohttp.web.Response]
AiohttpHandler = typing.Callable[
[aiohttp.web.Request],
typing.Coroutine[
typing.Awaitable,
typing.Any,
aiohttp.web.Response
]
]


LOGGER = logging.getLogger("swift_sharing_request.auth")


async def read_in_keys(
Expand All @@ -34,8 +46,14 @@ async def test_signature(
tokens: typing.List[bytes],
signature: str,
message: str,
) -> bool:
validity: str,
):
"""Validate signature against the given tokens."""
# Check signature expiration
if int(validity) < time.time():
raise aiohttp.web.HTTPUnauthorized(
reason="Signature expired"
)
byte_message = message.encode("utf-8")
for token in tokens:
digest = hmac.new(
Expand Down Expand Up @@ -65,10 +83,35 @@ async def handle_validate_authentication(
reason="Query string missing validity or signature."
)

project_tokens = []
project = None
try:
project = request.match_info["project"]
except KeyError:
try:
project = request.match_info["user"]
except KeyError:
try:
project = request.query["project"]
except KeyError:
pass
finally:
if project:
project_tokens = [
rec["token"].encode("utf-8")
for rec in await request.app["db_conn"].get_tokens(project)
]
else:
LOGGER.debug(f"No project ID found in request {request}")
raise aiohttp.web.HTTPUnauthorized(
reason="No project ID in request"
)

await test_signature(
request.app["tokens"],
request.app["tokens"] + project_tokens,
signature,
validity + path
validity + path,
validity
)

return await handler(request)
6 changes: 6 additions & 0 deletions swift_sharing_request/bindings/bind.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Async Python bindings for the swift-x-account-sharing backend."""


import os
import json
import typing

Expand Down Expand Up @@ -93,13 +94,18 @@ async def list_container_requests(
path = f"/request/container/{container}"
url = self.url + path

project = os.environ.get("OS_PROJECT_ID", None)

signature = sign_api_request(path)

params = {
"valid": signature["valid"],
"signature": signature["signature"],
}

if project:
params["project"] = project

async with self.session.get(url, params=params) as resp:
return json.loads(await resp.text())

Expand Down
60 changes: 60 additions & 0 deletions swift_sharing_request/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import random
import asyncio
import os
import typing

import asyncpg

Expand Down Expand Up @@ -155,3 +156,62 @@ async def delete_request(self, container, owner, recipient):
recipient
)
return True

async def get_tokens(
self,
token_owner: str
) -> typing.List[dict]:
"""Get tokens created for a project."""
query = await self.conn.fetch(
"""
SELECT *
FROM Tokens
WHERE token_owner = $1
;
""",
token_owner
)
return list(query)

async def revoke_token(
self,
token_owner: str,
token_identifier: str
):
"""Remove a token from the database."""
async with self.conn.transaction():
await self.conn.execute(
"""
DELETE FROM Tokens
WHERE
token_owner = $1 AND
identifier = $2
;
""",
token_owner,
token_identifier
)

async def add_token(
self,
token_owner: str,
token: str,
identifier: str
):
"""Add a token to the database."""
async with self.conn.transaction():
await self.conn.execute(
"""
INSERT INTO Tokens(
token_owner,
token,
identifier
) VALUES (
$1, $2, $3
)
;
""",
token_owner,
token,
identifier
)
11 changes: 11 additions & 0 deletions swift_sharing_request/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
handle_user_made_request_listing,
handle_container_request_listing,
handle_user_share_request_delete,
handle_user_add_token,
handle_user_delete_token,
handle_user_list_tokens
)
from .db import DBConn
from .preflight import handle_delete_preflight
Expand Down Expand Up @@ -76,6 +79,14 @@ async def init_server() -> aiohttp.web.Application:
handle_container_request_listing),
])

app.add_routes([
aiohttp.web.options("/token/{project}/{id}",
handle_delete_preflight),
aiohttp.web.post("/token/{project}/{id}", handle_user_add_token),
aiohttp.web.delete("/token/{project}/{id}", handle_user_delete_token),
aiohttp.web.get("/token/{project}", handle_user_list_tokens),
])

app.on_startup.append(resume_on_start)
app.on_startup.append(read_in_keys)
app.on_shutdown.append(graceful_shutdown)
Expand Down

0 comments on commit d415cca

Please sign in to comment.