Support admin user get functions of other projects
Partially implements: blueprint qinling-admin-operations Change-Id: I7192c0006fd82e62f751323cc9707479040f5764
This commit is contained in:
parent
7ba5676ec8
commit
2778c1cc3b
@ -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",
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user