2015-04-07 12:42:50 -03:00
|
|
|
import os
|
2015-05-20 12:14:38 -03:00
|
|
|
import json
|
2016-01-26 15:34:56 +01:00
|
|
|
from bson import ObjectId
|
|
|
|
from datetime import datetime
|
2015-10-20 23:52:18 +02:00
|
|
|
import bugsnag
|
|
|
|
from bugsnag.flask import handle_exceptions
|
2016-02-10 16:13:07 +01:00
|
|
|
from algoliasearch import algoliasearch
|
2015-05-18 11:42:17 -03:00
|
|
|
from flask import g
|
|
|
|
from flask import request
|
2015-09-08 15:06:45 +02:00
|
|
|
from flask import url_for
|
2015-10-11 22:20:18 +02:00
|
|
|
from flask import abort
|
2016-01-26 15:34:56 +01:00
|
|
|
from eve import Eve
|
2016-01-26 18:26:18 +01:00
|
|
|
from eve.auth import TokenAuth
|
2016-01-26 15:34:56 +01:00
|
|
|
from eve.io.mongo import Validator
|
2015-05-20 12:14:38 -03:00
|
|
|
|
2015-04-14 12:04:50 -03:00
|
|
|
RFC1123_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
|
|
|
|
|
2016-01-26 18:26:18 +01:00
|
|
|
class NewAuth(TokenAuth):
|
|
|
|
def check_auth(self, token, allowed_roles, resource, method):
|
|
|
|
if not token:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
validate_token()
|
|
|
|
return True
|
|
|
|
|
2015-03-10 11:38:57 +01:00
|
|
|
class ValidateCustomFields(Validator):
|
2015-10-10 16:27:02 +02:00
|
|
|
def convert_properties(self, properties, node_schema):
|
2015-10-08 09:24:34 +02:00
|
|
|
for prop in node_schema:
|
|
|
|
if not prop in properties:
|
|
|
|
continue
|
|
|
|
schema_prop = node_schema[prop]
|
|
|
|
prop_type = schema_prop['type']
|
|
|
|
if prop_type == 'dict':
|
2015-10-10 16:27:02 +02:00
|
|
|
properties[prop] = self.convert_properties(
|
2015-10-08 09:24:34 +02:00
|
|
|
properties[prop], schema_prop['schema'])
|
|
|
|
if prop_type == 'list':
|
|
|
|
if properties[prop] in ['', '[]']:
|
|
|
|
properties[prop] = []
|
|
|
|
for k, val in enumerate(properties[prop]):
|
|
|
|
if not 'schema' in schema_prop:
|
|
|
|
continue
|
|
|
|
item_schema = {'item': schema_prop['schema']}
|
|
|
|
item_prop = {'item': properties[prop][k]}
|
2015-10-10 16:27:02 +02:00
|
|
|
properties[prop][k] = self.convert_properties(
|
2015-10-08 09:24:34 +02:00
|
|
|
item_prop, item_schema)['item']
|
|
|
|
# Convert datetime string to RFC1123 datetime
|
|
|
|
elif prop_type == 'datetime':
|
|
|
|
prop_val = properties[prop]
|
|
|
|
properties[prop] = datetime.strptime(prop_val, RFC1123_DATE_FORMAT)
|
|
|
|
elif prop_type == 'objectid':
|
|
|
|
prop_val = properties[prop]
|
|
|
|
if prop_val:
|
|
|
|
properties[prop] = ObjectId(prop_val)
|
|
|
|
else:
|
|
|
|
properties[prop] = None
|
|
|
|
|
|
|
|
return properties
|
|
|
|
|
2015-03-11 16:03:19 +01:00
|
|
|
def _validate_valid_properties(self, valid_properties, field, value):
|
2016-01-25 16:32:50 +01:00
|
|
|
projects_collection = app.data.driver.db['projects']
|
|
|
|
lookup = {'_id': ObjectId(self.document['project'])}
|
|
|
|
project = projects_collection.find_one(lookup)
|
|
|
|
node_type = next(
|
|
|
|
(item for item in project['node_types'] if item.get('name') \
|
|
|
|
and item['name'] == self.document['node_type']), None)
|
2015-04-15 10:25:31 -03:00
|
|
|
try:
|
2015-10-08 09:24:34 +02:00
|
|
|
value = self.convert_properties(value, node_type['dyn_schema'])
|
2015-04-15 11:51:55 -03:00
|
|
|
except Exception, e:
|
|
|
|
print ("Error converting: {0}".format(e))
|
2015-04-14 12:04:50 -03:00
|
|
|
|
2015-03-11 16:03:19 +01:00
|
|
|
v = Validator(node_type['dyn_schema'])
|
|
|
|
val = v.validate(value)
|
2015-04-15 10:25:31 -03:00
|
|
|
|
2015-03-11 16:03:19 +01:00
|
|
|
if val:
|
|
|
|
return True
|
|
|
|
else:
|
2015-04-15 10:25:31 -03:00
|
|
|
try:
|
|
|
|
print (val.errors)
|
|
|
|
except:
|
|
|
|
pass
|
2015-03-27 15:42:28 +01:00
|
|
|
self._error(
|
|
|
|
field, "Error validating properties")
|
2015-03-11 16:03:19 +01:00
|
|
|
|
2015-10-08 10:26:22 +02:00
|
|
|
# We specify a settings.py file because when running on wsgi we can't detect it
|
2016-01-26 15:34:56 +01:00
|
|
|
# automatically. The default path (which works in Docker) can be overriden with
|
2015-10-08 10:26:22 +02:00
|
|
|
# an env variable.
|
2015-10-21 15:37:00 +02:00
|
|
|
settings_path = os.environ.get('EVE_SETTINGS', '/data/git/pillar/pillar/settings.py')
|
2015-10-11 22:20:18 +02:00
|
|
|
app = Eve(settings=settings_path, validator=ValidateCustomFields, auth=NewAuth)
|
2015-05-20 12:14:38 -03:00
|
|
|
|
2015-09-10 12:47:29 +02:00
|
|
|
import config
|
|
|
|
app.config.from_object(config.Deployment)
|
2015-09-11 15:04:25 +02:00
|
|
|
|
2015-10-20 23:52:18 +02:00
|
|
|
bugsnag.configure(
|
|
|
|
api_key = app.config['BUGSNAG_API_KEY'],
|
2015-10-21 15:37:00 +02:00
|
|
|
project_root = "/data/git/pillar/pillar",
|
2015-10-20 23:52:18 +02:00
|
|
|
)
|
|
|
|
handle_exceptions(app)
|
2016-01-07 20:06:25 +01:00
|
|
|
|
2016-02-10 16:13:07 +01:00
|
|
|
# Algolia search
|
|
|
|
if 'ALGOLIA_USER' in app.config:
|
|
|
|
client = algoliasearch.Client(
|
|
|
|
app.config['ALGOLIA_USER'],
|
|
|
|
app.config['ALGOLIA_API_KEY'])
|
|
|
|
algolia_index_users = client.init_index(app.config['ALGOLIA_INDEX_USERS'])
|
|
|
|
else:
|
|
|
|
algolia_index_users = None
|
|
|
|
|
2016-01-26 18:26:18 +01:00
|
|
|
from application.utils.authentication import validate_token
|
2016-01-26 15:34:56 +01:00
|
|
|
from application.utils.authorization import check_permissions
|
|
|
|
from application.utils.cdn import hash_file_path
|
|
|
|
from application.utils.gcs import GoogleCloudStorageBucket
|
|
|
|
from application.utils.gcs import update_file_name
|
2016-02-10 16:13:07 +01:00
|
|
|
from application.utils.algolia import algolia_index_user_save
|
2015-10-11 22:20:18 +02:00
|
|
|
|
|
|
|
|
2015-10-20 11:38:12 +02:00
|
|
|
def before_returning_item_permissions(response):
|
2015-10-11 22:20:18 +02:00
|
|
|
# Run validation process, since GET on nodes entry point is public
|
|
|
|
validate_token()
|
2015-10-19 19:09:32 +02:00
|
|
|
if not check_permissions(response, 'GET', append_allowed_methods=True):
|
|
|
|
return abort(403)
|
2015-10-11 22:20:18 +02:00
|
|
|
|
2015-10-20 11:38:12 +02:00
|
|
|
def before_returning_resource_permissions(response):
|
2015-10-15 16:12:46 +02:00
|
|
|
for item in response['_items']:
|
|
|
|
validate_token()
|
2015-10-19 19:09:32 +02:00
|
|
|
check_permissions(item, 'GET', append_allowed_methods=True)
|
|
|
|
|
2015-10-11 22:20:18 +02:00
|
|
|
def before_replacing_node(item, original):
|
|
|
|
check_permissions(original, 'PUT')
|
2016-01-07 20:06:25 +01:00
|
|
|
update_file_name(item)
|
2015-10-11 22:20:18 +02:00
|
|
|
|
|
|
|
def before_inserting_nodes(items):
|
2015-10-29 19:10:53 +01:00
|
|
|
"""Before inserting a node in the collection we check if the user is allowed
|
|
|
|
and we append the project id to it.
|
|
|
|
"""
|
|
|
|
nodes_collection = app.data.driver.db['nodes']
|
|
|
|
def find_parent_project(node):
|
|
|
|
"""Recursive function that finds the ultimate parent of a node."""
|
|
|
|
if node and 'parent' in node:
|
|
|
|
parent = nodes_collection.find_one({'_id': node['parent']})
|
|
|
|
return find_parent_project(parent)
|
|
|
|
if node:
|
|
|
|
return node
|
|
|
|
else:
|
|
|
|
return None
|
2015-10-11 22:20:18 +02:00
|
|
|
for item in items:
|
|
|
|
check_permissions(item, 'POST')
|
2015-11-06 16:09:54 +01:00
|
|
|
if 'parent' in item and 'project' not in item:
|
2015-10-29 19:10:53 +01:00
|
|
|
parent = nodes_collection.find_one({'_id': item['parent']})
|
|
|
|
project = find_parent_project(parent)
|
|
|
|
if project:
|
|
|
|
item['project'] = project['_id']
|
2015-10-11 22:20:18 +02:00
|
|
|
|
2015-12-01 19:02:29 +01:00
|
|
|
def item_parse_attachments(response):
|
2015-11-16 12:32:42 +01:00
|
|
|
"""Before returning a response, check if the 'attachments' property is
|
|
|
|
defined. If yes, load the file (for the moment only images) in the required
|
|
|
|
variation, get the link and build a Markdown representation. Search in the
|
|
|
|
'field' specified in the attachmentand replace the 'slug' tag with the
|
|
|
|
generated link.
|
|
|
|
"""
|
|
|
|
if 'properties' in response and 'attachments' in response['properties']:
|
|
|
|
files_collection = app.data.driver.db['files']
|
|
|
|
for field in response['properties']['attachments']:
|
|
|
|
for attachment in response['properties']['attachments']:
|
2015-12-01 18:23:33 +01:00
|
|
|
# Make a list from the property path
|
|
|
|
field_name_path = attachment['field'].split('.')
|
|
|
|
# This currently allow to access only properties inside of
|
|
|
|
# the properties property
|
|
|
|
if len(field_name_path) > 1:
|
|
|
|
field_content = response[field_name_path[0]][field_name_path[1]]
|
|
|
|
# This is for the "normal" first level property
|
|
|
|
else:
|
|
|
|
field_content = response[field_name_path[0]]
|
2015-11-16 12:32:42 +01:00
|
|
|
for f in attachment['files']:
|
|
|
|
slug = f['slug']
|
|
|
|
slug_tag = "[{0}]".format(slug)
|
|
|
|
f = files_collection.find_one({'_id': f['file']})
|
|
|
|
size = f['size'] if 'size' in f else 'l'
|
2015-11-25 16:16:09 +01:00
|
|
|
# Get the correc variation from the file
|
|
|
|
thumbnail = next((item for item in f['variations'] if
|
|
|
|
item['size'] == size), None)
|
|
|
|
l = generate_link(f['backend'], thumbnail['file_path'], str(f['project']))
|
2015-11-16 12:32:42 +01:00
|
|
|
# Build Markdown img string
|
|
|
|
l = ''.format(slug, l, f['name'])
|
2015-11-25 16:16:09 +01:00
|
|
|
# Parse the content of the file and replace the attachment
|
|
|
|
# tag with the actual image link
|
2015-11-16 16:53:42 +01:00
|
|
|
field_content = field_content.replace(slug_tag, l)
|
2015-12-01 18:23:33 +01:00
|
|
|
# Apply the parsed value back to the property. See above for
|
|
|
|
# clarifications on how this is done.
|
|
|
|
if len(field_name_path) > 1:
|
|
|
|
response[field_name_path[0]][field_name_path[1]] = field_content
|
|
|
|
else:
|
|
|
|
response[field_name_path[0]] = field_content
|
2015-11-16 12:32:42 +01:00
|
|
|
|
2015-12-01 19:02:29 +01:00
|
|
|
def resource_parse_attachments(response):
|
|
|
|
for item in response['_items']:
|
|
|
|
item_parse_attachments(item)
|
2015-10-12 18:27:16 +02:00
|
|
|
|
2016-01-26 18:26:18 +01:00
|
|
|
def project_node_type_has_method(response):
|
|
|
|
"""Check for a specific request arg, and check generate the allowed_methods
|
|
|
|
list for the required node_type.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
node_type_name = request.args['node_type']
|
|
|
|
except KeyError:
|
|
|
|
return
|
|
|
|
# Proceed only node_type has been requested
|
|
|
|
if node_type_name:
|
|
|
|
# Look up the node type in the project document
|
|
|
|
node_type = next(
|
|
|
|
(item for item in response['node_types'] if item.get('name') \
|
|
|
|
and item['name'] == node_type_name), None)
|
|
|
|
if not node_type:
|
|
|
|
return abort(404)
|
|
|
|
# Check permissions and append the allowed_methods to the node_type
|
|
|
|
if not check_permissions(node_type, 'GET', append_allowed_methods=True):
|
|
|
|
return abort(403)
|
|
|
|
|
|
|
|
|
2015-10-20 11:38:12 +02:00
|
|
|
app.on_fetched_item_nodes += before_returning_item_permissions
|
2015-12-01 19:02:29 +01:00
|
|
|
app.on_fetched_item_nodes += item_parse_attachments
|
2015-10-20 11:38:12 +02:00
|
|
|
app.on_fetched_resource_nodes += before_returning_resource_permissions
|
2015-12-02 11:52:20 +01:00
|
|
|
app.on_fetched_resource_nodes += resource_parse_attachments
|
2015-10-20 11:38:12 +02:00
|
|
|
app.on_fetched_item_node_types += before_returning_item_permissions
|
|
|
|
app.on_fetched_resource_node_types += before_returning_resource_permissions
|
2015-10-11 22:20:18 +02:00
|
|
|
app.on_replace_nodes += before_replacing_node
|
|
|
|
app.on_insert_nodes += before_inserting_nodes
|
2016-01-25 16:32:50 +01:00
|
|
|
app.on_fetched_item_projects += before_returning_item_permissions
|
2016-01-26 18:26:18 +01:00
|
|
|
app.on_fetched_item_projects += project_node_type_has_method
|
2016-01-25 16:32:50 +01:00
|
|
|
app.on_fetched_resource_projects += before_returning_resource_permissions
|
2015-10-11 22:20:18 +02:00
|
|
|
|
2015-05-20 12:14:38 -03:00
|
|
|
def post_GET_user(request, payload):
|
|
|
|
json_data = json.loads(payload.data)
|
2015-07-09 17:52:18 +02:00
|
|
|
# Check if we are querying the users endpoint (instead of the single user)
|
|
|
|
if json_data.get('_id') is None:
|
|
|
|
return
|
2015-10-11 22:20:18 +02:00
|
|
|
# json_data['computed_permissions'] = \
|
|
|
|
# compute_permissions(json_data['_id'], app.data.driver)
|
2015-05-20 12:14:38 -03:00
|
|
|
payload.data = json.dumps(json_data)
|
|
|
|
|
2016-02-10 16:13:07 +01:00
|
|
|
def after_replacing_user(item, original):
|
|
|
|
"""Push an update to the Algolia index when a user item is updated"""
|
|
|
|
algolia_index_user_save(item)
|
|
|
|
|
2015-05-20 12:14:38 -03:00
|
|
|
app.on_post_GET_users += post_GET_user
|
2016-02-10 16:13:07 +01:00
|
|
|
app.on_replace_users += after_replacing_user
|
2015-05-20 12:14:38 -03:00
|
|
|
|
2015-09-24 15:45:57 +02:00
|
|
|
from modules.file_storage import process_file
|
2015-11-05 18:47:36 +01:00
|
|
|
from modules.file_storage import delete_file
|
2015-09-24 15:45:57 +02:00
|
|
|
|
|
|
|
def post_POST_files(request, payload):
|
|
|
|
"""After an file object has been created, we do the necessary processing
|
|
|
|
and further update it.
|
|
|
|
"""
|
|
|
|
process_file(request.get_json())
|
|
|
|
|
|
|
|
app.on_post_POST_files += post_POST_files
|
|
|
|
|
2016-01-07 20:06:25 +01:00
|
|
|
|
2015-09-08 15:06:45 +02:00
|
|
|
# Hook to check the backend of a file resource, to build an appropriate link
|
|
|
|
# that can be used by the client to retrieve the actual file.
|
2015-11-05 18:47:36 +01:00
|
|
|
def generate_link(backend, file_path, project_id=None):
|
2015-11-04 12:59:08 +01:00
|
|
|
if backend == 'gcs':
|
|
|
|
storage = GoogleCloudStorageBucket(project_id)
|
2015-11-05 18:47:36 +01:00
|
|
|
blob = storage.Get(file_path)
|
2015-11-10 17:56:43 +01:00
|
|
|
link = None if not blob else blob['signed_url']
|
2015-11-04 12:59:08 +01:00
|
|
|
elif backend == 'pillar':
|
2015-11-09 17:41:54 +01:00
|
|
|
link = url_for('file_storage.index', file_name=file_path, _external=True,
|
|
|
|
_scheme=app.config['SCHEME'])
|
2015-09-08 15:06:45 +02:00
|
|
|
elif backend == 'cdnsun':
|
2015-11-05 18:47:36 +01:00
|
|
|
link = hash_file_path(file_path, None)
|
2015-09-08 15:06:45 +02:00
|
|
|
else:
|
|
|
|
link = None
|
|
|
|
return link
|
|
|
|
|
|
|
|
def before_returning_file(response):
|
2015-11-04 12:59:08 +01:00
|
|
|
# TODO: add project id to all files
|
|
|
|
project_id = None if 'project' not in response else str(response['project'])
|
2015-11-25 16:16:09 +01:00
|
|
|
response['link'] = generate_link(
|
|
|
|
response['backend'], response['file_path'], project_id)
|
|
|
|
if 'variations' in response:
|
|
|
|
for variation in response['variations']:
|
|
|
|
variation['link'] = generate_link(
|
|
|
|
response['backend'], variation['file_path'], project_id)
|
2015-09-08 15:06:45 +02:00
|
|
|
|
|
|
|
def before_returning_files(response):
|
|
|
|
for item in response['_items']:
|
2015-11-04 12:59:08 +01:00
|
|
|
# TODO: add project id to all files
|
|
|
|
project_id = None if 'project' not in item else str(item['project'])
|
2015-11-05 18:47:36 +01:00
|
|
|
item['link'] = generate_link(item['backend'], item['file_path'], project_id)
|
2015-09-08 15:06:45 +02:00
|
|
|
|
|
|
|
|
|
|
|
app.on_fetched_item_files += before_returning_file
|
|
|
|
app.on_fetched_resource_files += before_returning_files
|
2015-04-24 11:57:40 +02:00
|
|
|
|
2015-11-05 18:47:36 +01:00
|
|
|
|
|
|
|
def before_deleting_file(item):
|
|
|
|
delete_file(item)
|
|
|
|
|
|
|
|
app.on_delete_item_files += before_deleting_file
|
|
|
|
|
2015-09-24 15:45:57 +02:00
|
|
|
# The file_storage module needs app to be defined
|
|
|
|
from modules.file_storage import file_storage
|
|
|
|
#from modules.file_storage.serve import *
|
|
|
|
app.register_blueprint(file_storage, url_prefix='/storage')
|