From 01d8cda5d43d2a4738af3110bbd7c31b9c0de89b Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Mon, 4 Apr 2016 16:01:49 -0400 Subject: [PATCH 1/2] Add a role permission. --- app/permissions/application.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/permissions/application.py b/app/permissions/application.py index d96aec0..99853cb 100644 --- a/app/permissions/application.py +++ b/app/permissions/application.py @@ -79,3 +79,31 @@ class AppNeed(Permission): return True app_need = AppNeed() + +class RoleNeed(Permission): + """Checks if a role is met by hitting the app service.""" + @property + def error(self): + """Return an error based on the role.""" + return Unauthorized( + 'Please authenticate with an application with the {} role.'\ + .format(self.role)) + + def __init__(self, role): + """Initialize with a role.""" + self.role = role + + def is_met(self): + """Hit the app service and check if the role is available.""" + from flask import session + response = services.app.get('/role/', params={ + 'client_key': session.get('app_key'), + 'server_key': services.config['app_key'], + 'name': self.role}) + if not response.status_code == 200: + return False + data = response.json() + if 'data' not in data: + return False + data = data['data'] + return len(data) > 0 -- GitLab From 2900320c11bbc12b3dcd740c30deaf427010e9d1 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Mon, 4 Apr 2016 16:35:46 -0400 Subject: [PATCH 2/2] Check for the salesforce role before allowing access to the /project/?form=salesforce POST endpoint. --- app/config/test.default.py | 10 +- app/tests/test_project.py | 274 ++++++++++++++++++++----------------- app/views/project.py | 35 ++++- 3 files changed, 184 insertions(+), 135 deletions(-) diff --git a/app/config/test.default.py b/app/config/test.default.py index 557530a..81e4fb6 100644 --- a/app/config/test.default.py +++ b/app/config/test.default.py @@ -21,13 +21,9 @@ SERVICE_CONFIG = { # App APP_CACHE_EXPIRY = 60 * 60 # One hour. -# Test applications. Replace these for testing. -TEST_SECRET_CLIENT_KEY = '$TEST_SECRET_CLIENT_KEY' -TEST_SECRET_CLIENT_SECRET = '$TEST_SECRET_CLIENT_SECRET' -TEST_REFERRER_CLIENT_KEY = '$TEST_REFERRER_CLIENT_KEY' -TEST_REFERRER_CLIENT_REFERRER = '$TEST_REFERRER_CLIENT_REFERRER' -TEST_BAD_CLIENT_KEY = '$TEST_BAD_CLIENT_KEY' -TEST_BAD_CLIENT_SECRET = '$TEST_BAD_CLIENT_SECRET' +# A client with the project_post_salesforce role. +SALESFORCE_KEY = '$SALESFORCE_KEY' +SALESFORCE_SECRET = '$SALESFORCE_SECRET' # Auth GOOGLE_AUTH_HEADER = 'x-blocpower-google-token' diff --git a/app/tests/test_project.py b/app/tests/test_project.py index 628daa9..fbe0a62 100644 --- a/app/tests/test_project.py +++ b/app/tests/test_project.py @@ -3,8 +3,9 @@ import arrow from sqlalchemy import and_ +from app.lib.service import services from app.lib.database import db -from app.tests.base import RestTestCase +from app.tests.base import RestTestCase, UnprotectedRestTestCase from app.models.client import Client from app.models.contact import Contact, ContactMethod, ProjectContact from app.models.place import Place, Address @@ -60,6 +61,152 @@ class TestProject(RestTestCase): ProjectStateChange.state == data['state']))\ .first()) + def test_post_missing_name(self): + """Tests /project/ POST with a missing name.""" + response = self.post(self.url, + data={ + 'sales_force_id': 'test', + 'client_id': self.env.client.id, + 'place_id': self.env.place.id, + 'state': 'pending'}) + self.assertEqual(response.status_code, 400) + + def test_post_missing_state(self): + """Tests /project/ POST with a missing state.""" + response = self.post(self.url, + data={ + 'sales_force_id': 'test', + 'client_id': self.env.client.id, + 'place_id': self.env.place.id, + 'name': 'test'}) + self.assertEqual(response.status_code, 400) + + def test_post_bad_state(self): + """Tests /project/ POST with a bad state.""" + response = self.post(self.url, + data={ + 'sales_force_id': 'test', + 'client_id': self.env.client.id, + 'name': 'test', + 'state': 'foo'}) + self.assertEqual(response.status_code, 400) + + def test_post_bad_client_id(self): + """Tests /project/ POST with a bad client id.""" + response = self.post(self.url, + data={ + 'sales_force_id': 'test', + 'client_id': 1, + 'place_id': self.env.place.id, + 'name': 'test', + 'state': 'pending'}) + self.assertEqual(response.status_code, 400) + + def test_post_bad_place_id(self): + """Tests /project/ POST with a bad place id.""" + response = self.post(self.url, + data={ + 'sales_force_id': 'test', + 'client_id': self.env.client.id, + 'place_id': 1, + 'name': 'test', + 'state': 'pending'}) + self.assertEqual(response.status_code, 400) + + def test_put_name(self): + """Tests /project/ PUT with a new name.""" + model = self.env.project + data = model.get_dictionary() + data.update({'name': 'foo'}) + response_data = self._test_put(model.id, data) + self.assertEqual(response_data['name'], data['name']) + self.assertEqual( + db.session.query(Project)\ + .filter(Project.id == model.id)\ + .first().name, + data['name']) + + def test_put_state(self): + """Tests /project/ PUT with a new state. + + This should create a project state change model. + """ + model = self.env.project + data = model.get_dictionary() + data.update({'state': 'accepted'}) + response_data = self._test_put(model.id, data) + self.assertEqual(response_data['state'], data['state']) + self.assertEqual( + db.session.query(Project)\ + .filter(Project.id == model.id)\ + .first().state, + data['state']) + self.assertTrue( + db.session.query(ProjectStateChange)\ + .filter(and_( + ProjectStateChange.project_id == model.id, + ProjectStateChange.state == 'accepted')) + .first()) + + def test_put_bad_state(self): + """Tests /project/ PUT with an invalid state.""" + model = self.env.project + data = model.get_dictionary() + data.update({'state': 'foo'}) + response = self.put(self.url + str(model.id), data=data) + self.assertEqual(response.status_code, 400) + + def test_put_sales_force_id(self): + """Tests /project/ PUT with a sales force id. It should 400.""" + model = self.env.project + data = model.get_dictionary() + data.update({'sales_force_id': 'foo'}) + response = self.put(self.url + str(model.id), data=data) + self.assertEqual(response.status_code, 400) + + def test_put_client_id(self): + """Tests /project/ PUT with a client id. It should 400.""" + model = self.env.project + data = model.get_dictionary() + data.update({'client_id': self.env.client.id}) + response = self.put(self.url + str(model.id), data=data) + self.assertEqual(response.status_code, 400) + + def test_put_place_id(self): + """Tests /project/ PUT with a place id. It should 400.""" + model = self.env.project + data = model.get_dictionary() + data.update({'place_id': self.env.place.id}) + response = self.put(self.url + str(model.id), data=data) + self.assertEqual(response.status_code, 400) + + def test_delete(self): + """Tests /project/ DELETE. It should 405.""" + model = self.env.project + response = self.delete(self.url + str(model.id)) + self.assertEqual(response.status_code, 405) + + +class TestSalesForceProjectAuth(UnprotectedRestTestCase): + """Tests authentication for the /project/?form=salesforce POST endpoint.""" + url = '/project/' + Model = Project + + +class TestSalesForceProject(UnprotectedRestTestCase): + """Tests the /project/?form=salesforce POST endpoint.""" + url = '/project/' + Model = Project + + @property + def global_headers(self): + """Add app headers to authenticate a salesforce client.""" + return { + services.config['headers']['app_key']: \ + self.app.config['SALESFORCE_KEY'], + services.config['headers']['app_secret']: \ + self.app.config['SALESFORCE_SECRET']} + def test_post_sf_client(self): """Tests /project/?form=salesforce POST with a new client.""" client_data = {'sales_force_id': 'test', 'name': 'test'} @@ -503,128 +650,3 @@ class TestProject(RestTestCase): # There are additional ways that posting with a note could fail, but those # are already tested in the /note/ endpoint, so there's no need to test # them here. - - def test_post_missing_name(self): - """Tests /project/ POST with a missing name.""" - response = self.post(self.url, - data={ - 'sales_force_id': 'test', - 'client_id': self.env.client.id, - 'place_id': self.env.place.id, - 'state': 'pending'}) - self.assertEqual(response.status_code, 400) - - def test_post_missing_state(self): - """Tests /project/ POST with a missing state.""" - response = self.post(self.url, - data={ - 'sales_force_id': 'test', - 'client_id': self.env.client.id, - 'place_id': self.env.place.id, - 'name': 'test'}) - self.assertEqual(response.status_code, 400) - - def test_post_bad_state(self): - """Tests /project/ POST with a bad state.""" - response = self.post(self.url, - data={ - 'sales_force_id': 'test', - 'client_id': self.env.client.id, - 'name': 'test', - 'state': 'foo'}) - self.assertEqual(response.status_code, 400) - - def test_post_bad_client_id(self): - """Tests /project/ POST with a bad client id.""" - response = self.post(self.url, - data={ - 'sales_force_id': 'test', - 'client_id': 1, - 'place_id': self.env.place.id, - 'name': 'test', - 'state': 'pending'}) - self.assertEqual(response.status_code, 400) - - def test_post_bad_place_id(self): - """Tests /project/ POST with a bad place id.""" - response = self.post(self.url, - data={ - 'sales_force_id': 'test', - 'client_id': self.env.client.id, - 'place_id': 1, - 'name': 'test', - 'state': 'pending'}) - self.assertEqual(response.status_code, 400) - - def test_put_name(self): - """Tests /project/ PUT with a new name.""" - model = self.env.project - data = model.get_dictionary() - data.update({'name': 'foo'}) - response_data = self._test_put(model.id, data) - self.assertEqual(response_data['name'], data['name']) - self.assertEqual( - db.session.query(Project)\ - .filter(Project.id == model.id)\ - .first().name, - data['name']) - - def test_put_state(self): - """Tests /project/ PUT with a new state. - - This should create a project state change model. - """ - model = self.env.project - data = model.get_dictionary() - data.update({'state': 'accepted'}) - response_data = self._test_put(model.id, data) - self.assertEqual(response_data['state'], data['state']) - self.assertEqual( - db.session.query(Project)\ - .filter(Project.id == model.id)\ - .first().state, - data['state']) - self.assertTrue( - db.session.query(ProjectStateChange)\ - .filter(and_( - ProjectStateChange.project_id == model.id, - ProjectStateChange.state == 'accepted')) - .first()) - - def test_put_bad_state(self): - """Tests /project/ PUT with an invalid state.""" - model = self.env.project - data = model.get_dictionary() - data.update({'state': 'foo'}) - response = self.put(self.url + str(model.id), data=data) - self.assertEqual(response.status_code, 400) - - def test_put_sales_force_id(self): - """Tests /project/ PUT with a sales force id. It should 400.""" - model = self.env.project - data = model.get_dictionary() - data.update({'sales_force_id': 'foo'}) - response = self.put(self.url + str(model.id), data=data) - self.assertEqual(response.status_code, 400) - - def test_put_client_id(self): - """Tests /project/ PUT with a client id. It should 400.""" - model = self.env.project - data = model.get_dictionary() - data.update({'client_id': self.env.client.id}) - response = self.put(self.url + str(model.id), data=data) - self.assertEqual(response.status_code, 400) - - def test_put_place_id(self): - """Tests /project/ PUT with a place id. It should 400.""" - model = self.env.project - data = model.get_dictionary() - data.update({'place_id': self.env.place.id}) - response = self.put(self.url + str(model.id), data=data) - self.assertEqual(response.status_code, 400) - - def test_delete(self): - """Tests /project/ DELETE. It should 405.""" - model = self.env.project - response = self.delete(self.url + str(model.id)) - self.assertEqual(response.status_code, 405) diff --git a/app/views/project.py b/app/views/project.py index 9764266..3f090c3 100644 --- a/app/views/project.py +++ b/app/views/project.py @@ -2,16 +2,47 @@ from werkzeug.exceptions import MethodNotAllowed from flask import request -from app.views.base import RestView +from app.views.base import UnprotectedRestView from app.controllers.project import ProjectController +from app.permissions.application import app_need, RoleNeed +from app.permissions.auth import auth_need, standard_login_need -class ProjectView(RestView): + +class ProjectView(UnprotectedRestView): """The project view.""" def get_controller(self): """Return an instance of the project controller.""" return ProjectController() + @standard_login_need + def index(self): + return super(ProjectView, self).index() + + @standard_login_need + def get(self, id_): + return super(ProjectView, self).get(id_) + + def post(self): + """Check for an application and either a user or an application with + the project_post_salesforce role (and the salesforce form) before + posting a project. + """ + form = request.args.get('form') + + need = app_need + if form == 'salesforce': + need &= RoleNeed('project_post_salesforce') + else: + need &= auth_need + + with need: + return super(ProjectView, self).post() + + @standard_login_need + def put(self, id_): + return super(ProjectView, self).put(id_) + def delete(self, id_): """Not implemented""" raise MethodNotAllowed( -- GitLab