1 # -*- coding: utf-8 -*-
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
17 # Util functions previously in `httpserver`
20 __author__
= "Alfonso Tierno, Gerardo Garcia"
27 from jsonschema
import exceptions
as js_e
28 from jsonschema
import validate
as js_v
30 from . import errors
as httperrors
31 from io
import TextIOWrapper
33 logger
= logging
.getLogger('openmano.http')
36 def remove_clear_passwd(data
):
38 Removes clear passwords from the data received
39 :param data: data with clear password
40 :return: data without the password information
43 passw
= ['password: ', 'passwd: ']
46 init
= data
.find(pattern
)
48 end
= data
.find('\n', init
)
49 data
= data
[:init
] + '{}******'.format(pattern
) + data
[end
:]
51 init
= data
.find(pattern
, init
)
55 def change_keys_http2db(data
, http_db
, reverse
=False):
56 '''Change keys of dictionary data acording to the key_dict values
57 This allow change from http interface names to database names.
58 When reverse is True, the change is otherwise
60 data: can be a dictionary or a list
61 http_db: is a dictionary with hhtp names as keys and database names as value
62 reverse: by default change is done from http api to database.
63 If True change is done otherwise.
64 Return: None, but data is modified'''
65 if type(data
) is tuple or type(data
) is list:
67 change_keys_http2db(d
, http_db
, reverse
)
68 elif type(data
) is dict or type(data
) is bottle
.FormsDict
:
70 for k
,v
in http_db
.items():
71 if v
in data
: data
[k
]=data
.pop(v
)
73 for k
,v
in http_db
.items():
74 if k
in data
: data
[v
]=data
.pop(k
)
78 '''Return string of dictionary data according to requested json, yaml, xml.
81 logger
.debug("OUT: " + yaml
.safe_dump(data
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False, allow_unicode
=True) )
82 accept
= bottle
.request
.headers
.get('Accept')
83 if accept
and 'application/yaml' in accept
:
84 bottle
.response
.content_type
='application/yaml'
85 return yaml
.safe_dump(
86 data
, explicit_start
=True, indent
=4, default_flow_style
=False,
87 tags
=False, allow_unicode
=True) #, canonical=True, default_style='"'
88 else: #by default json
89 bottle
.response
.content_type
='application/json'
90 #return data #json no style
91 return json
.dumps(data
, indent
=4) + "\n"
94 def format_in(default_schema
, version_fields
=None, version_dict_schema
=None, confidential_data
=False):
96 Parse the content of HTTP request against a json_schema
98 :param default_schema: The schema to be parsed by default
99 if no version field is found in the client data.
100 In None no validation is done
101 :param version_fields: If provided it contains a tuple or list with the
102 fields to iterate across the client data to obtain the version
103 :param version_dict_schema: It contains a dictionary with the version as key,
104 and json schema to apply as value.
105 It can contain a None as key, and this is apply
106 if the client data version does not match any key
107 :return: user_data, used_schema: if the data is successfully decoded and
110 Launch a bottle abort if fails
112 #print "HEADERS :" + str(bottle.request.headers.items())
114 error_text
= "Invalid header format "
115 format_type
= bottle
.request
.headers
.get('Content-Type', 'application/json')
116 if 'application/json' in format_type
:
117 error_text
= "Invalid json format "
118 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
119 client_data
= json
.load(TextIOWrapper(bottle
.request
.body
, encoding
="utf-8")) # TODO py3
120 #client_data = bottle.request.json()
121 elif 'application/yaml' in format_type
:
122 error_text
= "Invalid yaml format "
123 client_data
= yaml
.load(bottle
.request
.body
, Loader
=yaml
.Loader
)
124 elif 'application/xml' in format_type
:
125 bottle
.abort(501, "Content-Type: application/xml not supported yet.")
127 logger
.warning('Content-Type ' + str(format_type
) + ' not supported.')
128 bottle
.abort(httperrors
.Not_Acceptable
, 'Content-Type ' + str(format_type
) + ' not supported.')
130 # if client_data == None:
131 # bottle.abort(httperrors.Bad_Request, "Content error, empty")
133 if confidential_data
:
134 logger
.info('IN: %s', remove_clear_passwd (yaml
.safe_dump(client_data
, explicit_start
=True, indent
=4, default_flow_style
=False,
135 tags
=False, allow_unicode
=True)))
137 logger
.info('IN: %s', yaml
.safe_dump(client_data
, explicit_start
=True, indent
=4, default_flow_style
=False,
138 tags
=False, allow_unicode
=True) )
139 # look for the client provider version
140 error_text
= "Invalid content "
141 if not default_schema
and not version_fields
:
142 return client_data
, None
143 client_version
= None
145 if version_fields
!= None:
146 client_version
= client_data
147 for field
in version_fields
:
148 if field
in client_version
:
149 client_version
= client_version
[field
]
153 if client_version
== None:
154 used_schema
= default_schema
155 elif version_dict_schema
!= None:
156 if client_version
in version_dict_schema
:
157 used_schema
= version_dict_schema
[client_version
]
158 elif None in version_dict_schema
:
159 used_schema
= version_dict_schema
[None]
160 if used_schema
==None:
161 bottle
.abort(httperrors
.Bad_Request
, "Invalid schema version or missing version field")
163 js_v(client_data
, used_schema
)
164 return client_data
, used_schema
165 except (TypeError, ValueError, yaml
.YAMLError
) as exc
:
166 error_text
+= str(exc
)
167 logger
.error(error_text
)
168 bottle
.abort(httperrors
.Bad_Request
, error_text
)
169 except js_e
.ValidationError
as exc
:
171 "validate_in error, jsonschema exception")
173 if len(exc
.path
)>0: error_pos
=" at " + ":".join(map(json
.dumps
, exc
.path
))
174 bottle
.abort(httperrors
.Bad_Request
, error_text
+ exc
.message
+ error_pos
)
176 # bottle.abort(httperrors.Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
179 def filter_query_string(qs
, http2db
, allowed
):
180 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
182 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
183 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
184 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
185 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
186 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
187 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
188 limit: limit dictated by user with the query string 'limit'. 100 by default
189 abort if not permited, using bottel.abort
194 #if type(qs) is not bottle.FormsDict:
195 # bottle.abort(httperrors.Internal_Server_Error, '!!!!!!!!!!!!!!invalid query string not a dictionary')
196 # #bottle.abort(httperrors.Internal_Server_Error, "call programmer")
199 select
+= qs
.getall(k
)
202 bottle
.abort(httperrors
.Bad_Request
, "Invalid query string at 'field="+v
+"'")
207 bottle
.abort(httperrors
.Bad_Request
, "Invalid query string at 'limit="+qs
[k
]+"'")
210 bottle
.abort(httperrors
.Bad_Request
, "Invalid query string at '"+k
+"="+qs
[k
]+"'")
211 if qs
[k
]!="null": where
[k
]=qs
[k
]
213 if len(select
)==0: select
+= allowed
214 #change from http api to database naming
215 for i
in range(0,len(select
)):
217 if http2db
and k
in http2db
:
218 select
[i
] = http2db
[k
]
220 change_keys_http2db(where
, http2db
)
221 #print "filter_query_string", select,where,limit
223 return select
,where
,limit