Skip to content

Commit 1943760

Browse files
committed
Authentication for operations on application
user query parameter added for application create, start, stop and delete APIs PNDA-4546
1 parent cb9abcb commit 1943760

File tree

5 files changed

+92
-35
lines changed

5 files changed

+92
-35
lines changed

README.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ Applications may be paused and restarted. This leaves all the installed componen
5959

6060
Each Creator implements a specific set of steps to uninstall components of its associated type. The Creator is passed the application data associated with the package and component and uses this to execute those steps.
6161

62-
6362
# Requirements
6463

6564
* [Maven](https://maven.apache.org/install.html)
@@ -358,22 +357,30 @@ Response Codes:
358357

359358
### Start _application_
360359
````
361-
POST /applications/<application>/start
360+
POST /applications/<application>/start?user=<username>
362361
363362
Response Codes:
364363
202 - Accepted, poll /applications/<application>/status for status
364+
403 - Unauthorised user
365365
404 - Application not known
366366
500 - Server Error
367+
368+
Query Parameters:
369+
user - User with permisson to perform this action on the application should be passed.
367370
````
368371

369372
### Stop _application_
370373
````
371-
POST /applications/<application>/stop
374+
POST /applications/<application>/stop?user=<username>
372375
373376
Response Codes:
374377
202 - Accepted, poll /applications/<application>/status for status
378+
403 - Unauthorised user
375379
404 - Application not known
376380
500 - Server Error
381+
382+
Query Parameters:
383+
user - User with permisson to perform this action on the application should be passed.
377384
````
378385

379386
### Get full information for _application_
@@ -419,9 +426,8 @@ Example response:
419426
### Create _application_ from _package_
420427

421428
````
422-
PUT /applications/<application>
429+
PUT /applications/<application>?user=<username>
423430
{
424-
"user": "<username>",
425431
"package": "<package>",
426432
"<componentType>": {
427433
"<componentName>": {
@@ -437,9 +443,11 @@ Response Codes:
437443
409 - Application already exists
438444
500 - Server Error
439445
446+
Query Parameters:
447+
user - User creating this application should be passed.
448+
440449
Example body:
441450
{
442-
"user": "somebody",
443451
"package": "<package>",
444452
"oozie": {
445453
"example": {
@@ -448,17 +456,21 @@ Example body:
448456
}
449457
}
450458
451-
Package and user are mandatory, property settings are optional
459+
Package is mandatory, property settings are optional
452460
````
453461

454462
### Destroy _application_
455463
````
456-
DELETE /applications/<application>
464+
DELETE /applications/<application>?user=<username>
457465
458466
Response Codes:
459467
200 - OK
468+
403 - Unauthorised user
460469
404 - Application not known
461470
500 - Server Error
471+
472+
Query Parameters:
473+
user - User with permisson to perform this action on the application should be passed.
462474
````
463475

464476
## Environment Endpoints API
@@ -557,3 +569,4 @@ oozie.libpath /pnda/deployment/platform
557569
oozie.use.system.libpath true
558570
user.name prod1
559571
````
572+

api/src/main/resources/app.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
import application_summary_registrar
3838
import deployment_manager
3939
from deployer_system_test import DeployerRestClientTester
40-
from exceptiondef import NotFound, ConflictingState, FailedValidation, FailedCreation, FailedConnection
40+
from exceptiondef import NotFound, ConflictingState, FailedValidation, FailedCreation, FailedConnection, Forbidden
4141
from async_dispatcher import AsyncDispatcher
4242
from package_repo_rest_client import PackageRepoRestClient
4343

@@ -78,6 +78,10 @@ def finish():
7878
logging.info(ex.msg)
7979
self.set_status(400)
8080
self.finish(ex.msg)
81+
elif isinstance(ex, Forbidden):
82+
logging.info(ex.msg)
83+
self.set_status(403)
84+
self.finish(ex.msg)
8185
elif isinstance(ex, FailedCreation):
8286
logging.info(ex.msg)
8387
self.set_status(500)
@@ -220,12 +224,13 @@ def do_call():
220224
class ApplicationDetailHandler(BaseHandler):
221225
@asynchronous
222226
def post(self, name, action):
227+
user_name = self.get_argument("user")
223228
def do_call():
224229
if action == 'start':
225-
dm.start_application(name)
230+
dm.start_application(name, user_name)
226231
self.send_accepted()
227232
elif action == 'stop':
228-
dm.stop_application(name)
233+
dm.stop_application(name, user_name)
229234
self.send_accepted()
230235
else:
231236
self.send_client_error("%s is not a valid action (start|stop)" % action)
@@ -264,11 +269,12 @@ def put(self, aname):
264269
self.send_client_error("Invalid request body. Missing field 'package'")
265270
return
266271

267-
if 'user' not in request_body:
268-
self.send_client_error("Invalid request body. Missing field 'user'")
272+
if 'user' in request_body:
273+
self.send_client_error("Invalid request body. User should be passed in URI")
269274
return
270-
275+
user_name = self.get_argument("user")
271276
def do_call():
277+
request_body.update({'user': user_name})
272278
dm.create_application(request_body['package'], aname, request_body)
273279
self.send_accepted()
274280

@@ -283,8 +289,9 @@ def do_call():
283289

284290
@asynchronous
285291
def delete(self, name):
292+
user_name = self.get_argument("user")
286293
def do_call():
287-
dm.delete_application(name)
294+
dm.delete_application(name, user_name)
288295
self.send_accepted()
289296

290297
DISPATCHER.run_as_asynch(task=do_call, on_error=self.handle_error)

api/src/main/resources/deployment_manager.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import requests
3131

3232
import application_creator
33-
from exceptiondef import ConflictingState, NotFound
33+
from exceptiondef import ConflictingState, NotFound, Forbidden
3434
from package_parser import PackageParser
3535
from async_dispatcher import AsyncDispatcher
3636
from lifecycle_states import ApplicationState, PackageDeploymentState
@@ -268,7 +268,7 @@ def list_applications(self):
268268
applications = self._application_registrar.list_applications()
269269
return applications
270270

271-
def _assert_application_status(self, application, required_status):
271+
def _assert_application_status(self, application, required_status, user_name):
272272
app_info = self.get_application_info(application)
273273
status = app_info['status']
274274

@@ -279,15 +279,21 @@ def _assert_application_status(self, application, required_status):
279279
else:
280280
raise ConflictingState(json.dumps({'status': status}))
281281

282+
if status != ApplicationState.NOTCREATED:
283+
created_user = app_info['overrides']['user']
284+
if user_name != created_user and user_name != self._config["admin_user"]:
285+
information = '%s is not authorized' % user_name
286+
raise Forbidden(json.dumps({'information': information}))
287+
282288
def _assert_application_exists(self, application):
283289
status = self.get_application_info(application)['status']
284290
if status == ApplicationState.NOTCREATED:
285291
raise NotFound(json.dumps({'status': status}))
286292

287-
def start_application(self, application):
293+
def start_application(self, application, user_name):
288294
logging.info('start_application')
289295
with self._lock:
290-
self._assert_application_status(application, ApplicationState.CREATED)
296+
self._assert_application_status(application, ApplicationState.CREATED, user_name)
291297
self._mark_starting(application)
292298

293299
def do_work():
@@ -306,10 +312,10 @@ def do_work():
306312

307313
self.dispatcher.run_as_asynch(task=do_work)
308314

309-
def stop_application(self, application):
315+
def stop_application(self, application, user_name):
310316
logging.info('stop_application')
311317
with self._lock:
312-
self._assert_application_status(application, ApplicationState.STARTED)
318+
self._assert_application_status(application, ApplicationState.STARTED, user_name)
313319
self._mark_stopping(application)
314320

315321
def do_work():
@@ -360,7 +366,7 @@ def create_application(self, package, application, overrides):
360366
package_data_path = None
361367

362368
with self._lock:
363-
self._assert_application_status(application, ApplicationState.NOTCREATED)
369+
self._assert_application_status(application, ApplicationState.NOTCREATED, overrides['user'])
364370
self._assert_package_status(package, PackageDeploymentState.DEPLOYED)
365371
defaults = self.get_package_info(package)['defaults']
366372
package_data_path = self._package_registrar.get_package_data(package)
@@ -404,10 +410,10 @@ def _handle_application_error(self, application, ex, app_status, operation):
404410
# set the status:
405411
self._application_registrar.set_application_status(application, app_status, error_message)
406412

407-
def delete_application(self, application):
413+
def delete_application(self, application, user_name):
408414
logging.info('delete_application')
409415
with self._lock:
410-
self._assert_application_status(application, [ApplicationState.CREATED, ApplicationState.STARTED])
416+
self._assert_application_status(application, [ApplicationState.CREATED, ApplicationState.STARTED], user_name)
411417
self._mark_destroying(application)
412418

413419
def do_work():

api/src/main/resources/exceptiondef.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ def __init__(self, arg):
3838
super(NotFound, self).__init__(arg)
3939
self.msg = arg
4040

41+
class Forbidden(DmException):
42+
43+
def __init__(self, arg):
44+
super(Forbidden, self).__init__(arg)
45+
self.msg = arg
46+
4147

4248
class ConflictingState(DmException):
4349

api/src/main/resources/test_deployer_manager.py

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from multiprocessing import Event
2626
from mock import Mock, patch, mock_open
2727
from deployment_manager import DeploymentManager
28-
from exceptiondef import NotFound, ConflictingState, FailedValidation
28+
from exceptiondef import NotFound, ConflictingState, FailedValidation, Forbidden
2929
from lifecycle_states import ApplicationState, PackageDeploymentState
3030

3131

@@ -75,15 +75,15 @@ def setUp(self):
7575
'queue_policy': 'echo dev',
7676
'namespace': 'mockspace'
7777
}
78-
self.mock_config = {"deployer_thread_limit": 1, 'stage_root': 'stage', 'plugins_path': 'plugins'}
78+
self.mock_config = {"deployer_thread_limit": 1, 'stage_root': 'stage', 'plugins_path': 'plugins', 'admin_user': 'username'}
7979
# mock app registrar:
8080
mock_application_registar = Mock()
8181
application_data = {}
8282
mock_application_registar.application_has_record = lambda app: app in application_data
8383
mock_application_registar.get_application = lambda app: application_data.get(app, None)
8484
mock_application_registar.set_application_status = \
8585
lambda app, status, info=None: set_dictionary_value(application_data, app,
86-
{"status": status, "information": info})
86+
{"status": status, "information": info, 'overrides':{'user':'username'}})
8787
self.mock_application_registar = mock_application_registar
8888
self.mock_summary_registar = Mock()
8989

@@ -342,7 +342,7 @@ def _assert_package_status(self, package, required_status):
342342

343343
self.mock_application_registar.set_application_status(self.test_app_name, ApplicationState.CREATED)
344344
# launch the asynch test
345-
deployment_manager.start_application(self.test_app_name)
345+
deployment_manager.start_application(self.test_app_name, 'username')
346346
# wait for test to finsish
347347
on_complete.wait(5)
348348
self.assertIsNotNone(test_result[0], "async task completed")
@@ -381,7 +381,7 @@ def _assert_package_status(self, package, required_status):
381381

382382
self.mock_application_registar.set_application_status(self.test_app_name, ApplicationState.STARTED)
383383
# launch the asynch test
384-
deployment_manager.delete_application(self.test_app_name)
384+
deployment_manager.delete_application(self.test_app_name, 'username')
385385
# wait for test to finsish
386386
on_complete.wait(5)
387387
self.assertIsNotNone(test_result[0], "async task completed")
@@ -420,7 +420,7 @@ def _assert_package_status(self, package, required_status):
420420

421421
self.mock_application_registar.set_application_status(self.test_app_name, ApplicationState.STARTED)
422422
# launch the asynch test
423-
deployment_manager.stop_application(self.test_app_name)
423+
deployment_manager.stop_application(self.test_app_name, 'username')
424424
# wait for test to finsish
425425
on_complete.wait(5)
426426
self.assertIsNotNone(test_result[0], "async task completed")
@@ -458,7 +458,7 @@ def create_mocks(self):
458458

459459
# launch the asynch test
460460
self.mock_application_registar.set_application_status(self.test_app_name, "STARTED")
461-
deployment_manager.stop_application(self.test_app_name)
461+
deployment_manager.stop_application(self.test_app_name, 'username')
462462
# wait for test to finsish
463463
on_complete.wait(5)
464464
self.assertIsNotNone(test_result[0], "async task completed")
@@ -731,7 +731,7 @@ def test_application_in_progress(self):
731731
'information': None}
732732
application_summary_registrar = Mock()
733733
environment = {"namespace": "some_namespace", 'webhdfs_host': 'webhdfshost', 'webhdfs_port': 'webhdfsport'}
734-
config = {"deployer_thread_limit": 10}
734+
config = {"deployer_thread_limit": 10, 'admin_user':'username'}
735735

736736
dmgr = DeploymentManager(repository,
737737
package_registrar,
@@ -740,15 +740,15 @@ def test_application_in_progress(self):
740740
environment,
741741
config)
742742

743-
self.assertRaises(ConflictingState, dmgr.start_application, "name")
743+
self.assertRaises(ConflictingState, dmgr.start_application, "name", 'username')
744744

745745
def test_package_in_progress(self):
746746
repository = Mock()
747747
package_registrar = Mock()
748748
application_registrar = Mock()
749749
application_summary_registrar = Mock()
750750
environment = {"namespace": "some_namespace", 'webhdfs_host': 'webhdfshost', 'webhdfs_port': 'webhdfsport'}
751-
config = {"deployer_thread_limit": 10}
751+
config = {"deployer_thread_limit": 10, 'admin_user':'username'}
752752

753753
class DeploymentManagerTester(DeploymentManager):
754754
def set_package_progress(self, package, state):
@@ -772,7 +772,7 @@ def test_package_not_exists(self):
772772
application_registrar = Mock()
773773
application_summary_registrar = Mock()
774774
environment = {"namespace": "some_namespace", 'webhdfs_host': 'webhdfshost', 'webhdfs_port': 'webhdfsport'}
775-
config = {"deployer_thread_limit": 10}
775+
config = {"deployer_thread_limit": 10, 'admin_user':'username'}
776776
dmgr = DeploymentManager(repository,
777777
package_registrar,
778778
application_registrar,
@@ -858,3 +858,28 @@ def test_get_application_summary(self):
858858
config)
859859

860860
self.assertEqual(dmgr.get_application_summary('name'), {'name':{'aggregate_status': 'COMPLETED_WITH_NO_FAILURES', 'component-1': {}}})
861+
862+
def test_unauthorized_user_start(self):
863+
repository = Mock()
864+
package_registrar = Mock()
865+
application_registrar = Mock()
866+
application_registrar.get_application.return_value = {
867+
'overrides': {"user":"user2"},
868+
'defaults': {},
869+
'name': 'name',
870+
'package_name': 'package_name',
871+
'status': ApplicationState.CREATED,
872+
'information': None}
873+
application_registrar.get_application_user.return_value = 'user2'
874+
application_summary_registrar = Mock()
875+
environment = {"namespace": "some_namespace", 'webhdfs_host': 'webhdfshost', 'webhdfs_port': 'webhdfsport'}
876+
config = {"deployer_thread_limit": 10, "admin_user": "admin"}
877+
878+
dmgr = DeploymentManager(repository,
879+
package_registrar,
880+
application_registrar,
881+
application_summary_registrar,
882+
environment,
883+
config)
884+
885+
self.assertRaises(Forbidden, dmgr.start_application, "name", 'user1')

0 commit comments

Comments
 (0)