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:create": "rule:context_is_admin",
|
||||||
"runtime:update": "rule:context_is_admin",
|
"runtime:update": "rule:context_is_admin",
|
||||||
"runtime:delete": "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
|
from webob.static import FileIter
|
||||||
import wsmeext.pecan as wsme_pecan
|
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 resources
|
||||||
from qinling.api.controllers.v1 import types
|
from qinling.api.controllers.v1 import types
|
||||||
from qinling import context
|
from qinling import context
|
||||||
@ -175,12 +176,32 @@ class FunctionsController(rest.RestController):
|
|||||||
return resources.Function.from_dict(func_db.to_dict()).to_dict()
|
return resources.Function.from_dict(func_db.to_dict()).to_dict()
|
||||||
|
|
||||||
@rest_utils.wrap_wsme_controller_exception
|
@rest_utils.wrap_wsme_controller_exception
|
||||||
@wsme_pecan.wsexpose(resources.Functions)
|
@wsme_pecan.wsexpose(resources.Functions, bool, types.uuid)
|
||||||
def get_all(self):
|
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.")
|
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())
|
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)
|
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 import options as db_options
|
||||||
from oslo_db.sqlalchemy import session as db_session
|
from oslo_db.sqlalchemy import session as db_session
|
||||||
|
|
||||||
|
from qinling import context
|
||||||
from qinling.db.sqlalchemy import sqlite_lock
|
from qinling.db.sqlalchemy import sqlite_lock
|
||||||
from qinling import exceptions as exc
|
from qinling import exceptions as exc
|
||||||
from qinling.utils import thread_local
|
from qinling.utils import thread_local
|
||||||
@ -159,6 +160,22 @@ def session_aware():
|
|||||||
return _decorator
|
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()
|
@session_aware()
|
||||||
def get_driver_name(session=None):
|
def get_driver_name(session=None):
|
||||||
return session.bind.url.drivername
|
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)
|
query = db_base.model_query(model) if insecure else _secure_query(model)
|
||||||
|
|
||||||
return query.filter_by(id=id).first()
|
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()
|
# NOTE(kong): Because we use 'in_' operator in _secure_query(), delete()
|
||||||
# method will raise error with default parameter. Please refer to
|
# 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
|
# 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
|
return 0 != result
|
||||||
|
|
||||||
|
|
||||||
|
@db_base.insecure_aware()
|
||||||
@db_base.session_aware()
|
@db_base.session_aware()
|
||||||
def get_function(id, insecure=False, session=None):
|
def get_function(id, insecure=False, session=None):
|
||||||
function = _get_db_object_by_id(models.Function, id, insecure=insecure)
|
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()
|
@db_base.session_aware()
|
||||||
def delete_functions(session=None, insecure=False, **kwargs):
|
def delete_functions(session=None, **kwargs):
|
||||||
return _delete_all(models.Function, insecure=insecure, **kwargs)
|
return _delete_all(models.Function, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@db_base.session_aware()
|
@db_base.session_aware()
|
||||||
@ -304,21 +307,25 @@ def get_runtimes(session=None, **kwargs):
|
|||||||
|
|
||||||
@db_base.session_aware()
|
@db_base.session_aware()
|
||||||
def delete_runtime(id, session=None):
|
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)
|
runtime = get_runtime(id)
|
||||||
|
|
||||||
session.delete(runtime)
|
session.delete(runtime)
|
||||||
|
|
||||||
|
|
||||||
@db_base.session_aware()
|
@db_base.session_aware()
|
||||||
def update_runtime(id, values, session=None):
|
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 = get_runtime(id)
|
||||||
runtime.update(values.copy())
|
runtime.update(values.copy())
|
||||||
|
|
||||||
return runtime
|
return runtime
|
||||||
|
|
||||||
|
|
||||||
|
@db_base.insecure_aware()
|
||||||
@db_base.session_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)
|
return _delete_all(models.Runtime, insecure=insecure, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@ -337,9 +344,10 @@ def create_execution(values, session=None):
|
|||||||
return execution
|
return execution
|
||||||
|
|
||||||
|
|
||||||
|
@db_base.insecure_aware()
|
||||||
@db_base.session_aware()
|
@db_base.session_aware()
|
||||||
def get_execution(id, session=None):
|
def get_execution(id, insecure=None, session=None):
|
||||||
execution = _get_db_object_by_id(models.Execution, id)
|
execution = _get_db_object_by_id(models.Execution, id, insecure=insecure)
|
||||||
|
|
||||||
if not execution:
|
if not execution:
|
||||||
raise exc.DBEntityNotFoundError("Execution not found [id=%s]" % id)
|
raise exc.DBEntityNotFoundError("Execution not found [id=%s]" % id)
|
||||||
@ -359,8 +367,9 @@ def delete_execution(id, session=None):
|
|||||||
session.delete(execution)
|
session.delete(execution)
|
||||||
|
|
||||||
|
|
||||||
|
@db_base.insecure_aware()
|
||||||
@db_base.session_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)
|
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)
|
return _get_collection_sorted_by_time(models.Job, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@db_base.insecure_aware()
|
||||||
@db_base.session_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)
|
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
|
export TOX_TESTENV_PASSENV=ZUUL_PROJECT
|
||||||
(cd $BASE/new/tempest/; sudo -E testr init)
|
(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.common.utils import data_utils
|
||||||
from tempest.lib import decorators
|
from tempest.lib import decorators
|
||||||
|
from tempest.lib import exceptions
|
||||||
|
|
||||||
from qinling_tempest_plugin.tests import base
|
from qinling_tempest_plugin.tests import base
|
||||||
|
|
||||||
@ -115,3 +116,56 @@ class FunctionsTest(base.BaseQinlingTest):
|
|||||||
resp = self.client.delete_resource('functions', function_id)
|
resp = self.client.delete_resource('functions', function_id)
|
||||||
|
|
||||||
self.assertEqual(204, resp.status)
|
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