adjutant/adjutant/api/v1/openstack.py

522 lines
17 KiB
Python

# Copyright (C) 2015 Catalyst IT Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf import settings
from django.utils import timezone
from rest_framework.response import Response
from adjutant.common import user_store
from adjutant.api import models
from adjutant.api import utils
from adjutant.api.v1 import tasks
from adjutant.api.v1.utils import add_task_id_for_roles, create_notification
from adjutant.common.quota import QuotaManager
class UserList(tasks.InviteUser):
@utils.mod_or_admin
def get(self, request):
"""Get a list of all users who have been added to a project"""
class_conf = settings.TASK_SETTINGS.get(
'edit_user', settings.DEFAULT_TASK_SETTINGS)
role_blacklist = class_conf.get('role_blacklist', [])
user_list = []
id_manager = user_store.IdentityManager()
project_id = request.keystone_user['project_id']
project = id_manager.get_project(project_id)
can_manage_roles = user_store.get_managable_roles(
request.keystone_user['roles'])
active_emails = set()
for user in id_manager.list_users(project):
skip = False
roles = []
for role in user.roles:
if role.name in role_blacklist:
skip = True
continue
roles.append(role.name)
if skip:
continue
inherited_roles = []
for role in user.inherited_roles:
if role.name in role_blacklist:
skip = True
continue
inherited_roles.append(role.name)
if skip:
continue
email = getattr(user, 'email', '')
enabled = getattr(user, 'enabled')
user_status = 'Active' if enabled else 'Account Disabled'
active_emails.add(email)
user_list.append({
'id': user.id,
'name': user.name,
'email': email,
'roles': roles,
'inherited_roles': inherited_roles,
'cohort': 'Member',
'status': user_status,
'manageable': set(can_manage_roles).issuperset(roles),
})
for user in id_manager.list_inherited_users(project):
skip = False
roles = []
for role in user.roles:
if role.name in role_blacklist:
skip = True
continue
roles.append(role.name)
if skip:
continue
email = getattr(user, 'email', '')
enabled = getattr(user, 'enabled')
user_status = 'Active' if enabled else 'Account Disabled'
user_list.append({'id': user.id,
'name': user.name,
'email': email,
'roles': roles,
'inherited_roles': [],
'cohort': 'Inherited',
'status': user_status,
'manageable': False,
})
# Get my active tasks for this project:
project_tasks = models.Task.objects.filter(
project_id=project_id,
task_type="invite_user",
completed=0,
cancelled=0)
registrations = []
for task in project_tasks:
status = "Invited"
for token in task.tokens:
if token.expired:
status = "Expired"
for notification in task.notifications:
if notification.error:
status = "Failed"
task_data = {}
for action in task.actions:
task_data.update(action.action_data)
registrations.append(
{'uuid': task.uuid, 'task_data': task_data, 'status': status})
for task in registrations:
# NOTE(adriant): commenting out for now as it causes more confusion
# than it helps. May uncomment once different duplication checking
# measures are in place.
# if task['task_data']['email'] not in active_emails:
user = {
'id': task['uuid'],
'name': task['task_data']['email'],
'email': task['task_data']['email'],
'roles': task['task_data']['roles'],
'inherited_roles':
task['task_data']['inherited_roles'],
'cohort': 'Invited',
'status': task['status']
}
if not settings.USERNAME_IS_EMAIL:
user['name'] = task['task_data']['username']
user_list.append(user)
return Response({'users': user_list})
class UserDetail(tasks.TaskView):
task_type = 'edit_user'
@utils.mod_or_admin
def get(self, request, user_id):
"""
Get user info based on the user id.
Will only find users in your project.
"""
id_manager = user_store.IdentityManager()
user = id_manager.get_user(user_id)
no_user = {'errors': ['No user with this id.']}
if not user:
return Response(no_user, status=404)
class_conf = settings.TASK_SETTINGS.get(
self.task_type, settings.DEFAULT_TASK_SETTINGS)
role_blacklist = class_conf.get('role_blacklist', [])
project_id = request.keystone_user['project_id']
project = id_manager.get_project(project_id)
roles = [role.name for role in id_manager.get_roles(user, project)]
roles_blacklisted = set(role_blacklist) & set(roles)
inherited_roles = [
role.name for role in id_manager.get_roles(user, project, True)]
inherited_roles_blacklisted = (
set(role_blacklist) & set(inherited_roles))
if not roles or roles_blacklisted or inherited_roles_blacklisted:
return Response(no_user, status=404)
return Response({'id': user.id,
"username": user.name,
"email": getattr(user, 'email', ''),
'roles': roles,
'inherited_roles': inherited_roles})
@utils.mod_or_admin
def delete(self, request, user_id):
"""
Remove this user from the project.
This may cancel a pending user invite, or simply revoke roles.
"""
id_manager = user_store.IdentityManager()
user = id_manager.get_user(user_id)
project_id = request.keystone_user['project_id']
# NOTE(dale): For now, we only support cancelling pending invites.
if user:
return Response(
{'errors': [
'Revoking keystone users not implemented. ' +
'Try removing all roles instead.']},
status=501)
project_tasks = models.Task.objects.filter(
project_id=project_id,
task_type="invite_user",
completed=0,
cancelled=0)
for task in project_tasks:
if task.uuid == user_id:
task.add_action_note(self.__class__.__name__, 'Cancelled.')
task.cancelled = True
task.save()
return Response('Cancelled pending invite task!', status=200)
return Response('Not found.', status=404)
class UserRoles(tasks.TaskView):
default_actions = ['EditUserRolesAction', ]
task_type = 'edit_roles'
@utils.mod_or_admin
def get(self, request, user_id):
""" Get role info based on the user id. """
id_manager = user_store.IdentityManager()
user = id_manager.get_user(user_id)
no_user = {'errors': ['No user with this id.']}
if not user:
return Response(no_user, status=404)
project_id = request.keystone_user['project_id']
project = id_manager.get_project(project_id)
class_conf = settings.TASK_SETTINGS.get(
self.task_type, settings.DEFAULT_TASK_SETTINGS)
role_blacklist = class_conf.get('role_blacklist', [])
roles = [role.name for role in id_manager.get_roles(user, project)]
roles_blacklisted = set(role_blacklist) & set(roles)
inherited_roles = [
role.name for role in id_manager.get_roles(user, project, True)]
inherited_roles_blacklisted = (
set(role_blacklist) & set(inherited_roles))
if not roles or roles_blacklisted or inherited_roles_blacklisted:
return Response(no_user, status=404)
return Response({'roles': roles,
'inherited_roles': inherited_roles})
@utils.mod_or_admin
def put(self, args, **kwargs):
""" Add user roles to the current project. """
kwargs['remove_role'] = False
return self._edit_user(args, **kwargs)
@utils.mod_or_admin
def delete(self, args, **kwargs):
""" Revoke user roles to the current project.
This only supports Active users
"""
kwargs['remove_role'] = True
return self._edit_user(args, **kwargs)
def _edit_user(self, request, user_id, remove_role=False, format=None):
""" Helper function to add or remove roles from a user """
request.data['remove'] = remove_role
if 'project_id' not in request.data:
request.data['project_id'] = request.keystone_user['project_id']
request.data['user_id'] = user_id
self.logger.info("(%s) - New EditUser %s request." % (
timezone.now(), request.method))
processed, status = self.process_actions(request)
errors = processed.get('errors', None)
if errors:
self.logger.info("(%s) - Validation errors with registration." %
timezone.now())
return Response({'errors': errors}, status=status)
response_dict = {'notes': processed.get('notes')}
add_task_id_for_roles(request, processed, response_dict, ['admin'])
return Response(response_dict, status=status)
class RoleList(tasks.TaskView):
task_type = 'edit_roles'
@utils.mod_or_admin
def get(self, request):
"""Returns a list of roles that may be managed for this project"""
# get roles for this user on the project
user_roles = request.keystone_user['roles']
managable_role_names = user_store.get_managable_roles(user_roles)
id_manager = user_store.IdentityManager()
# look up role names and form output dict of valid roles
managable_roles = []
for role_name in managable_role_names:
role = id_manager.find_role(role_name)
if role:
managable_roles.append(role.to_dict())
return Response({'roles': managable_roles})
class UserResetPassword(tasks.ResetPassword):
"""
The openstack forgot password endpoint.
---
"""
def get(self, request):
"""
The ResetPassword endpoint does not support GET.
This returns a 404.
"""
return Response(status=404)
class UserSetPassword(tasks.ResetPassword):
"""
The openstack endpoint to force a password reset.
---
"""
task_type = "force_password"
def get(self, request):
"""
The ForcePassword endpoint does not support GET.
This returns a 404.
"""
return Response(status=404)
@utils.admin
def post(self, request, format=None):
return super(UserSetPassword, self).post(request)
class UserUpdateEmail(tasks.UpdateEmail):
"""
The openstack endpoint for a user to update their own email.
---
"""
def get(self, request):
"""
The EmailUpdate endpoint does not support GET.
This returns a 404.
"""
return Response(status=404)
class SignUp(tasks.CreateProject):
"""
The openstack endpoint for signups.
"""
task_type = "signup"
def get(self, request):
"""
The SignUp endpoint does not support GET.
This returns a 404.
"""
return Response(status=404)
def post(self, request, format=None):
return super(SignUp, self).post(request)
class UpdateProjectQuotas(tasks.TaskView):
"""
The OpenStack endpoint to update the quota of a project in
one or more regions
"""
task_type = "update_quota"
default_actions = ["UpdateProjectQuotasAction", ]
_number_of_returned_tasks = 5
def get_active_quota_tasks(self):
# Get the 5 last quota tasks.
task_list = models.Task.objects.filter(
task_type__exact=self.task_type,
project_id__exact=self.project_id,
cancelled=0,
).order_by('-created_on')[:self._number_of_returned_tasks]
response_tasks = []
for task in task_list:
status = "Awaiting Approval"
if task.completed:
status = "Completed"
task_data = {}
for action in task.actions:
task_data.update(action.action_data)
new_dict = {
"id": task.uuid,
"regions": task_data['regions'],
"size": task_data['size'],
"request_user":
task.keystone_user['username'],
"task_created": task.created_on,
"valid": all([a.valid for a in task.actions]),
"status": status
}
response_tasks.append(new_dict)
return response_tasks
def check_region_exists(self, region):
# Check that the region actually exists
id_manager = user_store.IdentityManager()
v_region = id_manager.get_region(region)
if not v_region:
return False
return True
@utils.mod_or_admin
def get(self, request):
"""
This endpoint returns data about what sizes are available
as well as the current status of a specified region's quotas.
"""
quota_settings = settings.PROJECT_QUOTA_SIZES
size_order = settings.QUOTA_SIZES_ASC
self.project_id = request.keystone_user['project_id']
regions = request.query_params.get('regions', None)
include_usage = request.query_params.get('include_usage', True)
if regions:
regions = regions.split(",")
else:
id_manager = user_store.IdentityManager()
# Only get the region id as that is what will be passed from
# parameters otherwise
regions = (region.id for region in id_manager.list_regions())
region_quotas = []
quota_manager = QuotaManager(self.project_id)
for region in regions:
if self.check_region_exists(region):
region_quotas.append(quota_manager.get_region_quota_data(
region, include_usage))
else:
return Response(
{"ERROR": ['Region: %s is not valid' % region]}, 400)
response_tasks = self.get_active_quota_tasks()
return Response({'regions': region_quotas,
"quota_sizes": quota_settings,
"quota_size_order": size_order,
"active_quota_tasks": response_tasks})
@utils.mod_or_admin
def post(self, request):
request.data['project_id'] = request.keystone_user['project_id']
self.project_id = request.keystone_user['project_id']
regions = request.data.get('regions', None)
if not regions:
id_manager = user_store.IdentityManager()
regions = [region.id for region in id_manager.list_regions()]
request.data['regions'] = regions
self.logger.info("(%s) - New UpdateProjectQuotas request."
% timezone.now())
processed, status = self.process_actions(request)
# check the status
errors = processed.get('errors', None)
if errors:
self.logger.info("(%s) - Validation errors with task." %
timezone.now())
return Response({'errors': errors}, status=status)
if processed.get('auto_approved', False):
response_dict = {'notes': processed['notes']}
return Response(response_dict, status=status)
task = processed['task']
action_models = task.actions
valid = all([act.valid for act in action_models])
if not valid:
return Response({'errors': ['Actions invalid. You may have usage '
'above the new quota level.']}, 400)
# Action needs to be manually approved
notes = {
'notes':
['New task for UpdateProjectQuotas.']
}
create_notification(processed['task'], notes)
self.logger.info("(%s) - Task processed. Awaiting Aprroval"
% timezone.now())
response_dict = {'notes': ['Task processed. Awaiting Aprroval.']}
return Response(response_dict, status=202)