Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth: account info and public-only events #254

Merged
merged 2 commits into from
May 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions abe/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import logging

from .resource_models.account_resources import api as account_api
from .resource_models.event_resources import api as event_api
from .resource_models.label_resources import api as label_api
from .resource_models.ics_resources import api as ics_api
Expand Down Expand Up @@ -80,6 +81,7 @@ def call_after_request_callbacks(response): # For deferred callbacks


# Route resources
api.add_namespace(account_api)
api.add_namespace(event_api)
api.add_namespace(label_api)
api.add_namespace(ics_api)
Expand Down
4 changes: 2 additions & 2 deletions abe/helper_functions/query_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def event_query(search_dict):
'labels': lambda a: {'labels': {'$in': a}},
'labels_and': lambda a: {'labels': {'$all': a}},
'labels_not': lambda a: {'labels': {'$nin': a}},
'visibility': lambda a: {'visibility': {'$in': a}},
'visibility': lambda a: {'visibility': {'$eq': a}},
}

params_recu_event = {
Expand All @@ -79,7 +79,7 @@ def event_query(search_dict):
'labels': lambda a: {'labels': {'$in': a}},
'labels_and': lambda a: {'labels': {'$all': a}},
'labels_not': lambda a: {'labels': {'$nin': a}},
'visibility': lambda a: {'visibility': {'$in': a}},
'visibility': lambda a: {'visibility': {'$eq': a}},
}

# query for regular events
Expand Down
29 changes: 29 additions & 0 deletions abe/resource_models/account_resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from flask import request
from flask_restplus import Namespace, Resource, fields

from abe.auth import check_auth

api = Namespace('account', description='Account info')

resource_model = api.model("Account", {
"authenticated": fields.Boolean(required=True),
"permissions": fields.List(fields.String, required=True)
})


class AccountApi(Resource):
"""Account information for the authenticated user"""

@api.marshal_with(resource_model)
def get(self):
auth = check_auth(request)
permissions = []
if auth:
permissions += ['view_all_events', 'add_events', 'edit_events']
return {
'authenticated': auth,
'permissions': permissions,
}


api.add_resource(AccountApi, '/', methods=['GET'], endpoint='account')
16 changes: 9 additions & 7 deletions abe/resource_models/event_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from flask_restplus import Namespace, Resource, fields

from abe import database as db
from abe.auth import edit_auth_required
from abe.auth import check_auth, edit_auth_required
from abe.helper_functions.converting_helpers import mongo_to_dict, request_to_dict
from abe.helper_functions.mongodb_helpers import mongo_resource_errors
from abe.helper_functions.query_helpers import event_query, get_to_event_search
Expand Down Expand Up @@ -95,9 +95,12 @@ def get(self, event_id=None, rec_id=None):
if query_time_period > timedelta(days=366):
return "Too wide of date range in query. Max date range of 1 year allowed.", 404

if not check_auth(request):
query_dict['visibility'] = 'public'

query = event_query(query_dict)
results = db.Event.objects(__raw__=query) # {'start': new Date('2017-06-14')})
logging.debug('found {} events for query'.format(len(results)))
logging.debug('found %s events for query', len(results))

if not results: # if no results were found
return []
Expand All @@ -108,7 +111,6 @@ def get(self, event_id=None, rec_id=None):

events_list = []
for event in results:

if 'recurrence' in event: # checks for recurrent events
# expands a recurring event defintion into a json response with individual events
events_list = recurring_to_full(event, events_list, start, end)
Expand All @@ -127,7 +129,7 @@ def post(self):
Create new event with parameters passed in through args or form
"""
received_data = request_to_dict(request)
logging.debug("Received POST data: {}".format(received_data)) # combines args and form
logging.debug("Received POST data: %s", received_data) # combines args and form
new_event = db.Event(**received_data)
if new_event.labels == []: # if no labels were given
new_event.labels = ['unlabeled']
Expand All @@ -148,7 +150,7 @@ def put(self, event_id):
event_id id of the event to modify
"""
received_data = request_to_dict(request)
logging.debug("Received PUT data: {}".format(received_data))
logging.debug("Received PUT data: %s", received_data)
result = db.Event.objects(id=event_id).first()
if not result: # if no event was found
# try finding a sub_event with the id and save the parent event it is stored under
Expand Down Expand Up @@ -180,7 +182,7 @@ def delete(self, event_id, rec_id=None):

rec_id the rec_id of a sub_event to be deleted
"""
logging.debug('Event requested: ' + event_id)
logging.debug('Event requested: %s', event_id)
result = db.Event.objects(id=event_id).first()
if not result: # if no event is found with the id given
# try finding a sub_event with that id
Expand All @@ -197,7 +199,7 @@ def delete(self, event_id, rec_id=None):
logging.debug("Deleted sub_event for the first time")
else: # if a normal event is to be deleted
received_data = request_to_dict(request)
logging.debug("Received DELETE data: {}".format(received_data))
logging.debug("Received DELETE data: %s", received_data)
result.delete()
return mongo_to_dict(result)

Expand Down
44 changes: 44 additions & 0 deletions tests/test_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import os
from importlib import reload

import flask

from . import abe_unittest
from .context import abe # noqa: F401

# These imports have to happen after .context sets the environment variables
from abe.app import app # isort:skip
from abe import auth # isort:skip # noqa: F401


class AccountTestCase(abe_unittest.TestCase):

def setUp(self):
global auth
super().setUp()
# self.app = app.test_client(use_cookies=False)
os.environ['INTRANET_IPS'] = "127.0.0.1/24"
reload(auth)

def test_unauthorized_client(self):
client = app.test_client()
response = client.get(
'/account/',
content_type='application/json',
headers={'X-Forwarded-For': '192.168.1.1'}
)
self.assertEqual(response.status_code, 200)
account = flask.json.loads(response.data)
self.assertEqual(account['authenticated'], False)
self.assertEqual(account['permissions'], [])

def test_authorized_client(self):
client = app.test_client()
response = client.get(
'/account/',
content_type='application/json',
)
self.assertEqual(response.status_code, 200)
account = flask.json.loads(response.data)
self.assertEqual(account['authenticated'], True)
self.assertEqual(set(account['permissions']), {'add_events', 'edit_events', 'view_all_events'})
46 changes: 23 additions & 23 deletions tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,30 @@ class EventsTestCase(abe_unittest.TestCase):

def setUp(self):
super().setUp()
self.app = abe.app.app.test_client()
self.client = abe.app.app.test_client()
sample_data.load_data(self.db)

def test_get_events(self):
with self.subTest("a six-month query returns some events"):
response = self.app.get('/events/?start=2017-01-01&end=2017-07-01')
response = self.client.get('/events/?start=2017-01-01&end=2017-07-01')
self.assertEqual(response.status_code, 200)
self.assertEqual(len(flask.json.loads(response.data)), 25)

with self.subTest("a one-year query returns all events"):
response = self.app.get('/events/?start=2017-01-01&end=2018-01-01')
response = self.client.get('/events/?start=2017-01-01&end=2018-01-01')
self.assertEqual(response.status_code, 200)
self.assertEqual(len(flask.json.loads(response.data)), 69)

with self.subTest("a two-year query is too long"):
response = self.app.get('/events/?start=2017-01-01&end=2019-01-01')
response = self.client.get('/events/?start=2017-01-01&end=2019-01-01')
self.assertEqual(response.status_code, 404)

with self.subTest("a one-year query works for leap years"):
response = self.app.get('/events/?start=2020-01-01&end=2021-01-01')
response = self.client.get('/events/?start=2020-01-01&end=2021-01-01')
self.assertEqual(response.status_code, 200)

with self.subTest("an unauthenticated sender retrieves only public events"):
event_response = self.app.get('/events/?start=2017-01-01&end=2017-07-01')
event_response = self.client.get('/events/?start=2017-01-01&end=2017-07-01')
# TODO: change the following when #75 is implemented
self.assertEqual(len(flask.json.loads(event_response.data)), 25)

Expand All @@ -49,7 +49,7 @@ def test_post(self):
}

with self.subTest("succeeds when required fields are present"):
response = self.app.post(
response = self.client.post(
'/events/',
data=flask.json.dumps(event),
content_type='application/json'
Expand All @@ -59,7 +59,7 @@ def test_post(self):
with self.subTest("fails on missing fields"):
evt = event.copy()
del evt['title']
response = self.app.post(
response = self.client.post(
'/events/',
data=flask.json.dumps(evt),
content_type='application/json'
Expand All @@ -69,7 +69,7 @@ def test_post(self):

evt = event.copy()
del evt['start']
response = self.app.post(
response = self.client.post(
'/events/',
data=flask.json.dumps(evt),
content_type='application/json'
Expand All @@ -80,7 +80,7 @@ def test_post(self):
with self.subTest("fails on invalid fields"):
evt = event.copy()
evt['invalid'] = 'invalid field'
response = self.app.post(
response = self.client.post(
'/events/',
data=flask.json.dumps(evt),
content_type='application/json'
Expand All @@ -90,7 +90,7 @@ def test_post(self):
with self.subTest("fails on invalid field values"):
evt = event.copy()
evt['url'] = 'invalid field value'
response = self.app.post(
response = self.client.post(
'/events/',
data=flask.json.dumps(evt),
content_type='application/json'
Expand All @@ -103,7 +103,7 @@ def test_post_auth(self):
'start': isodate.parse_datetime('2018-05-04T09:00:00')
}
with self.subTest("fails when the client is not yet authorized"):
response = self.app.post(
response = self.client.post(
'/events/',
data=flask.json.dumps(event),
content_type='application/json',
Expand All @@ -114,7 +114,7 @@ def test_post_auth(self):
self.assertEqual(response.status_code, 401)

with self.subTest("succeeds when required fields are present"):
response = self.app.post(
response = self.client.post(
'/events/',
data=flask.json.dumps(event),
content_type='application/json'
Expand All @@ -123,7 +123,7 @@ def test_post_auth(self):
# TODO: test that the event is in the database

with self.subTest("succeeds due to auth cookie"):
response = self.app.post(
response = self.client.post(
'/events/',
data=flask.json.dumps(event),
content_type='application/json',
Expand All @@ -135,12 +135,12 @@ def test_post_auth(self):

def test_put(self):
# TODO: test unauthorized user
response = self.app.get('/events/?start=2017-01-01&end=2017-07-01')
response = self.client.get('/events/?start=2017-01-01&end=2017-07-01')
self.assertEqual(response.status_code, 200)
event_id = flask.json.loads(response.data)[0]['id']

with self.subTest("succeeds on valid id"):
response = self.app.put(
response = self.client.put(
f'/events/{event_id}',
data=flask.json.dumps({'title': 'new title'}),
content_type='application/json'
Expand All @@ -149,7 +149,7 @@ def test_put(self):
# TODO: test that the event has a new value

with self.subTest("fails on invalid id"):
response = self.app.put(
response = self.client.put(
f'/events/{event_id}x',
data=flask.json.dumps({'title': 'new title'}),
content_type='application/json'
Expand All @@ -158,15 +158,15 @@ def test_put(self):
self.assertEqual(response.status_code, 400)

with self.subTest("fails on invalid field"):
response = self.app.put(
response = self.client.put(
f'/events/{event_id}',
data=flask.json.dumps({'invalid_field': 'value'}),
content_type='application/json'
)
self.assertEqual(response.status_code, 400)

with self.subTest("fails on invalid field value"):
response = self.app.put(
response = self.client.put(
f'/events/{event_id}',
data=flask.json.dumps({'url': 'invalid url'}),
content_type='application/json'
Expand All @@ -175,7 +175,7 @@ def test_put(self):

# This exercises a different code path in mongodb -> JOSN error conversion
with self.subTest("fails on multiple invalid fields"):
response = self.app.put(
response = self.client.put(
f'/events/{event_id}',
data=flask.json.dumps({'invalid_field-1': 'value',
'invalid_field-2': 'value'}),
Expand All @@ -189,7 +189,7 @@ def test_put(self):
'name': 'protected_label',
'protected': True
}
self.app.post(
self.client.post(
'/labels/',
data=flask.json.dumps(label1),
content_type='application/json'
Expand All @@ -199,13 +199,13 @@ def test_put(self):
'start': isodate.parse_datetime('2018-05-10T09:00:00'),
'labels': ["protected_label"]
}
response = self.app.post(
response = self.client.post(
'/events/',
data=flask.json.dumps(event),
content_type='application/json'
)
event_id = flask.json.loads(response.data.decode("utf-8"))['id']
response = self.app.put(
response = self.client.put(
f'/events/{event_id}',
data=flask.json.dumps({'title': 'new title'}),
content_type='application/json'
Expand Down