Skip to content

Commit

Permalink
Merge branch 'master' into test-utils-filesystem
Browse files Browse the repository at this point in the history
  • Loading branch information
bourque authored Feb 1, 2019
2 parents 17e9571 + ca51c37 commit f49fbc5
Show file tree
Hide file tree
Showing 12 changed files with 366 additions and 34 deletions.
6 changes: 6 additions & 0 deletions docs/source/website.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ db.py
:members:
:undoc-members:

oauth.py
--------
.. automodule:: jwql.website.apps.jwql.oauth
:members:
:undoc-members:

manage.py
---------
.. automodule:: jwql.website.manage
Expand Down
3 changes: 2 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ dependencies:
- sqlalchemy=1.2.0
- stsci_rtd_theme=0.0.2
- pip:
- sphinx-automodapi==0.10
- authlib==0.10
- sphinx-automodapi==0.10
14 changes: 5 additions & 9 deletions jwql/jwql_monitors/generate_preview_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,10 @@
import numpy as np

from jwql.utils import permissions
from jwql.utils.logging_functions import configure_logging
from jwql.utils.logging_functions import log_info
from jwql.utils.logging_functions import log_fail
from jwql.utils.constants import NIRCAM_LONGWAVE_DETECTORS, NIRCAM_SHORTWAVE_DETECTORS
from jwql.utils.logging_functions import configure_logging, log_info, log_fail
from jwql.utils.preview_image import PreviewImage
from jwql.utils.utils import get_config
from jwql.utils.utils import filename_parser
from jwql.utils.utils import NIRCAM_LONGWAVE_DETECTORS
from jwql.utils.utils import NIRCAM_SHORTWAVE_DETECTORS
from jwql.utils.utils import get_config, filename_parser

# Size of NIRCam inter- and intra-module chip gaps
SW_MOD_GAP = 1387 # pixels = int(43 arcsec / 0.031 arcsec/pixel)
Expand Down Expand Up @@ -637,8 +633,8 @@ def group_filenames(input_files):
suffix = filename_parts['suffix']

observation_base = 'jw{}{}{}_{}{}{}_{}_'.format(
program, observation, visit, visit_group,
parallel, activity, exposure)
program, observation, visit, visit_group,
parallel, activity, exposure)

if detector in NIRCAM_SHORTWAVE_DETECTORS:
detector_str = 'NRC[AB][1234]'
Expand Down
9 changes: 5 additions & 4 deletions jwql/tests/test_api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@
from jwql.utils.utils import get_base_url


# Determine if this module is being run in production or locally
base_url = get_base_url()

urls = [
'api/proposals/', # all_proposals
'api/86700/filenames/', # filenames_by_proposal
Expand All @@ -39,9 +36,9 @@
'api/fgs/thumbnails/', # thumbnails_by_instrument
'api/86700/thumbnails/', # thumbnails_by_proposal
'api/jw86700005001_02101_00001_guider1/thumbnails/'] # thumbnails_by_rootname
urls = ['{}/{}'.format(base_url, url) for url in urls]


@pytest.mark.xfail
@pytest.mark.parametrize('url', urls)
def test_api_views(url):
"""Test to see if the given ``url`` returns a populated JSON object
Expand All @@ -53,6 +50,10 @@ def test_api_views(url):
``http://127.0.0.1:8000/api/86700/filenames/'``).
"""

# Build full URL
base_url = get_base_url()
url = '{}/{}'.format(base_url, url)

# Determine the type of data to check for based on the url
data_type = url.split('/')[-2]

Expand Down
11 changes: 9 additions & 2 deletions jwql/tests/test_edb_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,25 @@
"""

from astropy.time import Time

from ..utils.engineering_database import query_single_mnemonic, get_all_mnemonic_identifiers
import pytest


@pytest.mark.xfail
def test_get_all_mnemonics():
"""Test the retrieval of all mnemonics."""

from ..utils.engineering_database import get_all_mnemonic_identifiers

all_mnemonics = get_all_mnemonic_identifiers()[0]
assert len(all_mnemonics) > 1000


@pytest.mark.xfail
def test_query_single_mnemonic():
"""Test the query of a mnemonic over a given time range."""

from ..utils.engineering_database import query_single_mnemonic

mnemonic_identifier = 'SA_ZFGOUTFOV'
start_time = Time(2016.0, format='decimalyear')
end_time = Time(2018.1, format='decimalyear')
Expand Down
265 changes: 265 additions & 0 deletions jwql/website/apps/jwql/oauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
"""Provides an OAuth object for authentication of the ``jwql`` web app,
as well as decorator functions to require user authentication in other
views of the web application.
Authors
-------
- Matthew Bourque
- Christian Mesh
Use
---
This module is intended to be imported and used as such:
::
from .oauth import auth_info
from .oauth import auth_required
from .oauth import JWQL_OAUTH
@auth_info
def some_view(request):
pass
@auth_required
def login(request):
pass
References
----------
Much of this code was taken from the ``authlib`` documentation,
found here: ``http://docs.authlib.org/en/latest/client/django.html``
Dependencies
------------
The user must have a configuration file named ``config.json``
placed in the ``jwql/utils/`` directory.
"""

import os
import requests

from authlib.django.client import OAuth
from django.shortcuts import redirect

from jwql.utils.utils import get_base_url, get_config


def register_oauth():
"""Register the ``jwql`` application with the ``auth.mast``
authentication service.
Returns
-------
oauth : Object
An object containing methods to authenticate a user, provided
by the ``auth.mast`` service.
"""

# Get configuration parameters
client_id = get_config()['client_id']
client_secret = get_config()['client_secret']
auth_mast = get_config()['auth_mast']

# Register with auth.mast
oauth = OAuth()
client_kwargs = {'scope': 'mast:user:info'}
oauth.register(
'mast_auth',
client_id='{}'.format(client_id),
client_secret='{}'.format(client_secret),
access_token_url='https://{}/oauth/access_token?client_secret={}'.format(auth_mast, client_secret),
access_token_params=None,
refresh_token_url=None,
authorize_url='https://{}/oauth/authorize'.format(auth_mast),
api_base_url='https://{}/1.1/'.format(auth_mast),
client_kwargs=client_kwargs)

return oauth

JWQL_OAUTH = register_oauth()


def authorize(request):
"""Spawn the authentication process for the user
The authentication process involves retreiving an access token
from ``auth.mast`` and porting the data to a cookie.
Parameters
----------
request : HttpRequest object
Incoming request from the webpage
Returns
-------
HttpResponse object
Outgoing response sent to the webpage
"""

# Get auth.mast token
token = JWQL_OAUTH.mast_auth.authorize_access_token(request, headers={'Accept': 'application/json'})

# Determine domain
base_url = get_base_url()
if '127' in base_url:
domain = '127.0.0.1'
else:
domain = base_url.split('//')[-1]

# Set secure cookie parameters
cookie_args = {}
# cookie_args['domain'] = domain # Currently broken
# cookie_args['secure'] = True # Currently broken
cookie_args['httponly'] = True

# Set the cookie
response = redirect("/")
response.set_cookie("ASB-AUTH", token["access_token"], **cookie_args)

return response


def auth_required(fn):
"""A decorator function that requires the given function to have
authentication through ``auth.mast`` set up.
Parameters
----------
fn : function
The function to decorate
Returns
-------
check_auth : function
The decorated function
"""

@auth_info
def check_auth(request, user):
"""Check if the user is authenticated through ``auth.mast``.
If not, perform the authorization.
Parameters
----------
request : HttpRequest object
Incoming request from the webpage
user : dict
A dictionary of user credentials
Returns
-------
fn : function
The decorated function
"""

# If user is currently anonymous, require a login
if user["anon"]:
# Redirect to oauth login
redirect_uri = os.path.join(get_base_url(), 'authorize')
return JWQL_OAUTH.mast_auth.authorize_redirect(request, redirect_uri)

return fn(request, user)

return check_auth


def auth_info(fn):
"""A decorator function that will return user credentials along
with what is returned by the original function.
Parameters
----------
fn : function
The function to decorate
Returns
-------
user_info : function
The decorated function
"""

def user_info(request, **kwargs):
"""Store authenticated user credentials in a cookie and return
it. If the user is not authenticated, store no credentials in
the cookie.
Parameters
----------
request : HttpRequest object
Incoming request from the webpage
Returns
-------
fn : function
The decorated function
"""

cookie = request.COOKIES.get("ASB-AUTH")

# If user is authenticated, return user credentials
if cookie is not None:
response = requests.get(
'https://{}/info'.format(get_config()['auth_mast']),
headers={'Accept': 'application/json',
'Authorization': 'token {}'.format(cookie)})
response = response.json()

# If user is not authenticated, return no credentials
else:
response = {'ezid' : None, "anon": True}

return fn(request, response, **kwargs)

return user_info


@auth_required
def login(request, user):
"""Spawn a login process for the user
The ``auth_requred`` decorator is used to require that the user
authenticate through ``auth.mast``, then the user is redirected
back to the homepage.
Parameters
----------
request : HttpRequest object
Incoming request from the webpage
user : dict
A dictionary of user credentials.
Returns
-------
HttpResponse object
Outgoing response sent to the webpage
"""

return redirect("/")


def logout(request):
"""Spawn a logout process for the user
Upon logout, the user's ``auth.mast`` credientials are removed and
the user is redirected back to the homepage.
Parameters
----------
request : HttpRequest object
Incoming request from the webpage
user : dict
A dictionary of user credentials.
Returns
-------
HttpResponse object
Outgoing response sent to the webpage
"""

response = redirect("/")
response.delete_cookie("ASB-AUTH")

return response
8 changes: 8 additions & 0 deletions jwql/website/apps/jwql/static/css/jwql.css
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,14 @@
text-transform: uppercase;
}

#oauth_user {
color: #c85108;
}

#oauth_user:hover {
color: white;
}

.plot-container {
width: 100%;
height: 600px;
Expand Down
Loading

0 comments on commit f49fbc5

Please sign in to comment.