Check specific roles for specific addon features.

This commit is contained in:
Sybren A. Stüvel 2016-06-24 15:22:12 +02:00
parent 0f26551368
commit d53938e03b
4 changed files with 67 additions and 22 deletions

View File

@ -52,6 +52,7 @@ class SyncStatusProperties(PropertyGroup):
('INFO', 'INFO', ''), ('INFO', 'INFO', ''),
('WARNING', 'WARNING', ''), ('WARNING', 'WARNING', ''),
('ERROR', 'ERROR', ''), ('ERROR', 'ERROR', ''),
('SUBSCRIBE', 'SUBSCRIBE', ''),
], ],
name='level', name='level',
update=redraw) update=redraw)
@ -64,7 +65,11 @@ class SyncStatusProperties(PropertyGroup):
# Message can also be empty, just to erase it from the GUI. # Message can also be empty, just to erase it from the GUI.
# No need to actually log those. # No need to actually log those.
if message: if message:
log.log(logging._nameToLevel[self.level], message) try:
loglevel = logging._nameToLevel[self.level]
except KeyError:
loglevel = logging.WARNING
log.log(loglevel, message)
# List of syncable versions is stored in 'available_blender_versions' ID property, # List of syncable versions is stored in 'available_blender_versions' ID property,
# because I don't know how to store a variable list of strings in a proper RNA property. # because I don't know how to store a variable list of strings in a proper RNA property.
@ -157,15 +162,25 @@ class BlenderCloudPreferences(AddonPreferences):
'INFO': 'NONE', 'INFO': 'NONE',
'WARNING': 'INFO', 'WARNING': 'INFO',
'ERROR': 'ERROR', 'ERROR': 'ERROR',
'SUBSCRIBE': 'ERROR',
} }
message_container = row.row() message_container = row.row()
message_container.label(bss.message, icon=icon_for_level[bss.level]) message_container.label(bss.message, icon=icon_for_level[bss.level])
message_container.alert = True # bss.level in {'WARNING', 'ERROR'}
sub = bsync_box.column() sub = bsync_box.column()
sub.enabled = bss.status in {'NONE', 'IDLE'}
buttons = sub.column() if bss.level == 'SUBSCRIBE':
self.draw_subscribe_button(sub)
else:
self.draw_sync_buttons(sub, bss)
def draw_subscribe_button(self, layout):
layout.operator('pillar.subscribe', icon='WORLD')
def draw_sync_buttons(self, layout, bss):
layout.enabled = bss.status in {'NONE', 'IDLE'}
buttons = layout.column()
row_buttons = buttons.row().split(percentage=0.5) row_buttons = buttons.row().split(percentage=0.5)
row_pull = row_buttons.row(align=True) row_pull = row_buttons.row(align=True)
row_push = row_buttons.row() row_push = row_buttons.row()
@ -194,7 +209,8 @@ class BlenderCloudPreferences(AddonPreferences):
row_pull.label('Cloud Sync is running.') row_pull.label('Cloud Sync is running.')
class PillarCredentialsUpdate(Operator): class PillarCredentialsUpdate(pillar.PillarOperatorMixin,
Operator):
"""Updates the Pillar URL and tests the new URL.""" """Updates the Pillar URL and tests the new URL."""
bl_idname = 'pillar.credentials_update' bl_idname = 'pillar.credentials_update'
bl_label = 'Update credentials' bl_label = 'Update credentials'
@ -224,7 +240,7 @@ class PillarCredentialsUpdate(Operator):
try: try:
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.run_until_complete(pillar.refresh_pillar_credentials()) loop.run_until_complete(self.check_credentials(context, set()))
except blender_id.BlenderIdCommError as ex: except blender_id.BlenderIdCommError as ex:
log.exception('Error sending subclient-specific token to Blender ID') log.exception('Error sending subclient-specific token to Blender ID')
self.report({'ERROR'}, 'Failed to sync Blender ID to Blender Cloud') self.report({'ERROR'}, 'Failed to sync Blender ID to Blender Cloud')
@ -238,6 +254,20 @@ class PillarCredentialsUpdate(Operator):
return {'FINISHED'} return {'FINISHED'}
class PILLAR_OT_subscribe(Operator):
"""Opens a browser to subscribe the user to the Cloud."""
bl_idname = 'pillar.subscribe'
bl_label = 'Subscribe to the Cloud'
def execute(self, context):
import webbrowser
webbrowser.open_new_tab('https://cloud.blender.org/join')
self.report({'INFO'}, 'We just started a browser for you.')
return {'FINISHED'}
def preferences() -> BlenderCloudPreferences: def preferences() -> BlenderCloudPreferences:
return bpy.context.user_preferences.addons[ADDON_NAME].preferences return bpy.context.user_preferences.addons[ADDON_NAME].preferences
@ -246,6 +276,7 @@ def register():
bpy.utils.register_class(BlenderCloudPreferences) bpy.utils.register_class(BlenderCloudPreferences)
bpy.utils.register_class(PillarCredentialsUpdate) bpy.utils.register_class(PillarCredentialsUpdate)
bpy.utils.register_class(SyncStatusProperties) bpy.utils.register_class(SyncStatusProperties)
bpy.utils.register_class(PILLAR_OT_subscribe)
addon_prefs = preferences() addon_prefs = preferences()
@ -274,6 +305,7 @@ def unregister():
bpy.utils.unregister_class(PillarCredentialsUpdate) bpy.utils.unregister_class(PillarCredentialsUpdate)
bpy.utils.unregister_class(BlenderCloudPreferences) bpy.utils.unregister_class(BlenderCloudPreferences)
bpy.utils.unregister_class(SyncStatusProperties) bpy.utils.unregister_class(SyncStatusProperties)
bpy.utils.unregister_class(PILLAR_OT_subscribe)
del WindowManager.last_blender_cloud_location del WindowManager.last_blender_cloud_location
del WindowManager.blender_sync_status del WindowManager.blender_sync_status

View File

@ -30,6 +30,8 @@ import os
import pillarsdk import pillarsdk
from . import async_loop, pillar, cache from . import async_loop, pillar, cache
REQUIRED_ROLES_FOR_TEXTURE_BROWSER = {'subscriber', 'demo'}
icon_width = 128 icon_width = 128
icon_height = 128 icon_height = 128
target_item_width = 400 target_item_width = 400
@ -320,7 +322,7 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
self.log.debug('Checking credentials') self.log.debug('Checking credentials')
try: try:
user_id = await self.check_credentials(context) user_id = await self.check_credentials(context, REQUIRED_ROLES_FOR_TEXTURE_BROWSER)
except pillar.NotSubscribedToCloudError: except pillar.NotSubscribedToCloudError:
self.log.info('User not subscribed to Blender Cloud.') self.log.info('User not subscribed to Blender Cloud.')
self._show_subscribe_screen() self._show_subscribe_screen()

View File

@ -177,9 +177,10 @@ async def pillar_call(pillar_func, *args, caching=True, **kwargs):
return await loop.run_in_executor(None, partial) return await loop.run_in_executor(None, partial)
async def check_pillar_credentials(): async def check_pillar_credentials(required_roles: set):
"""Tries to obtain the user at Pillar using the user's credentials. """Tries to obtain the user at Pillar using the user's credentials.
:param required_roles: set of roles to require -- having one of those is enough.
:raises UserNotLoggedInError: when the user is not logged in on Blender ID. :raises UserNotLoggedInError: when the user is not logged in on Blender ID.
:raises CredentialsNotSyncedError: when the user is logged in on Blender ID but :raises CredentialsNotSyncedError: when the user is logged in on Blender ID but
doesn't have a valid subclient token for Pillar. doesn't have a valid subclient token for Pillar.
@ -203,9 +204,9 @@ async def check_pillar_credentials():
except (pillarsdk.UnauthorizedAccess, pillarsdk.ResourceNotFound, pillarsdk.ForbiddenAccess): except (pillarsdk.UnauthorizedAccess, pillarsdk.ResourceNotFound, pillarsdk.ForbiddenAccess):
raise CredentialsNotSyncedError() raise CredentialsNotSyncedError()
roles = db_user.roles roles = db_user.roles or set()
log.debug('User has roles %r', roles) log.debug('User has roles %r', roles)
if not roles or not {'subscriber', 'demo'}.intersection(set(roles)): if required_roles and not required_roles.intersection(set(roles)):
# Delete the subclient info. This forces a re-check later, which can # Delete the subclient info. This forces a re-check later, which can
# then pick up on the user's new status. # then pick up on the user's new status.
del profile.subclients[SUBCLIENT_ID] del profile.subclients[SUBCLIENT_ID]
@ -215,7 +216,7 @@ async def check_pillar_credentials():
return pillar_user_id return pillar_user_id
async def refresh_pillar_credentials(): async def refresh_pillar_credentials(required_roles: set):
"""Refreshes the authentication token on Pillar. """Refreshes the authentication token on Pillar.
:raises blender_id.BlenderIdCommError: when Blender ID refuses to send a token to Pillar. :raises blender_id.BlenderIdCommError: when Blender ID refuses to send a token to Pillar.
@ -239,7 +240,7 @@ async def refresh_pillar_credentials():
# Test the new URL # Test the new URL
_pillar_api = None _pillar_api = None
return await check_pillar_credentials() return await check_pillar_credentials(required_roles)
async def get_project_uuid(project_url: str) -> str: async def get_project_uuid(project_url: str) -> str:
@ -259,7 +260,7 @@ async def get_project_uuid(project_url: str) -> str:
async def get_nodes(project_uuid: str = None, parent_node_uuid: str = None, async def get_nodes(project_uuid: str = None, parent_node_uuid: str = None,
node_type = None) -> list: node_type=None) -> list:
"""Gets nodes for either a project or given a parent node. """Gets nodes for either a project or given a parent node.
@param project_uuid: the UUID of the project, or None if only querying by parent_node_uuid. @param project_uuid: the UUID of the project, or None if only querying by parent_node_uuid.
@ -662,8 +663,7 @@ def is_cancelled(future: asyncio.Future) -> bool:
class PillarOperatorMixin: class PillarOperatorMixin:
async def check_credentials(self, context, required_roles) -> bool:
async def check_credentials(self, context) -> bool:
"""Checks credentials with Pillar, and if ok returns the user ID. """Checks credentials with Pillar, and if ok returns the user ID.
Returns None if the user cannot be found, or if the user is not a Cloud subscriber. Returns None if the user cannot be found, or if the user is not a Cloud subscriber.
@ -672,7 +672,7 @@ class PillarOperatorMixin:
# self.report({'INFO'}, 'Checking Blender Cloud credentials') # self.report({'INFO'}, 'Checking Blender Cloud credentials')
try: try:
user_id = await check_pillar_credentials() user_id = await check_pillar_credentials(required_roles)
except NotSubscribedToCloudError: except NotSubscribedToCloudError:
self._log_subscription_needed() self._log_subscription_needed()
raise raise
@ -683,7 +683,7 @@ class PillarOperatorMixin:
return user_id return user_id
try: try:
user_id = await refresh_pillar_credentials() user_id = await refresh_pillar_credentials(required_roles)
except NotSubscribedToCloudError: except NotSubscribedToCloudError:
self._log_subscription_needed() self._log_subscription_needed()
raise raise

View File

@ -30,6 +30,7 @@ LOCAL_SETTINGS_RNA = [
(b'tempdir', 'filepaths.temporary_directory'), (b'tempdir', 'filepaths.temporary_directory'),
] ]
REQUIRES_ROLES_FOR_SYNC = {'subscriber', 'demo'}
HOME_PROJECT_ENDPOINT = '/bcloud/home-project' HOME_PROJECT_ENDPOINT = '/bcloud/home-project'
SYNC_GROUP_NODE_NAME = 'Blender Sync' SYNC_GROUP_NODE_NAME = 'Blender Sync'
SYNC_GROUP_NODE_DESC = 'The [Blender Cloud Addon](https://cloud.blender.org/services' \ SYNC_GROUP_NODE_DESC = 'The [Blender Cloud Addon](https://cloud.blender.org/services' \
@ -354,9 +355,22 @@ class PILLAR_OT_sync(pillar.PillarOperatorMixin,
self.log.info('Performing action %s', action) self.log.info('Performing action %s', action)
try: try:
self.user_id = await self.check_credentials(context) # Refresh credentials
log.debug('Found user ID: %s', self.user_id) try:
self.user_id = await self.check_credentials(context, REQUIRES_ROLES_FOR_SYNC)
log.debug('Found user ID: %s', self.user_id)
except pillar.NotSubscribedToCloudError:
self.log.exception('User not subscribed to cloud.')
self.bss_report({'SUBSCRIBE'}, 'Please subscribe to the Blender Cloud.')
self._state = 'QUIT'
return
except pillar.CredentialsNotSyncedError:
self.log.exception('Error checking/refreshing credentials.')
self.bss_report({'ERROR'}, 'Please log in on Blender ID first.')
self._state = 'QUIT'
return
# Find the home project.
try: try:
self.home_project_id = await get_home_project_id() self.home_project_id = await get_home_project_id()
except sdk_exceptions.ForbiddenAccess: except sdk_exceptions.ForbiddenAccess:
@ -390,9 +404,6 @@ class PILLAR_OT_sync(pillar.PillarOperatorMixin,
'REFRESH': self.action_refresh, 'REFRESH': self.action_refresh,
}[action] }[action]
await action_method(context) await action_method(context)
except pillar.CredentialsNotSyncedError:
self.log.exception('Error checking/refreshing credentials.')
self.bss_report({'ERROR'}, 'Please log in on Blender ID first.')
except Exception as ex: except Exception as ex:
self.log.exception('Unexpected exception caught.') self.log.exception('Unexpected exception caught.')
self.bss_report({'ERROR'}, 'Unexpected error: %s' % ex) self.bss_report({'ERROR'}, 'Unexpected error: %s' % ex)