Skip to content

Commit

Permalink
Merge pull request #37 from arXiv/develop
Browse files Browse the repository at this point in the history
Pre-release merge for v0.3.1
  • Loading branch information
erickpeirson committed Feb 28, 2019
2 parents 73345a3 + 185ddfc commit de1a009
Show file tree
Hide file tree
Showing 13 changed files with 361 additions and 370 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ before_install:
- pip install pipenv
- pipenv install --dev
script:
- pipenv install ./users
- pipenv install ./users --skip-lock
- "./lintstats.sh"
- WITH_INTEGRATION=1 pipenv run pytest --cov=accounts --cov=users/arxiv --cov=registry
--cov=authenticator --cov-report=term-missing accounts users/arxiv registry authenticator
Expand Down
13 changes: 13 additions & 0 deletions DECISIONS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
# Decision log

1. To minimize complexity, we'll start with a managed Redis cluster in AWS
ElastiCache. In the future, we may consider running our own HA key-value
store, and potentially evaluate performance of other backends.
2. In NG session store, will need to attach auth scope information. For now we
can decide what "admin", "moderator", etc means and inject the relevant
scopes. Later on, we will have an RBAC system. We therefore screen off the
scope-determination concern from the session-creation concern.

## 2019-02-28 Rename ``request.session`` to ``request.auth``

The auth middleware in ``arxiv.users`` package hands the authenticated session
on ``flask.session``, which clobbers the built-in Flask session interface. This
is a design flaw that's blocking other work. ARXIVNG-1920

Starting with v0.3.1, set ``AUTH_UPDATED_SESSION_REF=True`` in your
application config to rename ``request.session`` to ``request.auth``.
``request.auth`` will be the default name for the authenticated session
starting in v0.4.1.
7 changes: 3 additions & 4 deletions Dockerfile-authenticator
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ ENV LC_ALL en_US.utf-8
ENV LANG en_US.utf-8
RUN pipenv install
ADD users/ /opt/arxiv/users/
RUN pipenv install /opt/arxiv/users/
RUN pipenv install /opt/arxiv/users/ --skip-lock

ENV PATH "/opt/arxiv:${PATH}"

Expand All @@ -25,9 +25,8 @@ CMD ["--http-socket", ":8000", \
"-M", \
"-t 3000", \
"--manage-script-name", \
"--processes", "8", \
"--processes", "1", \
"--threads", "1", \
"--async", "100", \
"--ugreen", \
"--queue", "0", \
"--mount", "/=wsgi.py", \
"--logformat", "%(addr) %(addr) - %(user_id)|%(session_id) [%(rtime)] [%(uagent)] \"%(method) %(uri) %(proto)\" %(status) %(size) %(micros) %(ttfb)"]
11 changes: 5 additions & 6 deletions Dockerfile-registry
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ RUN pip install -U pip pipenv
ENV LC_ALL en_US.utf-8
ENV LANG en_US.utf-8

ADD Pipfile /opt/arxiv/
ADD Pipfile Pipfile.lock /opt/arxiv/
RUN pipenv install

ADD users/ /opt/arxiv/users/
RUN pipenv install /opt/arxiv/users/
RUN pipenv install /opt/arxiv/users/ --skip-lock

ENV PATH "/opt/arxiv:${PATH}"

Expand All @@ -29,9 +29,8 @@ CMD ["uwsgi", "--http-socket", ":8000", \
"-M", \
"-t 3000", \
"--manage-script-name", \
"--processes", "8", \
"--processes", "1", \
"--threads", "1", \
"--async", "100", \
"--ugreen", \
"--mount", "/api=wsgi.py", \
"--queue", "0", \
"--mount", "/=wsgi.py", \
"--logformat", "%(addr) %(addr) - %(user_id)|%(session_id) [%(rtime)] [%(uagent)] \"%(method) %(uri) %(proto)\" %(status) %(size) %(micros) %(ttfb)"]
11 changes: 4 additions & 7 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,21 @@ requests = "==2.20.0"
sqlalchemy = "*"
uwsgi = "*"
wtforms = "*"
arxiv-base = "==0.12.1"
arxiv-base = "==0.14.3"
authlib = "*"
openapi-spec-validator = "*"
"4010dd8" = {path = "./users"}
bleach = "*"

[dev-packages]
mimesis = "==2.1.0"
mypy = "*"
pydocstyle = "*"
mypy = "==0.670"
pydocstyle = "==2.1.1"
pylint = "*"
sphinx = "*"
sphinx-autodoc-typehints = "*"
coverage = "*"
coveralls = "*"
astroid = "<2.2"
pytest = "*"
pytest-cov = "*"
"nose2" = "*"

[requires]
python_version = "3.6"
545 changes: 200 additions & 345 deletions Pipfile.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ TLS is considered an infrastructure concern, and is therefore out of scope
- Clean up and document authenticator service.
- Fuzz testing registration?

## Updates

- Starting with v0.3.1, set ``AUTH_UPDATED_SESSION_REF=True`` in your
application config to rename ``request.session`` to ``request.auth``.
``request.auth`` will be the default name for the authenticated session
starting in v0.4.1.

## Dependencies

We use pipenv to manage dependencies. To install the dependencies for this
Expand Down
15 changes: 14 additions & 1 deletion users/arxiv/users/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from typing import Optional, Union
from datetime import datetime
import warnings
from pytz import UTC
from flask import Flask, request, Response, make_response, redirect, url_for
from werkzeug.http import parse_cookie
Expand Down Expand Up @@ -121,7 +122,19 @@ def load_session(self) -> None:

# Attach the session to the request so that other
# components can access it easily.
request.session = session
if self.app.config.get('AUTH_UPDATED_SESSION_REF'):
request.auth = session
else:
# This clobbers the built-in Flask session interface. This is a
# design flaw that's blocking other work. This is deprecated and
# will be removed in 0.4.1.
warnings.warn(
"Accessing the authenticated session via request.session is"
" deprecated, and will be removed in 0.4.1. Use request.auth"
" instead. ARXIVNG-1920.",
DeprecationWarning
)
request.session = session

def detect_and_clobber_dupe_cookies(self) -> Optional[Response]:
"""
Expand Down
23 changes: 23 additions & 0 deletions users/arxiv/users/auth/tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,29 @@ def protected():
with self.assertRaises(Forbidden):
protected()

@mock.patch(f'{decorators.__name__}.request')
def test_scope_is_present(self, mock_request):
"""Session has required scope."""
mock_request.session = domain.Session(
session_id='fooid',
start_time=datetime.now(tz=UTC),
user=domain.User(
user_id='235678',
email='[email protected]',
username='foouser'
),
authorizations=domain.Authorizations(
scopes=[scopes.VIEW_SUBMISSION, scopes.CREATE_SUBMISSION]
)
)

@decorators.scoped(scopes.CREATE_SUBMISSION)
def protected():
"""A protected function."""

# with self.assertRaises(Forbidden):
protected()

@mock.patch(f'{decorators.__name__}.request')
def test_user_and_client_are_missing(self, mock_request):
"""Session does not user nor client information."""
Expand Down
41 changes: 40 additions & 1 deletion users/arxiv/users/auth/tests/test_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,49 @@ def test_legacy_is_valid(self, mock_request, mock_legacy):
mock_legacy.sessions.load.return_value = session

inst = auth.Auth(mock_app)
inst.load_session()
# ARXIVNG-1920 using request.session is deprecated.
with self.assertWarns(DeprecationWarning):
inst.load_session()
self.assertEqual(mock_request.session, session,
"Session is attached to the request")

@mock.patch(f'{auth.__name__}.legacy')
@mock.patch(f'{auth.__name__}.request')
def test_auth_session_rename(self, mock_request, mock_legacy):
"""
The auth session is accessed via ``request.auth``.
Per ARXIVNG-1920 using ``request.session`` is deprecated.
"""
mock_request.environ = {'session': None}
mock_request.cookies = {'foo_cookie': 'sessioncookie123'}
mock_app = mock.MagicMock(
config={'CLASSIC_COOKIE_NAME': 'foo_cookie',
'AUTH_UPDATED_SESSION_REF': True}
)
mock_request.session = None
mock_request.auth = None
mock_legacy.is_configured.return_value = True
session = domain.Session(
session_id='fooid',
start_time=datetime.now(tz=UTC),
user=domain.User(
user_id='235678',
email='[email protected]',
username='foouser'
),
authorizations=domain.Authorizations(
scopes=[auth.scopes.VIEW_SUBMISSION]
)
)
mock_legacy.sessions.load.return_value = session

inst = auth.Auth(mock_app)
inst.load_session()
self.assertEqual(mock_request.auth, session,
"Session is attached to the request")
self.assertIsNone(mock_request.session, "request.session is not set")

@mock.patch(f'{auth.__name__}.request')
def test_middleware_exception(self, mock_request):
"""Middleware has passed an exception."""
Expand Down
11 changes: 7 additions & 4 deletions users/arxiv/users/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import uuid
from datetime import timedelta, datetime
from arxiv.users import auth, domain
from arxiv.base.globals import get_application_config


def generate_token(user_id: str, email: str, username: str,
Expand All @@ -19,7 +20,8 @@ def generate_token(user_id: str, email: str, username: str,
),
submission_groups: str = 'grp_physics',
endorsements: List[domain.Category] = [],
scope: List[domain.Scope] = []) -> None:
scope: List[domain.Scope] = [],
verified: bool = False) -> None:
"""Generate an auth token for dev/testing purposes."""
# Specify the validity period for the session.
start = datetime.now(tz=timezone('US/Eastern'))
Expand All @@ -40,10 +42,11 @@ def generate_token(user_id: str, email: str, username: str,
country=country,
default_category=default_category,
submission_groups=submission_groups.split(',')
)
),
verified=verified
),
authorizations=domain.Authorizations(scopes=scope,
authorizations=domain.Authorizations(scopes=[str(s) for s in scope],
endorsements=endorsements)
)
token = auth.tokens.encode(session, os.environ['JWT_SECRET'])
token = auth.tokens.encode(session, get_application_config()['JWT_SECRET'])
return token
43 changes: 43 additions & 0 deletions users/arxiv/users/tests/test_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Tests for :mod:`.helpers`."""

from unittest import TestCase, mock
import os
from flask import Flask
from arxiv import status
from arxiv.base import Base
from arxiv.base.middleware import wrap
from .. import auth, helpers


class TestGenerateToken(TestCase):
"""Tests for :func:`.helpers.generate_token`."""

@mock.patch(f'{helpers.__name__}.get_application_config')
def test_token_is_usable(self, mock_get_config):
"""Verify that :func:`.helpers.generate_token` makes usable tokens."""
mock_get_config.return_value = {'JWT_SECRET': 'thesecret'}
os.environ['JWT_SECRET'] = 'thesecret'
scope = [auth.scopes.VIEW_SUBMISSION, auth.scopes.EDIT_SUBMISSION,
auth.scopes.CREATE_SUBMISSION]
token = helpers.generate_token("1234", "[email protected]", "theuser",
scope=scope)

app = Flask('test')
app.config['JWT_SECRET'] = 'thesecret'
Base(app)
auth.Auth(app) # <- Install the Auth extension.
wrap(app, [auth.middleware.AuthMiddleware]) # <- Install middleware.

@app.route('/')
@auth.decorators.scoped(auth.scopes.EDIT_SUBMISSION)
def protected():
return "this is protected"

client = app.test_client()
with app.app_context():
response = client.get('/')
self.assertEqual(response.status_code,
status.HTTP_401_UNAUTHORIZED)

response = client.get('/', headers={'Authorization': token})
self.assertEqual(response.status_code, status.HTTP_200_OK)
2 changes: 1 addition & 1 deletion users/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

setup(
name='arxiv-auth',
version='0.2.7',
version='0.3.1',
packages=[f'arxiv.{package}' for package
in find_packages('./arxiv', exclude=['*test*'])],
install_requires=[
Expand Down

0 comments on commit de1a009

Please sign in to comment.