Renaming the project to Pillar.
This commit is contained in:
295
pillar/application/__init__.py
Normal file
295
pillar/application/__init__.py
Normal file
@@ -0,0 +1,295 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
from eve import Eve
|
||||
|
||||
# import random
|
||||
# import string
|
||||
|
||||
from eve.auth import TokenAuth
|
||||
from eve.auth import BasicAuth
|
||||
from eve.io.mongo import Validator
|
||||
from eve.methods.post import post_internal
|
||||
from bson import ObjectId
|
||||
|
||||
from flask import g
|
||||
from flask import request
|
||||
|
||||
from pre_hooks import pre_GET
|
||||
from pre_hooks import pre_PUT
|
||||
from pre_hooks import pre_PATCH
|
||||
from pre_hooks import pre_POST
|
||||
from pre_hooks import pre_DELETE
|
||||
from pre_hooks import check_permissions
|
||||
from pre_hooks import compute_permissions
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
RFC1123_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
|
||||
|
||||
|
||||
class SystemUtility():
|
||||
def __new__(cls, *args, **kwargs):
|
||||
raise TypeError("Base class may not be instantiated")
|
||||
|
||||
@staticmethod
|
||||
def blender_id_endpoint():
|
||||
"""Gets the endpoint for the authentication API. If the env variable
|
||||
is defined, it's possible to override the (default) production address.
|
||||
"""
|
||||
return os.environ.get(
|
||||
'BLENDER_ID_ENDPOINT', "https://www.blender.org/id")
|
||||
|
||||
|
||||
def validate(token):
|
||||
"""Validate a Token against Blender ID server
|
||||
"""
|
||||
import requests
|
||||
payload = dict(
|
||||
token=token)
|
||||
try:
|
||||
r = requests.post("{0}/u/validate_token".format(
|
||||
SystemUtility.blender_id_endpoint()), data=payload)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
raise e
|
||||
|
||||
if r.status_code == 200:
|
||||
message = r.json()['message']
|
||||
valid = r.json()['valid']
|
||||
user = r.json()['user']
|
||||
else:
|
||||
message = ""
|
||||
valid = False
|
||||
user = None
|
||||
return dict(valid=valid, message=message, user=user)
|
||||
|
||||
|
||||
def validate_token():
|
||||
token = request.authorization.username
|
||||
tokens = app.data.driver.db['tokens']
|
||||
users = app.data.driver.db['users']
|
||||
lookup = {'token': token, 'expire_time': {"$gt": datetime.now()}}
|
||||
dbtoken = tokens.find_one(lookup)
|
||||
if not dbtoken:
|
||||
validation = validate(token)
|
||||
if validation['valid']:
|
||||
email = validation['user']['email']
|
||||
dbuser = users.find_one({'email': email})
|
||||
tmpname = email.split('@')[0]
|
||||
if not dbuser:
|
||||
user_data = {
|
||||
'first_name': tmpname,
|
||||
'last_name': tmpname,
|
||||
'email': email,
|
||||
'role': ['admin'],
|
||||
}
|
||||
r = post_internal('users', user_data)
|
||||
user_id = r[0]["_id"]
|
||||
else:
|
||||
user_id = dbuser['_id']
|
||||
|
||||
token_data = {
|
||||
'user': user_id,
|
||||
'token': token,
|
||||
'expire_time': datetime.now() + timedelta(hours=1)
|
||||
}
|
||||
post_internal('tokens', token_data)
|
||||
return token_data
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
token_data = {
|
||||
'user': dbtoken['user'],
|
||||
'token': dbtoken['token'],
|
||||
'expire_time': dbtoken['expire_time']
|
||||
}
|
||||
return token_data
|
||||
|
||||
|
||||
class TokensAuth(TokenAuth):
|
||||
|
||||
def check_auth(self, token, allowed_roles, resource, method):
|
||||
if not token:
|
||||
return False
|
||||
|
||||
validate_token()
|
||||
|
||||
# if dbtoken:
|
||||
# check_permissions(dbtoken['user'])
|
||||
# return True
|
||||
|
||||
# return validation['valid']
|
||||
return True
|
||||
"""
|
||||
users = app.data.driver.db['users']
|
||||
lookup = {'first_name': token['username']}
|
||||
if allowed_roles:
|
||||
lookup['role'] = {'$in': allowed_roles}
|
||||
user = users.find_one(lookup)
|
||||
if not user:
|
||||
return False
|
||||
return token
|
||||
"""
|
||||
|
||||
|
||||
class BasicsAuth(BasicAuth):
|
||||
def check_auth(self, username, password, allowed_roles, resource, method):
|
||||
# return username == 'admin' and password == 'secret'
|
||||
return True
|
||||
|
||||
|
||||
class CustomTokenAuth(BasicsAuth):
|
||||
"""Switch between Basic and Token auth"""
|
||||
def __init__(self):
|
||||
self.token_auth = TokensAuth()
|
||||
self.authorized_protected = BasicsAuth.authorized
|
||||
|
||||
def authorized(self, allowed_roles, resource, method):
|
||||
# if resource == 'tokens':
|
||||
if False:
|
||||
return self.authorized_protected(
|
||||
self, allowed_roles, resource, method)
|
||||
else:
|
||||
return self.token_auth.authorized(allowed_roles, resource, method)
|
||||
|
||||
def authorized_protected(self):
|
||||
pass
|
||||
|
||||
def convert_properties(properties, node_schema):
|
||||
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':
|
||||
properties[prop] = convert_properties(
|
||||
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]}
|
||||
properties[prop][k] = convert_properties(
|
||||
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
|
||||
|
||||
|
||||
class ValidateCustomFields(Validator):
|
||||
def _validate_valid_properties(self, valid_properties, field, value):
|
||||
node_types = app.data.driver.db['node_types']
|
||||
lookup = {}
|
||||
lookup['_id'] = ObjectId(self.document['node_type'])
|
||||
node_type = node_types.find_one(lookup)
|
||||
|
||||
try:
|
||||
value = convert_properties(value, node_type['dyn_schema'])
|
||||
except Exception, e:
|
||||
print ("Error converting: {0}".format(e))
|
||||
#print (value)
|
||||
|
||||
v = Validator(node_type['dyn_schema'])
|
||||
val = v.validate(value)
|
||||
|
||||
if val:
|
||||
return True
|
||||
else:
|
||||
try:
|
||||
print (val.errors)
|
||||
except:
|
||||
pass
|
||||
self._error(
|
||||
field, "Error validating properties")
|
||||
|
||||
|
||||
def post_item(entry, data):
|
||||
return post_internal(entry, data)
|
||||
|
||||
|
||||
app = Eve(validator=ValidateCustomFields, auth=CustomTokenAuth)
|
||||
|
||||
|
||||
def global_validation():
|
||||
setattr(g, 'token_data', validate_token())
|
||||
setattr(g, 'validate', validate(g.get('token_data')['token']))
|
||||
check_permissions(g.get('token_data')['user'], app.data.driver)
|
||||
|
||||
|
||||
def pre_GET_nodes(request, lookup):
|
||||
# Only get allowed documents
|
||||
global_validation()
|
||||
# print ("Get")
|
||||
# print ("Owner: {0}".format(g.get('owner_permissions')))
|
||||
# print ("World: {0}".format(g.get('world_permissions')))
|
||||
return pre_GET(request, lookup, app.data.driver)
|
||||
|
||||
|
||||
def pre_PUT_nodes(request, lookup):
|
||||
# Only Update allowed documents
|
||||
global_validation()
|
||||
# print ("Put")
|
||||
# print ("Owner: {0}".format(g.get('owner_permissions')))
|
||||
# print ("World: {0}".format(g.get('world_permissions')))
|
||||
return pre_PUT(request, lookup, app.data.driver)
|
||||
|
||||
|
||||
def pre_PATCH_nodes(request):
|
||||
return pre_PATCH(request, app.data.driver)
|
||||
|
||||
|
||||
def pre_POST_nodes(request):
|
||||
global_validation()
|
||||
# print ("Post")
|
||||
# print ("World: {0}".format(g.get('world_permissions')))
|
||||
# print ("Group: {0}".format(g.get('groups_permissions')))
|
||||
return pre_POST(request, app.data.driver)
|
||||
|
||||
|
||||
def pre_DELETE_nodes(request, lookup):
|
||||
# Only Delete allowed documents
|
||||
global_validation()
|
||||
# print ("Delete")
|
||||
# print ("Owner: {0}".format(type_owner_permissions))
|
||||
# print ("World: {0}".format(type_world_permissions))
|
||||
# print ("Groups: {0}".format(type_groups_permissions))
|
||||
return pre_DELETE(request, lookup, app.data.driver)
|
||||
|
||||
|
||||
app.on_pre_GET_nodes += pre_GET_nodes
|
||||
app.on_pre_POST_nodes += pre_POST_nodes
|
||||
app.on_pre_PATCH_nodes += pre_PATCH_nodes
|
||||
app.on_pre_PUT_nodes += pre_PUT_nodes
|
||||
app.on_pre_DELETE_nodes += pre_DELETE_nodes
|
||||
|
||||
|
||||
def post_GET_user(request, payload):
|
||||
json_data = json.loads(payload.data)
|
||||
# Check if we are querying the users endpoint (instead of the single user)
|
||||
if json_data.get('_id') is None:
|
||||
return
|
||||
json_data['computed_permissions'] = \
|
||||
compute_permissions(json_data['_id'], app.data.driver)
|
||||
payload.data = json.dumps(json_data)
|
||||
|
||||
|
||||
app.on_post_GET_users += post_GET_user
|
||||
|
||||
|
||||
# The file_server module needs app to be defined
|
||||
from file_server import file_server
|
||||
app.register_blueprint(file_server, url_prefix='/file_server')
|
179
pillar/application/file_server.py
Normal file
179
pillar/application/file_server.py
Normal file
@@ -0,0 +1,179 @@
|
||||
import os
|
||||
import hashlib
|
||||
|
||||
from flask import Blueprint
|
||||
from flask import request
|
||||
|
||||
from application import app
|
||||
from application import post_item
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from bson import ObjectId
|
||||
|
||||
RFC1123_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
|
||||
|
||||
|
||||
file_server = Blueprint('file_server', __name__,
|
||||
template_folder='templates',
|
||||
static_folder='static/storage')
|
||||
|
||||
|
||||
def hashfile(afile, hasher, blocksize=65536):
|
||||
buf = afile.read(blocksize)
|
||||
while len(buf) > 0:
|
||||
hasher.update(buf)
|
||||
buf = afile.read(blocksize)
|
||||
return hasher.hexdigest()
|
||||
|
||||
|
||||
@file_server.route('/build_previews/<file_name>')
|
||||
def build_previews(file_name=None):
|
||||
from pymongo import MongoClient
|
||||
|
||||
# Get File
|
||||
client = MongoClient()
|
||||
db = client.eve
|
||||
file_ = db.files.find({"path": "{0}".format(file_name)})
|
||||
file_ = file_[0]
|
||||
user = file_['user']
|
||||
|
||||
folder_name = file_name[:2]
|
||||
file_folder_path = os.path.join(app.config['FILE_STORAGE'],
|
||||
folder_name)
|
||||
# The original file exists?
|
||||
file_path = os.path.join(file_folder_path, file_name)
|
||||
if not os.path.isfile(file_path):
|
||||
return "", 404
|
||||
|
||||
sizes = ["xs", "s", "m", "l", "xl"]
|
||||
size_dict = {
|
||||
"xs": (32, 32),
|
||||
"s": (64, 64),
|
||||
"m": (128, 128),
|
||||
"l": (640, 480),
|
||||
"xl": (1024, 768)
|
||||
}
|
||||
|
||||
# Generate
|
||||
preview_list = []
|
||||
for size in sizes:
|
||||
resized_file_name = "{0}_{1}".format(size, file_name)
|
||||
resized_file_path = os.path.join(
|
||||
app.config['FILE_STORAGE'],
|
||||
resized_file_name)
|
||||
|
||||
# Create thumbnail
|
||||
#if not os.path.isfile(resized_file_path):
|
||||
try:
|
||||
im = Image.open(file_path)
|
||||
except IOError:
|
||||
return "", 500
|
||||
im.thumbnail(size_dict[size])
|
||||
width = im.size[0]
|
||||
height = im.size[1]
|
||||
format = im.format.lower()
|
||||
try:
|
||||
im.save(resized_file_path)
|
||||
except IOError:
|
||||
return "", 500
|
||||
|
||||
# file_static_path = os.path.join("", folder_name, size, file_name)
|
||||
picture_file_file = open(resized_file_path, 'rb')
|
||||
hash_ = hashfile(picture_file_file, hashlib.md5())
|
||||
name = "{0}{1}".format(hash_,
|
||||
os.path.splitext(file_name)[1])
|
||||
picture_file_file.close()
|
||||
description = "Thumbnail {0} for file {1}".format(
|
||||
size, file_name)
|
||||
|
||||
prop = {}
|
||||
prop['name'] = resized_file_name
|
||||
prop['description'] = description
|
||||
prop['user'] = user
|
||||
# Preview properties:
|
||||
prop['is_preview'] = True
|
||||
prop['size'] = size
|
||||
prop['format'] = format
|
||||
prop['width'] = width
|
||||
prop['height'] = height
|
||||
# TODO set proper contentType and length
|
||||
prop['contentType'] = 'image/png'
|
||||
prop['length'] = 0
|
||||
prop['uploadDate'] = datetime.strftime(
|
||||
datetime.now(), RFC1123_DATE_FORMAT)
|
||||
prop['md5'] = hash_
|
||||
prop['filename'] = resized_file_name
|
||||
prop['backend'] = 'attract'
|
||||
prop['path'] = name
|
||||
|
||||
entry = post_item ('files', prop)
|
||||
if entry[0]['_status'] == 'ERR':
|
||||
entry = db.files.find({"path": name})
|
||||
|
||||
entry = entry[0]
|
||||
prop['_id'] = entry['_id']
|
||||
|
||||
new_folder_name = name[:2]
|
||||
new_folder_path = os.path.join(
|
||||
app.config['FILE_STORAGE'],
|
||||
new_folder_name)
|
||||
new_file_path = os.path.join(
|
||||
new_folder_path,
|
||||
name)
|
||||
|
||||
if not os.path.exists(new_folder_path):
|
||||
os.makedirs(new_folder_path)
|
||||
|
||||
# Clean up temporary file
|
||||
os.rename(
|
||||
resized_file_path,
|
||||
new_file_path)
|
||||
|
||||
preview_list.append(str(prop['_id']))
|
||||
#print (new_file_path)
|
||||
|
||||
# Add previews to file
|
||||
previews = []
|
||||
try:
|
||||
previews = file_['previews']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
preview_list = preview_list + previews
|
||||
|
||||
#print (previews)
|
||||
#print (preview_list)
|
||||
#print (file_['_id'])
|
||||
|
||||
file_ = db.files.update(
|
||||
{"_id": ObjectId(file_['_id'])},
|
||||
{"$set": {"previews": preview_list}}
|
||||
)
|
||||
|
||||
#print (file_)
|
||||
|
||||
return "", 200
|
||||
|
||||
|
||||
@file_server.route('/file', methods=['POST'])
|
||||
@file_server.route('/file/<file_name>')
|
||||
def index(file_name=None):
|
||||
#GET file
|
||||
if file_name:
|
||||
folder_name = file_name[:2]
|
||||
file_path = os.path.join("", folder_name, file_name)
|
||||
return file_server.send_static_file(file_path)
|
||||
#POST file
|
||||
file_name = request.form['name']
|
||||
folder_name = file_name[:2]
|
||||
file_folder_path = os.path.join(app.config['FILE_STORAGE'],
|
||||
folder_name)
|
||||
if not os.path.exists(file_folder_path):
|
||||
os.mkdir(file_folder_path)
|
||||
file_path = os.path.join(file_folder_path, file_name)
|
||||
request.files['data'].save(file_path)
|
||||
|
||||
return "{}", 200
|
214
pillar/application/pre_hooks.py
Normal file
214
pillar/application/pre_hooks.py
Normal file
@@ -0,0 +1,214 @@
|
||||
import json
|
||||
from flask import g
|
||||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
from bson import ObjectId
|
||||
|
||||
# from application import app
|
||||
|
||||
|
||||
def permissions_lookup(action, lookup):
|
||||
type_world_permissions = g.get('type_world_permissions')
|
||||
type_owner_permissions = g.get('type_owner_permissions')
|
||||
node_types = []
|
||||
# Get all node_types allowed by world:
|
||||
for per in type_world_permissions:
|
||||
if action in type_world_permissions[per]:
|
||||
node_types.append(str(per))
|
||||
# Get all nodes with node_type allowed by owner if user == owner
|
||||
owner_lookup = []
|
||||
for per in type_owner_permissions:
|
||||
if action in type_owner_permissions[per]:
|
||||
if action not in type_world_permissions[per]:
|
||||
# If one of the following is true
|
||||
# If node_type==node_type and user==user
|
||||
owner_lookup.append(
|
||||
{'$and': [{'node_type': str(per)},
|
||||
{'user': str(g.get('token_data')['user'])}]})
|
||||
lookup['$or'] = [{'node_type': {'$in': node_types}}]
|
||||
if len(owner_lookup) > 0:
|
||||
lookup['$or'].append({'$or': owner_lookup})
|
||||
return lookup
|
||||
|
||||
|
||||
def pre_GET(request, lookup, data_driver):
|
||||
action = 'GET'
|
||||
if 'token_type' not in lookup and '_id' not in request.view_args:
|
||||
# Is quering for all nodes (mixed types)
|
||||
lookup = permissions_lookup(action, lookup)
|
||||
else:
|
||||
# Is quering for one specific node
|
||||
if action not in g.get('world_permissions') and \
|
||||
action not in g.get('groups_permissions'):
|
||||
lookup['user'] = g.get('token_data')['user']
|
||||
# token_data = validate_token()
|
||||
# validate(token_data['token'])
|
||||
|
||||
# lookup["userr"] = "user"
|
||||
# print ("Lookup")
|
||||
# print (lookup)
|
||||
|
||||
|
||||
def pre_PUT(request, lookup, data_driver):
|
||||
action = 'UPDATE'
|
||||
if 'token_type' not in lookup and '_id' not in request.view_args:
|
||||
# Is updating all nodes (mixed types)
|
||||
lookup = permissions_lookup(action, lookup)
|
||||
else:
|
||||
# Is updating one specific node
|
||||
if action not in g.get('world_permissions') and \
|
||||
action not in g.get('groups_permissions'):
|
||||
lookup['user'] = g.get('token_data')['user']
|
||||
|
||||
# print ("Lookup")
|
||||
# print (lookup)
|
||||
|
||||
|
||||
def pre_PATCH(request, lookup, data_driver):
|
||||
print ("Patch")
|
||||
|
||||
|
||||
def pre_POST(request, data_driver):
|
||||
# Only Post allowed documents
|
||||
action = 'POST'
|
||||
print (g.get('type_groups_permissions'))
|
||||
# Is quering for one specific node
|
||||
if action not in g.get('world_permissions') and \
|
||||
action not in g.get('groups_permissions'):
|
||||
abort(403)
|
||||
|
||||
|
||||
def pre_DELETE(request, lookup, data_driver):
|
||||
type_world_permissions = g.get('type_world_permissions')
|
||||
type_owner_permissions = g.get('type_owner_permissions')
|
||||
type_groups_permissions = g.get('type_groups_permissions')
|
||||
action = 'DELETE'
|
||||
|
||||
if '_id' in lookup:
|
||||
nodes = data_driver.db['nodes']
|
||||
dbnode = nodes.find_one({'_id': ObjectId(lookup['_id'])})
|
||||
# print (dbnode.count())
|
||||
node_type = str(dbnode['node_type'])
|
||||
if g.get('token_data')['user'] == dbnode['user']:
|
||||
owner = True
|
||||
else:
|
||||
owner = False
|
||||
if action not in type_world_permissions[node_type] and \
|
||||
action not in type_groups_permissions[node_type]:
|
||||
if action not in type_owner_permissions[node_type]:
|
||||
print ("Abort1")
|
||||
abort(403)
|
||||
else:
|
||||
if not owner:
|
||||
print ("Abort2")
|
||||
abort(403)
|
||||
else:
|
||||
print ("Abort3")
|
||||
abort(403)
|
||||
|
||||
|
||||
def compute_permissions(user, data_driver):
|
||||
node_type = None
|
||||
dbnode = None
|
||||
owner_permissions = []
|
||||
world_permissions = []
|
||||
groups_permissions = []
|
||||
groups = data_driver.db['groups']
|
||||
users = data_driver.db['users']
|
||||
owner_group = groups.find_one({'name': 'owner'})
|
||||
world_group = groups.find_one({'name': 'world'})
|
||||
user_data = users.find_one({'_id': ObjectId(user)})
|
||||
# If is requesting a specific node
|
||||
try:
|
||||
uuid = request.path.split("/")[2]
|
||||
nodes = data_driver.db['nodes']
|
||||
lookup = {'_id': ObjectId(uuid)}
|
||||
dbnode = nodes.find_one(lookup)
|
||||
except IndexError:
|
||||
pass
|
||||
if dbnode:
|
||||
node_type = str(dbnode['node_type'])
|
||||
|
||||
json_data = None
|
||||
try:
|
||||
json_data = json.loads(request.data)
|
||||
except ValueError:
|
||||
pass
|
||||
if not node_type and json_data:
|
||||
if 'node_type' in json_data:
|
||||
node_type = json_data['node_type']
|
||||
|
||||
# Extract query lookup
|
||||
# which node_type is asking for?
|
||||
for arg in request.args:
|
||||
if arg == 'where':
|
||||
try:
|
||||
where = json.loads(request.args[arg])
|
||||
except ValueError:
|
||||
raise
|
||||
if where.get('node_type'):
|
||||
node_type = where.get('node_type')
|
||||
break
|
||||
|
||||
# Get and store permissions for that node_type
|
||||
type_owner_permissions = {}
|
||||
type_world_permissions = {}
|
||||
type_groups_permissions = {}
|
||||
type_mixed_permissions = {}
|
||||
|
||||
for per in owner_group['permissions']:
|
||||
type_owner_permissions[str(per['node_type'])] = per['permissions']
|
||||
if str(per['node_type']) == node_type:
|
||||
owner_permissions = per['permissions']
|
||||
|
||||
for per in world_group['permissions']:
|
||||
type_world_permissions[str(per['node_type'])] = per['permissions']
|
||||
if str(per['node_type']) == node_type:
|
||||
world_permissions = per['permissions']
|
||||
|
||||
# Adding empty permissions
|
||||
if str(per['node_type']) not in type_groups_permissions:
|
||||
type_groups_permissions[str(per['node_type'])] = []
|
||||
|
||||
type_mixed_permissions = type_world_permissions
|
||||
|
||||
groups_data = user_data.get('groups')
|
||||
if groups_data:
|
||||
for group in groups_data:
|
||||
group_data = groups.find_one({'_id': ObjectId(group)})
|
||||
for per in group_data['permissions']:
|
||||
type_groups_permissions[str(per['node_type'])] += \
|
||||
per['permissions']
|
||||
type_mixed_permissions[str(per['node_type'])] += \
|
||||
per['permissions']
|
||||
if str(per['node_type']) == node_type:
|
||||
groups_permissions = per['permissions']
|
||||
|
||||
return {
|
||||
'owner_permissions': owner_permissions,
|
||||
'world_permissions': world_permissions,
|
||||
'groups_permissions': groups_permissions,
|
||||
'type_owner_permissions': type_owner_permissions,
|
||||
'type_world_permissions': type_world_permissions,
|
||||
'type_groups_permissions': type_groups_permissions,
|
||||
'type_mixed_permissions': type_mixed_permissions
|
||||
}
|
||||
|
||||
|
||||
def check_permissions(user, data_driver):
|
||||
# Entry point should be nodes
|
||||
entry_point = request.path.split("/")[1]
|
||||
if entry_point != 'nodes':
|
||||
return
|
||||
|
||||
permissions = compute_permissions(user, data_driver)
|
||||
|
||||
# Store permission properties on global
|
||||
setattr(g, 'owner_permissions', permissions['owner_permissions'])
|
||||
setattr(g, 'world_permissions', permissions['world_permissions'])
|
||||
setattr(g, 'groups_permissions', permissions['groups_permissions'])
|
||||
setattr(g, 'type_owner_permissions', permissions['type_owner_permissions'])
|
||||
setattr(g, 'type_world_permissions', permissions['type_world_permissions'])
|
||||
setattr(g, 'type_groups_permissions',
|
||||
permissions['type_groups_permissions'])
|
3
pillar/application/static/.webassets-cache/.gitignore
vendored
Normal file
3
pillar/application/static/.webassets-cache/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Ignore everything but self
|
||||
*
|
||||
!.gitignore
|
0
pillar/application/static/placeholder
Normal file
0
pillar/application/static/placeholder
Normal file
28
pillar/config.py.example
Normal file
28
pillar/config.py.example
Normal file
@@ -0,0 +1,28 @@
|
||||
import os
|
||||
|
||||
class Config(object):
|
||||
# Configured for GMAIL
|
||||
MAIL_SERVER = ''
|
||||
MAIL_PORT = 465
|
||||
MAIL_USE_SSL = True
|
||||
MAIL_USERNAME = ''
|
||||
MAIL_PASSWORD = ''
|
||||
DEFAULT_MAIL_SENDER = ''
|
||||
|
||||
# Flask-Security setup
|
||||
SECURITY_LOGIN_WITHOUT_CONFIRMATION = True
|
||||
SECURITY_REGISTERABLE = True
|
||||
SECURITY_RECOVERABLE = True
|
||||
SECURITY_CHANGEABLE = True
|
||||
SECUIRTY_POST_LOGIN = '/'
|
||||
SECURITY_PASSWORD_HASH = ''
|
||||
SECURITY_PASSWORD_SALT = ''
|
||||
SECURITY_EMAIL_SENDER = ''
|
||||
|
||||
class Development(Config):
|
||||
SECRET_KEY = ''
|
||||
HOST = '0.0.0.0'
|
||||
PORT = 5000
|
||||
DEBUG = True
|
||||
FILE_STORAGE = '{0}/application/static/storage'.format(
|
||||
os.path.join(os.path.dirname(__file__)))
|
662
pillar/manage.py
Normal file
662
pillar/manage.py
Normal file
@@ -0,0 +1,662 @@
|
||||
import os
|
||||
from application import app
|
||||
from application import post_item
|
||||
from flask.ext.script import Manager
|
||||
|
||||
manager = Manager(app)
|
||||
|
||||
@manager.command
|
||||
def runserver():
|
||||
try:
|
||||
import config
|
||||
PORT = config.Development.PORT
|
||||
HOST = config.Development.HOST
|
||||
DEBUG = config.Development.DEBUG
|
||||
app.config['FILE_STORAGE'] = config.Development.FILE_STORAGE
|
||||
except ImportError:
|
||||
# Default settings
|
||||
PORT = 5000
|
||||
HOST = '0.0.0.0'
|
||||
DEBUG = True
|
||||
app.config['FILE_STORAGE'] = '{0}/application/static/storage'.format(
|
||||
os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
# Automatic creation of FILE_STORAGE path if it's missing
|
||||
if not os.path.exists(app.config['FILE_STORAGE']):
|
||||
os.makedirs(app.config['FILE_STORAGE'])
|
||||
|
||||
app.run(
|
||||
port=PORT,
|
||||
host=HOST,
|
||||
debug=DEBUG)
|
||||
|
||||
|
||||
@manager.command
|
||||
def clear_db():
|
||||
"""Wipes the database
|
||||
"""
|
||||
from pymongo import MongoClient
|
||||
|
||||
client = MongoClient()
|
||||
db = client.eve
|
||||
db.drop_collection('nodes')
|
||||
db.drop_collection('node_types')
|
||||
db.drop_collection('tokens')
|
||||
db.drop_collection('users')
|
||||
|
||||
|
||||
@manager.command
|
||||
def remove_properties_order():
|
||||
"""Removes properties.order
|
||||
"""
|
||||
from pymongo import MongoClient
|
||||
client = MongoClient()
|
||||
db = client.eve
|
||||
nodes = db.nodes.find()
|
||||
for node in nodes:
|
||||
new_prop = {}
|
||||
for prop in node['properties']:
|
||||
if prop == 'order':
|
||||
continue
|
||||
else:
|
||||
new_prop[prop] = node['properties'][prop]
|
||||
db.nodes.update({"_id": node['_id']},
|
||||
{"$set": {"properties": new_prop}})
|
||||
|
||||
|
||||
@manager.command
|
||||
def upgrade_node_types():
|
||||
"""Wipes node_types collection
|
||||
and populates it again
|
||||
"""
|
||||
from pymongo import MongoClient
|
||||
|
||||
client = MongoClient()
|
||||
db = client.eve
|
||||
node_types = db.node_types.find({})
|
||||
old_ids = {}
|
||||
for nt in node_types:
|
||||
old_ids[nt['name']] = nt['_id']
|
||||
populate_node_types(old_ids)
|
||||
|
||||
|
||||
def get_id(collection, name):
|
||||
"""Returns the _id of the given collection
|
||||
and name."""
|
||||
from pymongo import MongoClient
|
||||
client = MongoClient()
|
||||
db = client.eve
|
||||
node = db[collection].find({'name': name})
|
||||
print (node[0]['_id'])
|
||||
return node[0]['_id']
|
||||
|
||||
|
||||
@manager.command
|
||||
def manage_groups():
|
||||
"""Take user email and group name,
|
||||
and add or remove the user from that group.
|
||||
"""
|
||||
from pymongo import MongoClient
|
||||
client = MongoClient()
|
||||
db = client.eve
|
||||
|
||||
print ("")
|
||||
print ("Add or Remove user from group")
|
||||
print ("leave empty to cancel")
|
||||
print ("")
|
||||
|
||||
# Select Action
|
||||
print ("Do you want to Add or Remove the user from the group?")
|
||||
retry = True
|
||||
while retry:
|
||||
action = raw_input('add/remove: ')
|
||||
if action == '':
|
||||
return
|
||||
elif action.lower() in ['add', 'a', 'insert']:
|
||||
action == 'add'
|
||||
retry = False
|
||||
elif action.lower() in ['remove', 'r', 'rmv', 'rem', 'delete', 'del']:
|
||||
action = 'remove'
|
||||
retry = False
|
||||
else:
|
||||
print ("Incorrect action, press type 'add' or 'remove'")
|
||||
|
||||
# Select User
|
||||
retry = True
|
||||
while retry:
|
||||
user_email = raw_input('User email: ')
|
||||
if user_email == '':
|
||||
return
|
||||
user = db.users.find_one({'email': user_email})
|
||||
if user:
|
||||
retry = False
|
||||
else:
|
||||
print ("Incorrect user email, try again, or leave empty to cancel")
|
||||
|
||||
# Select group
|
||||
retry = True
|
||||
while retry:
|
||||
group_name = raw_input('Group name: ')
|
||||
if group_name == '':
|
||||
return
|
||||
group = db.groups.find_one({'name': group_name})
|
||||
if group:
|
||||
retry = False
|
||||
else:
|
||||
print ("Incorrect group name, try again, or leave empty to cancel")
|
||||
|
||||
# Do
|
||||
current_groups = user.get('groups', [])
|
||||
if action == 'add':
|
||||
if group['_id'] in current_groups:
|
||||
print "User {0} is already in group {1}".format(
|
||||
user_email, group_name)
|
||||
else:
|
||||
current_groups.append(group['_id'])
|
||||
db.users.update({'_id': user['_id']},
|
||||
{"$set": {'groups': current_groups}})
|
||||
print "User {0} added to group {1}".format(user_email, group_name)
|
||||
elif action == 'remove':
|
||||
if group['_id'] not in current_groups:
|
||||
print "User {0} is not in group {1}".format(user_email, group_name)
|
||||
else:
|
||||
current_groups.remove(group['_id'])
|
||||
db.users.update({'_id': user['_id']},
|
||||
{"$set": {'groups': current_groups}})
|
||||
print "User {0} removed from group {1}".format(
|
||||
user_email, group_name)
|
||||
|
||||
|
||||
@manager.command
|
||||
def add_groups():
|
||||
"""Add permisions
|
||||
"""
|
||||
admin_group = {
|
||||
'name': 'admin',
|
||||
'permissions': [
|
||||
{'node_type': get_id('node_types', 'shot'),
|
||||
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
|
||||
},
|
||||
{'node_type': get_id('node_types', 'task'),
|
||||
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
|
||||
},
|
||||
{'node_type': get_id('node_types', 'scene'),
|
||||
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
|
||||
},
|
||||
{'node_type': get_id('node_types', 'act'),
|
||||
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
|
||||
},
|
||||
{'node_type': get_id('node_types', 'comment'),
|
||||
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
|
||||
},
|
||||
]
|
||||
}
|
||||
post_item('groups', admin_group)
|
||||
|
||||
owner_group = {
|
||||
'name': 'owner',
|
||||
'permissions': [
|
||||
{'node_type': get_id('node_types', 'shot'),
|
||||
'permissions': ['GET', 'UPDATE', 'DELETE']
|
||||
},
|
||||
{'node_type': get_id('node_types', 'task'),
|
||||
'permissions': ['GET', 'UPDATE', 'DELETE']
|
||||
},
|
||||
{'node_type': get_id('node_types', 'scene'),
|
||||
'permissions': ['GET', 'UPDATE', 'DELETE']
|
||||
},
|
||||
{'node_type': get_id('node_types', 'act'),
|
||||
'permissions': ['GET', 'UPDATE', 'DELETE']
|
||||
},
|
||||
{'node_type': get_id('node_types', 'comment'),
|
||||
'permissions': ['GET', 'UPDATE', 'DELETE']
|
||||
},
|
||||
]
|
||||
}
|
||||
post_item('groups', owner_group)
|
||||
|
||||
world_group = {
|
||||
'name': 'world',
|
||||
'permissions': [
|
||||
{'node_type': get_id('node_types', 'shot'),
|
||||
'permissions': ['GET']
|
||||
},
|
||||
{'node_type': get_id('node_types', 'task'),
|
||||
'permissions': ['GET']
|
||||
},
|
||||
{'node_type': get_id('node_types', 'scene'),
|
||||
'permissions': ['GET']
|
||||
},
|
||||
{'node_type': get_id('node_types', 'act'),
|
||||
'permissions': ['GET']
|
||||
},
|
||||
{'node_type': get_id('node_types', 'comment'),
|
||||
'permissions': ['GET', 'POST']
|
||||
},
|
||||
]
|
||||
}
|
||||
post_item('groups', world_group)
|
||||
|
||||
|
||||
@manager.command
|
||||
def populate_db_test():
|
||||
"""Populate the db with sample data
|
||||
"""
|
||||
populate_node_types()
|
||||
|
||||
|
||||
def populate_node_types(old_ids={}):
|
||||
shot_node_type = {
|
||||
'name': 'shot',
|
||||
'description': 'Shot Node Type, for shots',
|
||||
'dyn_schema': {
|
||||
'url': {
|
||||
'type': 'string',
|
||||
},
|
||||
'cut_in': {
|
||||
'type': 'integer'
|
||||
},
|
||||
'cut_out': {
|
||||
'type': 'integer'
|
||||
},
|
||||
'status': {
|
||||
'type': 'string',
|
||||
'allowed': [
|
||||
'on_hold',
|
||||
'todo',
|
||||
'in_progress',
|
||||
'review',
|
||||
'final'
|
||||
],
|
||||
},
|
||||
'notes': {
|
||||
'type': 'string',
|
||||
'maxlength': 256,
|
||||
},
|
||||
'shot_group': {
|
||||
'type': 'string',
|
||||
#'data_relation': {
|
||||
# 'resource': 'nodes',
|
||||
# 'field': '_id',
|
||||
#},
|
||||
},
|
||||
},
|
||||
'form_schema': {
|
||||
'url': {},
|
||||
'cut_in': {},
|
||||
'cut_out': {},
|
||||
'status': {},
|
||||
'notes': {},
|
||||
'shot_group': {}
|
||||
},
|
||||
'parent': {
|
||||
'node_types': ['scene']
|
||||
}
|
||||
}
|
||||
|
||||
task_node_type = {
|
||||
'name': 'task',
|
||||
'description': 'Task Node Type, for tasks',
|
||||
'dyn_schema': {
|
||||
'status': {
|
||||
'type': 'string',
|
||||
'allowed': [
|
||||
'todo',
|
||||
'in_progress',
|
||||
'on_hold',
|
||||
'approved',
|
||||
'cbb',
|
||||
'final',
|
||||
'review'
|
||||
],
|
||||
'required': True,
|
||||
},
|
||||
'filepath': {
|
||||
'type': 'string',
|
||||
},
|
||||
'revision': {
|
||||
'type': 'integer',
|
||||
},
|
||||
'owners': {
|
||||
'type': 'dict',
|
||||
'schema': {
|
||||
'users': {
|
||||
'type': 'list',
|
||||
'schema': {
|
||||
'type': 'objectid',
|
||||
}
|
||||
},
|
||||
'groups': {
|
||||
'type': 'list',
|
||||
'schema': {
|
||||
'type': 'objectid',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'time': {
|
||||
'type': 'dict',
|
||||
'schema': {
|
||||
'start': {
|
||||
'type': 'datetime'
|
||||
},
|
||||
'duration': {
|
||||
'type': 'integer'
|
||||
},
|
||||
'chunks': {
|
||||
'type': 'list',
|
||||
'schema': {
|
||||
'type': 'dict',
|
||||
'schema': {
|
||||
'start': {
|
||||
'type': 'datetime',
|
||||
},
|
||||
'duration': {
|
||||
'type': 'integer',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
'is_conflicting' : {
|
||||
'type': 'boolean'
|
||||
},
|
||||
'is_processing' : {
|
||||
'type': 'boolean'
|
||||
},
|
||||
'is_open' : {
|
||||
'type': 'boolean'
|
||||
}
|
||||
|
||||
},
|
||||
'form_schema': {
|
||||
'status': {},
|
||||
'filepath': {},
|
||||
'revision': {},
|
||||
'owners': {
|
||||
'schema': {
|
||||
'users':{
|
||||
'items': [('User', 'first_name')],
|
||||
},
|
||||
'groups': {}
|
||||
}
|
||||
},
|
||||
'time': {
|
||||
'schema': {
|
||||
'start': {},
|
||||
'duration': {},
|
||||
'chunks': {
|
||||
'visible': False,
|
||||
'schema': {
|
||||
'start': {},
|
||||
'duration': {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'is_conflicting': {},
|
||||
'is_open': {},
|
||||
'is_processing': {},
|
||||
},
|
||||
'parent': {
|
||||
'node_types': ['shot'],
|
||||
}
|
||||
}
|
||||
|
||||
scene_node_type = {
|
||||
'name': 'scene',
|
||||
'description': 'Scene node type',
|
||||
'dyn_schema': {
|
||||
'order': {
|
||||
'type': 'integer',
|
||||
}
|
||||
},
|
||||
'form_schema': {
|
||||
'order': {},
|
||||
},
|
||||
'parent': {
|
||||
"node_types": ["act"]
|
||||
}
|
||||
}
|
||||
|
||||
act_node_type = {
|
||||
'name': 'act',
|
||||
'description': 'Act node type',
|
||||
'dyn_schema': {
|
||||
'order': {
|
||||
'type': 'integer',
|
||||
}
|
||||
},
|
||||
'form_schema': {
|
||||
'order': {},
|
||||
},
|
||||
'parent': {}
|
||||
}
|
||||
|
||||
comment_node_type = {
|
||||
'name': 'comment',
|
||||
'description': 'Comment node type',
|
||||
'dyn_schema': {
|
||||
'text': {
|
||||
'type': 'string',
|
||||
'maxlength': 256
|
||||
},
|
||||
'attachments': {
|
||||
'type': 'list',
|
||||
'schema': {
|
||||
'type': 'objectid',
|
||||
'data_relation': {
|
||||
'resource': 'files',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'form_schema': {
|
||||
'text': {},
|
||||
'attachments': {
|
||||
'items': [("File", "name")]
|
||||
}
|
||||
},
|
||||
'parent': {
|
||||
"node_types": ["shot", "task"]
|
||||
}
|
||||
}
|
||||
|
||||
project_node_type = {
|
||||
'name': 'project',
|
||||
'parent': {},
|
||||
'description': 'The official project type',
|
||||
'dyn_schema': {
|
||||
'category': {
|
||||
'type': 'string',
|
||||
'allowed': [
|
||||
'film',
|
||||
'assets',
|
||||
'software',
|
||||
'game'
|
||||
],
|
||||
'required': True,
|
||||
},
|
||||
'is_private': {
|
||||
'type': 'boolean'
|
||||
},
|
||||
'url': {
|
||||
'type': 'string'
|
||||
},
|
||||
'organization': {
|
||||
'type': 'objectid',
|
||||
'nullable': True,
|
||||
'data_relation': {
|
||||
'resource': 'organizations',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
},
|
||||
},
|
||||
'owners': {
|
||||
'type': 'dict',
|
||||
'schema': {
|
||||
'users': {
|
||||
'type': 'list',
|
||||
'schema': {
|
||||
'type': 'objectid',
|
||||
}
|
||||
},
|
||||
'groups': {
|
||||
'type': 'list',
|
||||
'schema': {
|
||||
'type': 'objectid',
|
||||
'data_relation': {
|
||||
'resource': 'groups',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
# Logo
|
||||
'picture_1': {
|
||||
'type': 'objectid',
|
||||
'nullable': True,
|
||||
'data_relation': {
|
||||
'resource': 'files',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
},
|
||||
},
|
||||
# Header
|
||||
'picture_2': {
|
||||
'type': 'objectid',
|
||||
'nullable': True,
|
||||
'data_relation': {
|
||||
'resource': 'files',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
},
|
||||
},
|
||||
},
|
||||
'form_schema': {
|
||||
'is_private': {},
|
||||
# TODO add group parsing
|
||||
'category': {},
|
||||
'url': {},
|
||||
'organization': {},
|
||||
'picture_1': {},
|
||||
'picture_2': {},
|
||||
'owners': {
|
||||
'schema': {
|
||||
'users':{
|
||||
'items': [('User', 'first_name')],
|
||||
},
|
||||
'groups': {
|
||||
'items': [('Group', 'name')],
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
group_node_type = {
|
||||
'name': 'group',
|
||||
'description': 'Generic group node type',
|
||||
'dyn_schema': {
|
||||
'url': {
|
||||
'type': 'string',
|
||||
},
|
||||
'status': {
|
||||
'type': 'string',
|
||||
'allowed': [
|
||||
'published',
|
||||
'pending'
|
||||
],
|
||||
},
|
||||
'notes': {
|
||||
'type': 'string',
|
||||
'maxlength': 256,
|
||||
},
|
||||
'parent': {}
|
||||
},
|
||||
'form_schema': {
|
||||
'url': {},
|
||||
'status': {},
|
||||
'notes': {},
|
||||
},
|
||||
}
|
||||
|
||||
from pymongo import MongoClient
|
||||
|
||||
client = MongoClient()
|
||||
db = client.eve
|
||||
|
||||
def mix_node_type(old_id, node_type_dict):
|
||||
# Take eve parameters
|
||||
node_type = db.node_types.find({'_id':old_id})
|
||||
node_type = node_type[0]
|
||||
for attr in node_type:
|
||||
if attr[0]=='_':
|
||||
# Mix with node type attributes
|
||||
node_type_dict[attr]=node_type[attr]
|
||||
return node_type_dict
|
||||
|
||||
def upgrade(node_type, old_ids):
|
||||
node_name = node_type['name']
|
||||
if node_name in old_ids:
|
||||
node_type = mix_node_type(old_ids[node_name], node_type)
|
||||
# Remove old node_type
|
||||
db.node_types.remove({'_id': old_ids[node_name]})
|
||||
# Insert new node_type
|
||||
db.node_types.insert(node_type)
|
||||
else:
|
||||
print("Making the node")
|
||||
print node_type
|
||||
post_item('node_types', node_type)
|
||||
|
||||
# upgrade(shot_node_type, old_ids)
|
||||
# upgrade(task_node_type, old_ids)
|
||||
# upgrade(scene_node_type, old_ids)
|
||||
# upgrade(act_node_type, old_ids)
|
||||
# upgrade(comment_node_type, old_ids)
|
||||
upgrade(project_node_type, old_ids)
|
||||
|
||||
|
||||
@manager.command
|
||||
def migrate_custom():
|
||||
from pymongo import MongoClient
|
||||
|
||||
client = MongoClient()
|
||||
db = client.eve
|
||||
|
||||
group_node_type = {
|
||||
'name': 'group',
|
||||
'description': 'Generic group node type',
|
||||
'dyn_schema': {
|
||||
'url': {
|
||||
'type': 'string',
|
||||
},
|
||||
'status': {
|
||||
'type': 'string',
|
||||
'allowed': [
|
||||
'published',
|
||||
'pending'
|
||||
],
|
||||
},
|
||||
'notes': {
|
||||
'type': 'string',
|
||||
'maxlength': 256,
|
||||
},
|
||||
'parent': {}
|
||||
},
|
||||
'form_schema': {
|
||||
'url': {},
|
||||
'status': {},
|
||||
'notes': {},
|
||||
},
|
||||
}
|
||||
db.node_types.insert(group_node_type)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
manager.run()
|
427
pillar/settings.py
Normal file
427
pillar/settings.py
Normal file
@@ -0,0 +1,427 @@
|
||||
import os
|
||||
|
||||
# Enable reads (GET), inserts (POST) and DELETE for resources/collections
|
||||
# (if you omit this line, the API will default to ['GET'] and provide
|
||||
# read-only access to the endpoint).
|
||||
RESOURCE_METHODS = ['GET', 'POST', 'DELETE']
|
||||
|
||||
# Enable reads (GET), edits (PATCH), replacements (PUT) and deletes of
|
||||
# individual items (defaults to read-only item access).
|
||||
ITEM_METHODS = ['GET', 'PUT', 'DELETE', 'PATCH']
|
||||
|
||||
PAGINATION_LIMIT = 999
|
||||
|
||||
# To be implemented on Eve 0.6
|
||||
# RETURN_MEDIA_AS_URL = True
|
||||
|
||||
users_schema = {
|
||||
'first_name': {
|
||||
'type': 'string',
|
||||
'minlength': 1,
|
||||
'maxlength': 60,
|
||||
},
|
||||
'last_name': {
|
||||
'type': 'string',
|
||||
'minlength': 1,
|
||||
'maxlength': 60,
|
||||
},
|
||||
'username': {
|
||||
'type': 'string',
|
||||
'minlength': 1,
|
||||
'maxlength': 60,
|
||||
'required': True,
|
||||
},
|
||||
'email': {
|
||||
'type': 'string',
|
||||
'minlength': 1,
|
||||
'maxlength': 60,
|
||||
},
|
||||
'role': {
|
||||
'type': 'list',
|
||||
'allowed': ["admin"],
|
||||
'required': True,
|
||||
},
|
||||
'groups': {
|
||||
'type': 'list',
|
||||
'default': [],
|
||||
'schema': {
|
||||
'type': 'objectid',
|
||||
'data_relation': {
|
||||
'resource': 'groups',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
organizations_schema = {
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'minlength': 1,
|
||||
'maxlength': 128,
|
||||
'required': True
|
||||
},
|
||||
'url': {
|
||||
'type': 'string',
|
||||
'minlength': 1,
|
||||
'maxlength': 128,
|
||||
'required': True
|
||||
},
|
||||
'description': {
|
||||
'type': 'string',
|
||||
'maxlength': 256,
|
||||
},
|
||||
'website': {
|
||||
'type': 'string',
|
||||
'maxlength': 256,
|
||||
},
|
||||
'location': {
|
||||
'type': 'string',
|
||||
'maxlength': 256,
|
||||
},
|
||||
'picture': {
|
||||
'type': 'objectid',
|
||||
'nullable': True,
|
||||
'data_relation': {
|
||||
'resource': 'files',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
},
|
||||
},
|
||||
'users': {
|
||||
'type': 'list',
|
||||
'default': [],
|
||||
'schema': {
|
||||
'type': 'objectid',
|
||||
'data_relation': {
|
||||
'resource': 'users',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
}
|
||||
}
|
||||
},
|
||||
'teams': {
|
||||
'type': 'list',
|
||||
'default': [],
|
||||
'schema': {
|
||||
'type': 'dict',
|
||||
'schema': {
|
||||
# Team name
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'minlength': 1,
|
||||
'maxlength': 128,
|
||||
'required': True
|
||||
},
|
||||
# List of user ids for the team
|
||||
'users': {
|
||||
'type': 'list',
|
||||
'default': [],
|
||||
'schema': {
|
||||
'type': 'objectid',
|
||||
'data_relation': {
|
||||
'resource': 'users',
|
||||
'field': '_id',
|
||||
}
|
||||
}
|
||||
},
|
||||
# List of groups assigned to the team (this will automatically
|
||||
# update the groups property of each user in the team)
|
||||
'groups': {
|
||||
'type': 'list',
|
||||
'default': [],
|
||||
'schema': {
|
||||
'type': 'objectid',
|
||||
'data_relation': {
|
||||
'resource': 'groups',
|
||||
'field': '_id',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nodes_schema = {
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'minlength': 1,
|
||||
'maxlength': 128,
|
||||
'required': True,
|
||||
},
|
||||
'description': {
|
||||
'type': 'string',
|
||||
'minlength': 0,
|
||||
'maxlength': 128,
|
||||
},
|
||||
'picture': {
|
||||
'type': 'objectid',
|
||||
'nullable': True,
|
||||
'data_relation': {
|
||||
'resource': 'files',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
},
|
||||
},
|
||||
'order': {
|
||||
'type': 'integer',
|
||||
'minlength': 0,
|
||||
},
|
||||
'parent': {
|
||||
'type': 'objectid',
|
||||
'data_relation': {
|
||||
'resource': 'nodes',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
},
|
||||
},
|
||||
'user': {
|
||||
'type': 'objectid',
|
||||
'required': True,
|
||||
'data_relation': {
|
||||
'resource': 'users',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
},
|
||||
},
|
||||
'node_type': {
|
||||
'type': 'objectid',
|
||||
'required': True,
|
||||
'data_relation': {
|
||||
'resource': 'node_types',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
},
|
||||
},
|
||||
'properties': {
|
||||
'type' : 'dict',
|
||||
'valid_properties' : True,
|
||||
'required': True,
|
||||
},
|
||||
}
|
||||
|
||||
node_types_schema = {
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'minlength': 1,
|
||||
'maxlength': 128,
|
||||
'required': True,
|
||||
},
|
||||
'description': {
|
||||
'type': 'string',
|
||||
'maxlength': 256,
|
||||
},
|
||||
'dyn_schema': {
|
||||
'type': 'dict',
|
||||
'required': True,
|
||||
},
|
||||
'form_schema': {
|
||||
'type': 'dict',
|
||||
'required': True,
|
||||
},
|
||||
'parent': {
|
||||
'type': 'dict',
|
||||
'required': True,
|
||||
}
|
||||
}
|
||||
|
||||
tokens_schema = {
|
||||
'user': {
|
||||
'type': 'objectid',
|
||||
'required': True,
|
||||
},
|
||||
'token': {
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
},
|
||||
'expire_time': {
|
||||
'type': 'datetime',
|
||||
'required': True,
|
||||
},
|
||||
}
|
||||
|
||||
files_schema = {
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
},
|
||||
'description': {
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
},
|
||||
# Preview parameters:
|
||||
'is_preview': {
|
||||
'type': 'boolean'
|
||||
},
|
||||
'size': {
|
||||
'type': 'string'
|
||||
},
|
||||
'format': {
|
||||
'type': 'string'
|
||||
},
|
||||
'width': {
|
||||
'type': 'integer'
|
||||
},
|
||||
'height': {
|
||||
'type': 'integer'
|
||||
},
|
||||
#
|
||||
'user': {
|
||||
'type': 'objectid',
|
||||
'required': True,
|
||||
},
|
||||
'contentType': {
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
},
|
||||
'length': {
|
||||
'type': 'integer',
|
||||
'required': True,
|
||||
},
|
||||
'uploadDate': {
|
||||
'type': 'datetime',
|
||||
'required': True,
|
||||
},
|
||||
'md5': {
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
},
|
||||
'filename': {
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
},
|
||||
'backend': {
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'allowed': ["attract-web", "attract"]
|
||||
},
|
||||
'path': {
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'unique': True,
|
||||
},
|
||||
'previews': {
|
||||
'type': 'list',
|
||||
'schema': {
|
||||
'type': 'objectid',
|
||||
'data_relation': {
|
||||
'resource': 'files',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binary_files_schema = {
|
||||
'data': {
|
||||
'type': 'media',
|
||||
'required': True
|
||||
}
|
||||
}
|
||||
|
||||
groups_schema = {
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'required': True
|
||||
},
|
||||
'permissions': {
|
||||
'type': 'list',
|
||||
'required': True,
|
||||
'schema': {
|
||||
'type': 'dict',
|
||||
'schema': {
|
||||
'node_type': {
|
||||
'type': 'objectid',
|
||||
'required': True,
|
||||
'data_relation': {
|
||||
'resource': 'node_types',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
}
|
||||
},
|
||||
'permissions': {
|
||||
'type': 'list',
|
||||
'required': True,
|
||||
'allowed': ['GET', 'POST', 'UPDATE', 'DELETE']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes = {
|
||||
'schema': nodes_schema
|
||||
}
|
||||
|
||||
node_types = {
|
||||
'resource_methods': ['GET', 'POST'],
|
||||
'schema': node_types_schema,
|
||||
}
|
||||
|
||||
users = {
|
||||
'item_title': 'user',
|
||||
|
||||
# We choose to override global cache-control directives for this resource.
|
||||
'cache_control': 'max-age=10,must-revalidate',
|
||||
'cache_expires': 10,
|
||||
|
||||
# most global settings can be overridden at resource level
|
||||
'resource_methods': ['GET', 'POST'],
|
||||
|
||||
'public_methods': ['GET', 'POST'],
|
||||
# 'public_item_methods': ['GET'],
|
||||
|
||||
'schema': users_schema
|
||||
}
|
||||
|
||||
tokens = {
|
||||
'resource_methods': ['GET', 'POST'],
|
||||
|
||||
# Allow 'token' to be returned with POST responses
|
||||
#'extra_response_fields': ['token'],
|
||||
|
||||
'schema' : tokens_schema
|
||||
}
|
||||
|
||||
files = {
|
||||
'resource_methods': ['GET', 'POST'],
|
||||
'schema': files_schema,
|
||||
}
|
||||
|
||||
binary_files = {
|
||||
'resource_methods': ['GET', 'POST'],
|
||||
'schema': binary_files_schema,
|
||||
}
|
||||
|
||||
groups = {
|
||||
'resource_methods': ['GET', 'POST'],
|
||||
'schema': groups_schema,
|
||||
}
|
||||
|
||||
organizations = {
|
||||
'schema': organizations_schema,
|
||||
}
|
||||
|
||||
DOMAIN = {
|
||||
'users': users,
|
||||
'nodes': nodes,
|
||||
'node_types': node_types,
|
||||
'tokens': tokens,
|
||||
'files': files,
|
||||
'binary_files': binary_files,
|
||||
'groups': groups,
|
||||
'organizations': organizations
|
||||
}
|
||||
|
||||
try:
|
||||
os.environ['TEST_ATTRACT']
|
||||
MONGO_DBNAME = 'attract_test'
|
||||
except:
|
||||
pass
|
||||
|
||||
if os.environ.get('MONGO_HOST'):
|
||||
MONGO_HOST = os.environ.get('MONGO_HOST')
|
158
pillar/test_attract.py
Normal file
158
pillar/test_attract.py
Normal file
@@ -0,0 +1,158 @@
|
||||
import os
|
||||
import json
|
||||
import unittest
|
||||
from pymongo import MongoClient
|
||||
from bson import ObjectId
|
||||
from datetime import datetime
|
||||
import base64
|
||||
|
||||
|
||||
class AttractTestCase(unittest.TestCase):
|
||||
|
||||
|
||||
def encodeUsrPass(self, user, password):
|
||||
usrPass = "{0}:{1}".format(user, password)
|
||||
b64Val = base64.b64encode(usrPass)
|
||||
return b64Val
|
||||
|
||||
def addUser(self, first_name, last_name, role):
|
||||
return self.app.post('/users', data=dict(
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
role=role,
|
||||
), follow_redirects=True)
|
||||
|
||||
def addNodeType(self, name, schema):
|
||||
data = {
|
||||
'name': name,
|
||||
'dyn_schema': schema,
|
||||
}
|
||||
headers = {
|
||||
'content-type': 'application/json',
|
||||
'Authorization': 'Basic QU5MR05TSUVaSjoxMjM0'
|
||||
}
|
||||
return self.app.post(
|
||||
'/node_types',
|
||||
data=json.dumps(data),
|
||||
headers=headers,
|
||||
follow_redirects=True)
|
||||
|
||||
def addNode(self, name, nodeType, properties):
|
||||
data = {
|
||||
'name': name,
|
||||
'node_type': nodeType,
|
||||
'properties': properties
|
||||
}
|
||||
headers = {
|
||||
'content-type': 'application/json',
|
||||
'Authorization': 'Basic QU5MR05TSUVaSjoxMjM0',
|
||||
}
|
||||
return self.app.post(
|
||||
'/nodes',
|
||||
data=json.dumps(data),
|
||||
headers=headers,
|
||||
follow_redirects=True)
|
||||
|
||||
def login(self, username, password):
|
||||
headers = {
|
||||
'content-type': 'application/json',
|
||||
'Authorization': 'Basic {0}'.format(
|
||||
self.encodeUsrPass(username, password))
|
||||
}
|
||||
data = {
|
||||
'username': username,
|
||||
}
|
||||
return self.app.post(
|
||||
'/tokens',
|
||||
data=json.dumps(data),
|
||||
headers=headers,
|
||||
follow_redirects=True)
|
||||
|
||||
def logout(self):
|
||||
return self.app.get('/logout', follow_redirects=True)
|
||||
|
||||
|
||||
# Tests
|
||||
|
||||
def test_add_user(self):
|
||||
rv = self.addUser('admin', 'default', 'author')
|
||||
assert 201 == rv.status_code
|
||||
|
||||
def test_add_node_type(self):
|
||||
schema = {
|
||||
'frame_start': {
|
||||
'type':'integer',
|
||||
}
|
||||
}
|
||||
rv = self.addNodeType('NodeName', schema)
|
||||
assert 201 == rv.status_code
|
||||
|
||||
def test_add_node(self):
|
||||
properties = {
|
||||
'frame_start': 123
|
||||
}
|
||||
rv = self.addNode('Shot01', '55016a52135d32466fc800be', properties)
|
||||
assert 201 == rv.status_code
|
||||
|
||||
def test_login(self):
|
||||
rv = self.login('admin', 'secret')
|
||||
#print (rv.data)
|
||||
assert 201 == rv.status_code
|
||||
|
||||
def test_empty_db(self):
|
||||
rv = self.app.get('/')
|
||||
assert 401 == rv.status_code
|
||||
|
||||
# Test Setup
|
||||
|
||||
def setUp(self):
|
||||
# Setup DB
|
||||
client = MongoClient()
|
||||
db = client.attract_test
|
||||
for col in db.collection_names():
|
||||
try:
|
||||
db[col].remove({})
|
||||
except:
|
||||
pass
|
||||
|
||||
test_user = {
|
||||
"_id": ObjectId("550171c8135d3248e477f288"),
|
||||
"_updated": datetime.now(),
|
||||
"firs_tname": "TestFirstname",
|
||||
"last_name": "TestLastname",
|
||||
"role": "author",
|
||||
"_created": datetime.now(),
|
||||
"_etag": "302236e27f51d2e26041ae9de49505d77332b260"
|
||||
}
|
||||
|
||||
test_node_type = {
|
||||
"_id": ObjectId("55016a52135d32466fc800be"),
|
||||
"_updated": datetime.now(),
|
||||
"name": "NodeName",
|
||||
"dyn_schema": {"frame_start": {"type": "integer"}},
|
||||
"_created": datetime.now(),
|
||||
"_etag": "0ea3c4f684a0cda85525184d5606c4f4ce6ac5f5"
|
||||
}
|
||||
|
||||
test_token = {
|
||||
"-id": ObjectId("5502f289135d3274cb658ba7"),
|
||||
"username": "TestFirstname",
|
||||
"token": "ANLGNSIEZJ",
|
||||
"_etag": "1e96ed46b133b7ede5ce6ef0d6d4fc53edd9f2ba"
|
||||
}
|
||||
|
||||
db.users.insert(test_user)
|
||||
db.node_types.insert(test_node_type)
|
||||
db.tokens.insert(test_token)
|
||||
|
||||
# Initialize Attract
|
||||
os.environ['TEST_ATTRACT'] = '1'
|
||||
import application
|
||||
application.app.config['TESTING'] = True
|
||||
self.app = application.app.test_client()
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Reference in New Issue
Block a user