Skip to content

Commit

Permalink
Merge pull request #268 from CSCfi/devel
Browse files Browse the repository at this point in the history
Merge v1.1.0b3
  • Loading branch information
sampsapenna authored Jul 7, 2021
2 parents f975a97 + 7560e79 commit 325409c
Show file tree
Hide file tree
Showing 41 changed files with 1,125 additions and 588 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/chrome.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2.1.5
uses: actions/setup-node@v2.2.0
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2.1.5
uses: actions/setup-node@v2.2.0
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/eslint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2.1.5
uses: actions/setup-node@v2.2.0
with:
node-version: ${{ matrix.node }}
- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/firefox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2.1.5
uses: actions/setup-node@v2.2.0
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2.1.5
uses: actions/setup-node@v2.2.0
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:14.17.0-alpine3.12 as FRONTEND
FROM node:14.17.1-alpine3.12 as FRONTEND

RUN apk add --update \
&& apk add --no-cache build-base curl-dev linux-headers bash git\
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile-Devel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:14.17.0-alpine3.12 as FRONTEND
FROM node:14.17.1-alpine3.12 as FRONTEND

RUN apk add --update \
&& apk add --no-cache build-base curl-dev linux-headers bash git\
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile-SD
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:14.17.0-alpine3.12 as FRONTEND
FROM node:14.17.1-alpine3.12 as FRONTEND

RUN apk add --update \
&& apk add --no-cache build-base curl-dev linux-headers bash git\
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
author = "CSC Developers"

# The full version, including alpha/beta/rc tags
version = release = "1.1.0b2"
version = release = "1.1.0b3"


# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ python-swiftclient==3.12.0
cryptography==3.4.7
keystoneauth1==4.3.1
click==8.0.1
sphinx==4.0.2
sphinx==4.0.3
sphinx_rtd_theme==0.5.2
uvloop==0.15.2
gunicorn>=20.0.1
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
"coveralls==3.1.0",
"flake8==3.9.2",
"flake8-docstrings==1.6.0",
"pytest-xdist==2.2.1",
"pytest-xdist==2.3.0",
"asynctest==0.13.0",
"black==21.6b0",
],
"docs": ["sphinx==4.0.2", "sphinx_rtd_theme==0.5.2", "selenium==3.141.0"],
"docs": ["sphinx==4.0.3", "sphinx_rtd_theme==0.5.2", "selenium==3.141.0"],
"ui_test": ["pytest==6.2.4", "selenium==3.141.0 ", "pytest-timeout==1.4.2"],
},
packages=[__name__],
Expand Down
2 changes: 1 addition & 1 deletion swift_browser_ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@


__name__ = "swift_browser_ui"
__version__ = "1.1.0b2"
__version__ = "1.1.0b3"
__author__ = "CSC Developers"
__license__ = "MIT License"
21 changes: 21 additions & 0 deletions swift_browser_ui/_convenience.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,27 @@ def initiate_os_session(unscoped: str, project: str) -> keystoneauth1.session.Se
)


def os_get_token_from_credentials(
username: str,
password: str,
) -> str:
"""Get an unscoped token with provided credentials."""
os_auth = v3.Password(
setd["auth_endpoint_url"],
username=username,
password=password,
user_domain_name=setd["os_user_domain"],
unscoped=True,
)

os_session = keystoneauth1.session.Session(
auth=os_auth,
verify=True,
)

return os_session.get_token()


def initiate_os_service(
os_session: keystoneauth1.session.Session, url: str = None
) -> swiftclient.service.SwiftService:
Expand Down
22 changes: 22 additions & 0 deletions swift_browser_ui/front.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,25 @@ async def index(
raise AttributeError
except (AttributeError, InvalidToken, KeyError, aiohttp.web.HTTPUnauthorized):
return aiohttp.web.FileResponse(str(setd["static_directory"]) + "/index.html")


async def loginpassword(
request: typing.Optional[aiohttp.web.Request],
) -> typing.Union[aiohttp.web.Response, aiohttp.web.FileResponse]:
"""Serve the username and password login page."""
try:
if request is not None:
session_check(request)
request.app["Log"].info("Redirecting an existing session to app")
return aiohttp.web.Response(
status=303,
headers={
"Location": "/browse",
},
)
else:
raise AttributeError
except (AttributeError, InvalidToken, KeyError, aiohttp.web.HTTPUnauthorized):
return aiohttp.web.FileResponse(
str(setd["static_directory"]) + "/loginpassword.html"
)
179 changes: 111 additions & 68 deletions swift_browser_ui/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

# aiohttp
import aiohttp.web
import keystoneauth1.exceptions.http
from multidict import MultiDictProxy

import typing
Expand All @@ -21,6 +22,7 @@
session_check,
initiate_os_session,
initiate_os_service,
os_get_token_from_credentials,
test_swift_endpoint,
clear_session_info,
)
Expand Down Expand Up @@ -90,27 +92,129 @@ def test_token(
f"from address {request.remote} :: {time.ctime()}"
)
if unscoped is None:
raise aiohttp.web.HTTPClientError(reason="Token missing from query")
raise aiohttp.web.HTTPBadRequest(reason="Token missing from query")
if not (re.match("[a-f0-9]{32}", unscoped) and len(unscoped) == 32):
raise aiohttp.web.HTTPClientError(reason="Token is malformed")
raise aiohttp.web.HTTPBadRequest(reason="Token is malformed")

log.info("Got OS token in login return")

return unscoped


async def credentials_login_end(
request: aiohttp.web.Request,
) -> typing.Union[aiohttp.web.Response, aiohttp.web.FileResponse]:
"""Handle the login procedure with classic POST."""
log = request.app["Log"]
log.info("Got login request with username, password")

form = await request.post()

try:
username = str(form["username"])
password = str(form["password"])
except KeyError:
raise aiohttp.web.HTTPBadRequest(reason="Username or password not provided")

log.debug(f"username: {username}, password: {password}")

# Get an unscoped token with credentials
try:
unscoped: str = os_get_token_from_credentials(
username,
password,
)
except keystoneauth1.exceptions.http.BadRequest:
raise aiohttp.web.HTTPBadRequest(reason="No username or password provided.")
except keystoneauth1.exceptions.http.Unauthorized:
raise aiohttp.web.HTTPUnauthorized(
reason="Wrong username or password, or no access to the service."
)

log.debug(f"got token {unscoped}")

return await login_with_token(request, unscoped)


async def sso_query_end(
request: aiohttp.web.Request,
) -> typing.Union[aiohttp.web.Response, aiohttp.web.FileResponse]:
"""Handle the login procedure return from SSO or user from POST."""
log = request.app["Log"]
response: typing.Union[aiohttp.web.Response, aiohttp.web.FileResponse]
formdata = await request.post()
log.debug(f"Got {formdata} in form.")
# Declare the unscoped token
unscoped = test_token(formdata, request)

# Establish connection and begin session
return await login_with_token(request, unscoped)


async def token_rescope(request: aiohttp.web.Request) -> aiohttp.web.Response:
"""Rescope the requesting session's token to the new project."""
session_check(request)
session = decrypt_cookie(request)["id"]
request.app["Log"].info(
f"Call to rescope token from {request.remote}, sess: {session} :: {time.ctime()}"
)

if request.query["project"] not in [
p["id"] for p in request.app["Sessions"][session]["Avail"]["projects"]
]:
raise aiohttp.web.HTTPForbidden(
reason="The project is not available for this token."
)

# Invalidate the old scoped token
request.app["Sessions"][session]["OS_sess"].invalidate(
request.app["Sessions"][session]["OS_sess"].auth
)
# Overwrite the old session with a new one, with the updated project id
request.app["Sessions"][session]["OS_sess"] = initiate_os_session(
request.app["Sessions"][session]["Token"],
request.query["project"],
)
# Overwrite the old connection with a new one, with the updated keystone
# session
request.app["Sessions"][session]["ST_conn"] = initiate_os_service(
request.app["Sessions"][session]["OS_sess"],
)

# Ditch the session download proxy if that exists
if "runner" in request.app["Sessions"][session].keys():
request.app["Sessions"][session].pop("runner")

# Save the new project as the active project in session
new_project_name = [
i["name"]
for i in request.app["Sessions"][session]["Avail"]["projects"]
if i["id"] == request.query["project"]
][0]
request.app["Sessions"][session]["active_project"] = {
"name": new_project_name,
"id": request.query["project"],
}

response = aiohttp.web.Response(status=303, reason="Successfully rescoped token.")
response.headers["Location"] = "/browse"
if "Referer" in request.headers:
if len(request.headers["Referer"].split("/")) == 5:
response.headers["Location"] = request.headers["Referer"]
response.set_cookie(
"LAST_ACTIVE",
request.app["Sessions"][session]["active_project"]["id"],
expires=str(setd["history_lifetime"]), # type: ignore
)

return response


async def login_with_token(
request: aiohttp.web.Request,
token: str,
) -> typing.Union[aiohttp.web.Response, aiohttp.web.FileResponse]:
"""Log in a session with token."""
# Establish connection and begin user session
response: typing.Union[aiohttp.web.Response, aiohttp.web.FileResponse]
response = aiohttp.web.Response(status=303)
cookie, _ = generate_cookie(request)

Expand Down Expand Up @@ -141,11 +245,11 @@ async def sso_query_end(
request.app["Sessions"][session] = {}

# Save the unscoped token to the session, as it's needed for re-scoping
request.app["Sessions"][session]["Token"] = unscoped
request.app["Sessions"][session]["Token"] = token

try:
# Check token availability
request.app["Sessions"][session]["Avail"] = get_availability_from_token(unscoped)
request.app["Sessions"][session]["Avail"] = get_availability_from_token(token)
except urllib.error.HTTPError:
raise aiohttp.web.HTTPUnauthorized(
reason="Token no longer valid",
Expand All @@ -169,9 +273,7 @@ async def sso_query_end(
project_id = request.app["Sessions"][session]["Avail"]["projects"][0]["id"]

# Open an OS session for the first project that's found for the user.
request.app["Sessions"][session]["OS_sess"] = initiate_os_session(
unscoped, project_id
)
request.app["Sessions"][session]["OS_sess"] = initiate_os_session(token, project_id)

test_swift_endpoint(
request.app["Sessions"][session]["OS_sess"].get_endpoint(
Expand Down Expand Up @@ -220,65 +322,6 @@ async def sso_query_end(
return response


async def token_rescope(request: aiohttp.web.Request) -> aiohttp.web.Response:
"""Rescope the requesting session's token to the new project."""
session_check(request)
session = decrypt_cookie(request)["id"]
request.app["Log"].info(
f"Call to rescope token from {request.remote}, sess: {session} :: {time.ctime()}"
)

if request.query["project"] not in [
p["id"] for p in request.app["Sessions"][session]["Avail"]["projects"]
]:
raise aiohttp.web.HTTPForbidden(
reason="The project is not available for this token."
)

# Invalidate the old scoped token
request.app["Sessions"][session]["OS_sess"].invalidate(
request.app["Sessions"][session]["OS_sess"].auth
)
# Overwrite the old session with a new one, with the updated project id
request.app["Sessions"][session]["OS_sess"] = initiate_os_session(
request.app["Sessions"][session]["Token"],
request.query["project"],
)
# Overwrite the old connection with a new one, with the updated keystone
# session
request.app["Sessions"][session]["ST_conn"] = initiate_os_service(
request.app["Sessions"][session]["OS_sess"],
)

# Ditch the session download proxy if that exists
if "runner" in request.app["Sessions"][session].keys():
request.app["Sessions"][session].pop("runner")

# Save the new project as the active project in session
new_project_name = [
i["name"]
for i in request.app["Sessions"][session]["Avail"]["projects"]
if i["id"] == request.query["project"]
][0]
request.app["Sessions"][session]["active_project"] = {
"name": new_project_name,
"id": request.query["project"],
}

response = aiohttp.web.Response(status=303, reason="Successfully rescoped token.")
response.headers["Location"] = "/browse"
if "Referer" in request.headers:
if len(request.headers["Referer"].split("/")) == 5:
response.headers["Location"] = request.headers["Referer"]
response.set_cookie(
"LAST_ACTIVE",
request.app["Sessions"][session]["active_project"]["id"],
expires=str(setd["history_lifetime"]), # type: ignore
)

return response


async def handle_logout(request: aiohttp.web.Request) -> aiohttp.web.Response:
"""Properly kill the session for the user."""
session = ""
Expand Down
Loading

0 comments on commit 325409c

Please sign in to comment.