Coverage for watcher/api/hooks.py: 100%

34 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-17 12:22 +0000

1# -*- encoding: utf-8 -*- 

2# 

3# Copyright © 2012 New Dream Network, LLC (DreamHost) 

4# 

5# Licensed under the Apache License, Version 2.0 (the "License"); you may 

6# not use this file except in compliance with the License. You may obtain 

7# a copy of the License at 

8# 

9# http://www.apache.org/licenses/LICENSE-2.0 

10# 

11# Unless required by applicable law or agreed to in writing, software 

12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

14# License for the specific language governing permissions and limitations 

15# under the License. 

16 

17 

18from http import HTTPStatus 

19from oslo_config import cfg 

20from pecan import hooks 

21 

22from watcher.common import context 

23 

24 

25class ContextHook(hooks.PecanHook): 

26 """Configures a request context and attaches it to the request. 

27 

28 The following HTTP request headers are used: 

29 

30 X-User: 

31 Used for context.user. 

32 

33 X-User-Id: 

34 Used for context.user_id. 

35 

36 X-Project-Name: 

37 Used for context.project. 

38 

39 X-Project-Id: 

40 Used for context.project_id. 

41 

42 X-Auth-Token: 

43 Used for context.auth_token. 

44 

45 """ 

46 

47 def before(self, state): 

48 headers = state.request.headers 

49 user = headers.get('X-User') 

50 user_id = headers.get('X-User-Id') 

51 project = headers.get('X-Project-Name') 

52 project_id = headers.get('X-Project-Id') 

53 domain_id = headers.get('X-User-Domain-Id') 

54 domain_name = headers.get('X-User-Domain-Name') 

55 auth_token = headers.get('X-Storage-Token') 

56 auth_token = headers.get('X-Auth-Token', auth_token) 

57 show_deleted = headers.get('X-Show-Deleted') 

58 auth_token_info = state.request.environ.get('keystone.token_info') 

59 roles = (headers.get('X-Roles', None) and 

60 headers.get('X-Roles').split(',')) 

61 

62 state.request.context = context.make_context( 

63 auth_token=auth_token, 

64 auth_token_info=auth_token_info, 

65 user=user, 

66 user_id=user_id, 

67 project=project, 

68 project_id=project_id, 

69 domain_id=domain_id, 

70 domain_name=domain_name, 

71 show_deleted=show_deleted, 

72 roles=roles) 

73 

74 

75class NoExceptionTracebackHook(hooks.PecanHook): 

76 """Workaround rpc.common: deserialize_remote_exception. 

77 

78 deserialize_remote_exception builds rpc exception traceback into error 

79 message which is then sent to the client. Such behavior is a security 

80 concern so this hook is aimed to cut-off traceback from the error message. 

81 """ 

82 # NOTE(max_lobur): 'after' hook used instead of 'on_error' because 

83 # 'on_error' never fired for wsme+pecan pair. wsme @wsexpose decorator 

84 # catches and handles all the errors, so 'on_error' dedicated for unhandled 

85 # exceptions never fired. 

86 

87 def after(self, state): 

88 # Omit empty body. Some errors may not have body at this level yet. 

89 if not state.response.body: 

90 return 

91 

92 # Do nothing if there is no error. 

93 # Status codes in the range 200 (OK) to 399 (400 = BAD_REQUEST) are not 

94 # an error. 

95 if (HTTPStatus.OK <= state.response.status_int < 

96 HTTPStatus.BAD_REQUEST): 

97 return 

98 

99 json_body = state.response.json 

100 # Do not remove traceback when traceback config is set 

101 if cfg.CONF.debug: 

102 return 

103 

104 faultstring = json_body.get('faultstring') 

105 traceback_marker = 'Traceback (most recent call last):' 

106 if faultstring and traceback_marker in faultstring: 

107 # Cut-off traceback. 

108 faultstring = faultstring.split(traceback_marker, 1)[0] 

109 # Remove trailing newlines and spaces if any. 

110 json_body['faultstring'] = faultstring.rstrip() 

111 # Replace the whole json. Cannot change original one because it's 

112 # generated on the fly. 

113 state.response.json = json_body