Support admin user get functions of other projects

Partially implements: blueprint qinling-admin-operations
Change-Id: I7192c0006fd82e62f751323cc9707479040f5764
This commit is contained in:
Lingxian Kong 2017-12-12 15:30:06 +13:00
parent 7ba5676ec8
commit 2778c1cc3b
6 changed files with 118 additions and 14 deletions

View File

@ -7,4 +7,6 @@
"runtime:create": "rule:context_is_admin",
"runtime:update": "rule:context_is_admin",
"runtime:delete": "rule:context_is_admin",
"function:get_all:all_projects": "rule:context_is_admin",
}

View File

@ -24,6 +24,7 @@ from pecan import rest
from webob.static import FileIter
import wsmeext.pecan as wsme_pecan
from qinling.api import access_control as acl
from qinling.api.controllers.v1 import resources
from qinling.api.controllers.v1 import types
from qinling import context
@ -175,12 +176,32 @@ class FunctionsController(rest.RestController):
return resources.Function.from_dict(func_db.to_dict()).to_dict()
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.Functions)
def get_all(self):
@wsme_pecan.wsexpose(resources.Functions, bool, types.uuid)
def get_all(self, all_projects=False, project_id=None):
"""Return a list of functions.
:param project_id: Optional. Admin user can query other projects
resources, the param is ignored for normal user.
:param all_projects: Optional. Get resources of all projects.
"""
ctx = context.get_ctx()
if project_id and not ctx.is_admin:
project_id = context.ctx().projectid
if project_id and ctx.is_admin:
all_projects = True
if all_projects:
acl.enforce('function:get_all:all_projects', ctx)
LOG.info("Get all functions.")
filters = rest_utils.get_filters(
project_id=project_id,
)
db_functions = db_api.get_functions(insecure=all_projects, **filters)
functions = [resources.Function.from_dict(db_model.to_dict())
for db_model in db_api.get_functions()]
for db_model in db_functions]
return resources.Functions(functions=functions)

View File

@ -18,6 +18,7 @@ from oslo_config import cfg
from oslo_db import options as db_options
from oslo_db.sqlalchemy import session as db_session
from qinling import context
from qinling.db.sqlalchemy import sqlite_lock
from qinling import exceptions as exc
from qinling.utils import thread_local
@ -159,6 +160,22 @@ def session_aware():
return _decorator
def insecure_aware():
"""Decorator for methods working within insecure db query or not."""
def _decorator(func):
@functools.wraps(func)
def _with_insecure(*args, **kw):
if kw.get('insecure') is None:
insecure = context.get_ctx().is_admin
kw['insecure'] = insecure
return func(*args, **kw)
return _with_insecure
return _decorator
@session_aware()
def get_driver_name(session=None):
return session.bind.url.drivername

View File

@ -186,13 +186,15 @@ def _get_collection_sorted_by_time(model, insecure=False, fields=None,
)
def _get_db_object_by_id(model, id, insecure=False):
@db_base.insecure_aware()
def _get_db_object_by_id(model, id, insecure=None):
query = db_base.model_query(model) if insecure else _secure_query(model)
return query.filter_by(id=id).first()
def _delete_all(model, insecure=False, **kwargs):
@db_base.insecure_aware()
def _delete_all(model, insecure=None, **kwargs):
# NOTE(kong): Because we use 'in_' operator in _secure_query(), delete()
# method will raise error with default parameter. Please refer to
# http://docs.sqlalchemy.org/en/rel_1_0/orm/query.html#sqlalchemy.orm.query.Query.delete
@ -216,6 +218,7 @@ def conditional_update(model, values, expected_values, insecure=False,
return 0 != result
@db_base.insecure_aware()
@db_base.session_aware()
def get_function(id, insecure=False, session=None):
function = _get_db_object_by_id(models.Function, id, insecure=insecure)
@ -262,8 +265,8 @@ def delete_function(id, session=None):
@db_base.session_aware()
def delete_functions(session=None, insecure=False, **kwargs):
return _delete_all(models.Function, insecure=insecure, **kwargs)
def delete_functions(session=None, **kwargs):
return _delete_all(models.Function, **kwargs)
@db_base.session_aware()
@ -304,21 +307,25 @@ def get_runtimes(session=None, **kwargs):
@db_base.session_aware()
def delete_runtime(id, session=None):
# Because we don't allow normal user to delete runtime in api layer, so it
# is safe to get runtime here
runtime = get_runtime(id)
session.delete(runtime)
@db_base.session_aware()
def update_runtime(id, values, session=None):
# Because we don't allow normal user to update runtime in api layer, so it
# is safe to get runtime here
runtime = get_runtime(id)
runtime.update(values.copy())
return runtime
@db_base.insecure_aware()
@db_base.session_aware()
def delete_runtimes(session=None, insecure=False, **kwargs):
def delete_runtimes(session=None, insecure=None, **kwargs):
return _delete_all(models.Runtime, insecure=insecure, **kwargs)
@ -337,9 +344,10 @@ def create_execution(values, session=None):
return execution
@db_base.insecure_aware()
@db_base.session_aware()
def get_execution(id, session=None):
execution = _get_db_object_by_id(models.Execution, id)
def get_execution(id, insecure=None, session=None):
execution = _get_db_object_by_id(models.Execution, id, insecure=insecure)
if not execution:
raise exc.DBEntityNotFoundError("Execution not found [id=%s]" % id)
@ -359,8 +367,9 @@ def delete_execution(id, session=None):
session.delete(execution)
@db_base.insecure_aware()
@db_base.session_aware()
def delete_executions(session=None, insecure=False, **kwargs):
def delete_executions(session=None, insecure=None, **kwargs):
return _delete_all(models.Execution, insecure=insecure, **kwargs)
@ -522,6 +531,7 @@ def get_jobs(session=None, **kwargs):
return _get_collection_sorted_by_time(models.Job, **kwargs)
@db_base.insecure_aware()
@db_base.session_aware()
def delete_jobs(session=None, insecure=False, **kwargs):
def delete_jobs(session=None, insecure=None, **kwargs):
return _delete_all(models.Job, insecure=insecure, **kwargs)

View File

@ -31,4 +31,4 @@ sudo cp $BASE/new/tempest/etc/logging.conf.sample $BASE/new/tempest/etc/logging.
export TOX_TESTENV_PASSENV=ZUUL_PROJECT
(cd $BASE/new/tempest/; sudo -E testr init)
(cd $BASE/new/tempest/; sudo -E tox -eall-plugin qinling)
(cd $BASE/new/tempest/; sudo -E tox -eall-plugin -- qinling --serial)

View File

@ -17,6 +17,7 @@ import zipfile
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions
from qinling_tempest_plugin.tests import base
@ -115,3 +116,56 @@ class FunctionsTest(base.BaseQinlingTest):
resp = self.client.delete_resource('functions', function_id)
self.assertEqual(204, resp.status)
@decorators.idempotent_id('051f3106-df01-4fcd-a0a3-c81c99653163')
def test_get_all_admin(self):
# Create function by normal user
function_name = data_utils.rand_name('function',
prefix=self.name_prefix)
with open(self.python_zip_file, 'rb') as package_data:
resp, body = self.client.create_function(
{"source": "package"},
self.runtime_id,
name=function_name,
package_data=package_data,
entry='%s.main' % self.base_name
)
self.assertEqual(201, resp.status_code)
function_id = body['id']
self.addCleanup(self.client.delete_resource, 'functions',
function_id, ignore_notfound=True)
# Get functions by admin
resp, body = self.admin_client.get_resources('functions')
self.assertEqual(200, resp.status)
self.assertNotIn(
function_id,
[function['id'] for function in body['functions']]
)
# Get other projects functions by admin
resp, body = self.admin_client.get_resources(
'functions?all_projects=true'
)
self.assertEqual(200, resp.status)
self.assertIn(
function_id,
[function['id'] for function in body['functions']]
)
@decorators.idempotent_id('cd396bda-2174-4335-9f7f-2457aab61a4a')
def test_get_all_not_allowed(self):
# Get other projects functions by normal user
context = self.assertRaises(
exceptions.Forbidden,
self.client.get_resources,
'functions?all_projects=true'
)
self.assertIn(
'Operation not allowed',
context.resp_body.get('faultstring')
)