2017-03-10 15:36:30 +01:00
|
|
|
import logging
|
|
|
|
|
|
|
|
import flask
|
|
|
|
from werkzeug.local import LocalProxy
|
2017-06-14 16:26:29 +02:00
|
|
|
|
|
|
|
from pillar.api.utils import authorization
|
2017-03-10 15:36:30 +01:00
|
|
|
from pillar.extension import PillarExtension
|
|
|
|
|
|
|
|
EXTENSION_NAME = 'cloud'
|
2017-08-22 16:40:05 +02:00
|
|
|
ROLES_TO_BE_SUBSCRIBER = {'demo', 'subscriber', 'admin'} # TODO: get rid of this, use 'subscriber' cap
|
2017-03-10 15:36:30 +01:00
|
|
|
|
|
|
|
|
|
|
|
class CloudExtension(PillarExtension):
|
2017-06-14 16:26:29 +02:00
|
|
|
has_context_processor = True
|
2017-11-30 15:30:08 +01:00
|
|
|
user_roles = {'subscriber-pro', 'has_subscription'}
|
|
|
|
user_roles_indexable = {'subscriber-pro', 'has_subscription'}
|
|
|
|
|
|
|
|
user_caps = {
|
|
|
|
'has_subscription': {'can-renew-subscription'},
|
|
|
|
}
|
2017-06-14 16:26:29 +02:00
|
|
|
|
2017-03-10 15:36:30 +01:00
|
|
|
def __init__(self):
|
|
|
|
self._log = logging.getLogger('%s.CloudExtension' % __name__)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
return EXTENSION_NAME
|
|
|
|
|
|
|
|
def flask_config(self):
|
|
|
|
"""Returns extension-specific defaults for the Flask configuration.
|
|
|
|
|
|
|
|
Use this to set sensible default values for configuration settings
|
|
|
|
introduced by the extension.
|
|
|
|
|
|
|
|
:rtype: dict
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Just so that it registers the management commands.
|
|
|
|
from . import cli
|
|
|
|
|
2017-11-30 15:45:40 +01:00
|
|
|
return {
|
|
|
|
'EXTERNAL_SUBSCRIPTIONS_MANAGEMENT_SERVER': 'https://store.blender.org/api/',
|
|
|
|
'EXTERNAL_SUBSCRIPTIONS_TIMEOUT_SECS': 10,
|
2017-12-01 19:06:16 +01:00
|
|
|
'BLENDER_ID_WEBHOOK_USER_CHANGED_SECRET': 'oos9wah1Zoa0Yau6ahThohleiChephoi',
|
2017-11-30 15:45:40 +01:00
|
|
|
}
|
2017-03-10 15:36:30 +01:00
|
|
|
|
|
|
|
def eve_settings(self):
|
|
|
|
"""Returns extensions to the Eve settings.
|
|
|
|
|
|
|
|
Currently only the DOMAIN key is used to insert new resources into
|
|
|
|
Eve's configuration.
|
|
|
|
|
|
|
|
:rtype: dict
|
|
|
|
"""
|
|
|
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
def blueprints(self):
|
|
|
|
"""Returns the list of top-level blueprints for the extension.
|
|
|
|
|
|
|
|
These blueprints will be mounted at the url prefix given to
|
|
|
|
app.load_extension().
|
|
|
|
|
|
|
|
:rtype: list of flask.Blueprint objects.
|
|
|
|
"""
|
|
|
|
from . import routes
|
2017-03-17 09:40:50 +01:00
|
|
|
import cloud.stats.routes
|
|
|
|
return [
|
|
|
|
routes.blueprint,
|
|
|
|
cloud.stats.routes.blueprint,
|
|
|
|
]
|
2017-03-10 15:36:30 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def template_path(self):
|
|
|
|
import os.path
|
|
|
|
return os.path.join(os.path.dirname(__file__), 'templates')
|
|
|
|
|
|
|
|
@property
|
|
|
|
def static_path(self):
|
|
|
|
import os.path
|
|
|
|
return os.path.join(os.path.dirname(__file__), 'static')
|
|
|
|
|
2017-06-14 16:26:29 +02:00
|
|
|
def context_processor(self):
|
|
|
|
return {
|
2017-08-22 16:40:05 +02:00
|
|
|
'current_user_is_subscriber': authorization.user_has_cap('subscriber')
|
2017-06-14 16:26:29 +02:00
|
|
|
}
|
|
|
|
|
2017-07-11 12:40:13 +02:00
|
|
|
def setup_app(self, app):
|
|
|
|
"""Links certain roles to the subscriber role.
|
|
|
|
|
|
|
|
This means that users who get the subscriber role also get this linked
|
|
|
|
role, and when the subscriber role is revoked, the linked role is also
|
|
|
|
revoked.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from pillar.api.service import signal_user_changed_role
|
2017-12-01 19:06:16 +01:00
|
|
|
from . import routes, webhooks
|
2017-07-11 12:40:13 +02:00
|
|
|
|
|
|
|
signal_user_changed_role.connect(self._user_changed_role)
|
2017-09-19 13:45:10 +02:00
|
|
|
routes.setup_app(app)
|
2017-12-01 19:06:16 +01:00
|
|
|
app.register_api_blueprint(webhooks.blueprint, '/webhooks')
|
2017-07-11 12:40:13 +02:00
|
|
|
|
|
|
|
def _user_changed_role(self, sender, user: dict):
|
|
|
|
from pillar.api import service
|
|
|
|
|
|
|
|
linked_roles = {'flamenco-user', 'attract-user'}
|
2017-08-15 15:25:50 +02:00
|
|
|
link_to = {'subscriber', 'demo'}
|
2017-07-11 12:40:13 +02:00
|
|
|
user_roles = set(user.get('roles', []))
|
|
|
|
|
|
|
|
# Determine what to do
|
|
|
|
has_linked_roles = not (linked_roles - user_roles)
|
2017-08-15 15:25:50 +02:00
|
|
|
has_link_to = bool(link_to.intersection(user_roles))
|
2017-07-11 12:40:13 +02:00
|
|
|
action = ''
|
2017-08-15 15:25:50 +02:00
|
|
|
if has_link_to and not has_linked_roles:
|
2017-07-11 12:40:13 +02:00
|
|
|
self._log.info('Granting roles %s to user %s', linked_roles, user['_id'])
|
|
|
|
action = 'grant'
|
2017-08-15 15:25:50 +02:00
|
|
|
elif not has_link_to and has_linked_roles:
|
2017-07-11 12:40:13 +02:00
|
|
|
self._log.info('Revoking roles %s from user %s', linked_roles, user['_id'])
|
|
|
|
action = 'revoke'
|
|
|
|
|
|
|
|
if not action:
|
|
|
|
return
|
|
|
|
|
|
|
|
# Avoid infinite loops while we're changing the user's roles.
|
|
|
|
service.signal_user_changed_role.disconnect(self._user_changed_role)
|
|
|
|
try:
|
2017-08-23 09:32:01 +02:00
|
|
|
if linked_roles:
|
|
|
|
service.do_badger(action, roles=linked_roles, user_id=user['_id'])
|
2017-07-11 12:40:13 +02:00
|
|
|
finally:
|
|
|
|
service.signal_user_changed_role.connect(self._user_changed_role)
|
|
|
|
|
2017-03-10 15:36:30 +01:00
|
|
|
|
|
|
|
def _get_current_cloud():
|
|
|
|
"""Returns the Cloud extension of the current application."""
|
|
|
|
return flask.current_app.pillar_extensions[EXTENSION_NAME]
|
|
|
|
|
|
|
|
|
|
|
|
current_cloud = LocalProxy(_get_current_cloud)
|
|
|
|
"""Cloud extension of the current app."""
|