Add basic support for managing, and accepting API tokens

also various other changes in pursuit of that.  so far tokens are only
accepted by web API and not traditional web app
This commit is contained in:
Lance Edgar 2023-05-14 20:10:05 -05:00
parent 85947878c4
commit c002d3d182
9 changed files with 318 additions and 26 deletions

View file

@ -2282,9 +2282,15 @@ class MasterView(View):
if info and info.markdown_text:
return info.markdown_text
def can_edit_help(self):
if self.has_perm('edit_help'):
return True
if self.request.has_perm('common.edit_help'):
return True
return False
def edit_help(self):
if (not self.has_perm('edit_help')
and not self.request.has_perm('common.edit_help')):
if not self.can_edit_help():
raise self.forbidden()
model = self.model
@ -2317,8 +2323,7 @@ class MasterView(View):
return {'ok': True}
def edit_field_help(self):
if (not self.has_perm('edit_help')
and not self.request.has_perm('common.edit_help')):
if not self.can_edit_help():
raise self.forbidden()
model = self.model
@ -2371,8 +2376,7 @@ class MasterView(View):
'grid_index': self.grid_index,
'help_url': self.get_help_url(),
'help_markdown': self.get_help_markdown(),
'can_edit_help': (self.has_perm('edit_help')
or self.request.has_perm('common.edit_help')),
'can_edit_help': self.can_edit_help(),
'quickie': None,
}
@ -2638,16 +2642,16 @@ class MasterView(View):
elif is_primary:
btn_kw['type'] = 'is-primary'
if icon_left:
btn_kw['icon_left'] = icon_left
elif is_external:
btn_kw['icon_left'] = 'external-link-alt'
elif url:
btn_kw['icon_left'] = 'eye'
if url:
btn_kw['href'] = url
if icon_left:
btn_kw['icon_left'] = icon_left
elif is_external:
btn_kw['icon_left'] = 'external-link-alt'
else:
btn_kw['icon_left'] = 'eye'
if target:
btn_kw['target'] = target
elif is_external:
@ -4017,8 +4021,7 @@ class MasterView(View):
'action_url': self.request.current_route_url(_query=None),
'assume_local_times': self.has_local_times,
'route_prefix': route_prefix,
'can_edit_help': (self.has_perm('edit_help')
or self.request.has_perm('common.edit_help')),
'can_edit_help': self.can_edit_help(),
}
if defaults['can_edit_help']:

View file

@ -38,6 +38,7 @@ from webhelpers2.html import HTML, tags
from tailbone import forms
from tailbone.views import MasterView, View
from tailbone.views.principal import PrincipalMasterView, PermissionsRenderer
from tailbone.util import raw_datetime
class UserView(PrincipalMasterView):
@ -51,6 +52,10 @@ class UserView(PrincipalMasterView):
touchable = True
mergeable = True
labels = {
'api_tokens': "API Tokens",
}
grid_columns = [
'username',
'person',
@ -68,6 +73,7 @@ class UserView(PrincipalMasterView):
'active_sticky',
'set_password',
'prevent_password_change',
'api_tokens',
'roles',
'permissions',
]
@ -218,6 +224,17 @@ class UserView(PrincipalMasterView):
# if self.creating:
# f.set_required('password')
# api_tokens
if self.creating or self.editing:
f.remove('api_tokens')
elif self.has_perm('manage_api_tokens'):
f.set_renderer('api_tokens', self.render_api_tokens)
f.set_vuejs_component_kwargs(**{':apiTokens': 'apiTokens',
'@api-new-token': 'apiNewToken',
'@api-token-delete': 'apiTokenDelete'})
else:
f.remove('api_tokens')
# roles
f.set_renderer('roles', self.render_roles)
if self.creating or self.editing:
@ -260,6 +277,75 @@ class UserView(PrincipalMasterView):
if self.viewing or self.deleting:
f.remove('set_password')
def render_api_tokens(self, user, field):
route_prefix = self.get_route_prefix()
permission_prefix = self.get_permission_prefix()
factory = self.get_grid_factory()
g = factory(
key='{}.api_tokens'.format(route_prefix),
data=[],
columns=['description', 'created'],
main_actions=[
self.make_action('delete', icon='trash',
click_handler="$emit('api-token-delete', props.row)")])
button = self.make_buefy_button("New", is_primary=True,
icon_left='plus',
**{'@click': "$emit('api-new-token')"})
table = HTML.literal(
g.render_buefy_table_element(data_prop='apiTokens'))
return HTML.tag('div', c=[button, table])
def add_api_token(self):
user = self.get_instance()
data = self.request.json_body
token = self.auth_handler.add_api_token(user, data['description'])
self.Session.flush()
return {'ok': True,
'raw_token': token.token_string,
'tokens': self.get_api_tokens(user)}
def delete_api_token(self):
model = self.model
user = self.get_instance()
data = self.request.json_body
token = self.Session.get(model.UserAPIToken, data['uuid'])
if not token:
return {'error': "API token not found"}
if token.user is not user:
return {'error': "API token not found"}
self.auth_handler.delete_api_token(token)
self.Session.flush()
return {'ok': True,
'tokens': self.get_api_tokens(user)}
def template_kwargs_view(self, **kwargs):
kwargs = super(UserView, self).template_kwargs_view(**kwargs)
user = kwargs['instance']
kwargs['api_tokens_data'] = self.get_api_tokens(user)
return kwargs
def get_api_tokens(self, user):
tokens = []
for token in reversed(user.api_tokens):
tokens.append({
'uuid': token.uuid,
'description': token.description,
'created': raw_datetime(self.rattail_config, token.created),
})
return tokens
def get_possible_roles(self):
model = self.model
@ -554,6 +640,25 @@ class UserView(PrincipalMasterView):
config.add_tailbone_permission(permission_prefix, '{}.edit_roles'.format(permission_prefix),
"Edit the Roles to which a {} belongs".format(model_title))
# manage API tokens
config.add_tailbone_permission(permission_prefix,
'{}.manage_api_tokens'.format(permission_prefix),
"Manage API tokens for any {}".format(model_title))
config.add_route('{}.add_api_token'.format(route_prefix),
'{}/add-api-token'.format(instance_url_prefix),
request_method='POST')
config.add_view(cls, attr='add_api_token',
route_name='{}.add_api_token'.format(route_prefix),
permission='{}.manage_api_tokens'.format(permission_prefix),
renderer='json')
config.add_route('{}.delete_api_token'.format(route_prefix),
'{}/delete-api-token'.format(instance_url_prefix),
request_method='POST')
config.add_view(cls, attr='delete_api_token',
route_name='{}.delete_api_token'.format(route_prefix),
permission='{}.manage_api_tokens'.format(permission_prefix),
renderer='json')
# edit preferences for any user
config.add_tailbone_permission(permission_prefix,
'{}.preferences'.format(permission_prefix),