123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- # -*- coding: utf-8 -*-
- from openerp import http
- from openerp.http import request
- from passlib.context import CryptContext
- from werkzeug.wrappers import Response
- from string import Template
- import os
- import jwt
- import simplejson as json
- import logging
- RESOURCES_MAP = None
- JWT_SECRET_KEY = '@MjSk$2016?'
- JWT_HEADER = 'Authorization'
- JWT_HEADER_PREFIX = 'JWT'
- CRYPT_CONTEXT = CryptContext(['pbkdf2_sha512', 'md5_crypt'], deprecated=['md5_crypt'])
- LOGGER = logging.getLogger(__name__)
- with open(os.path.dirname(__file__) + '/resources.json') as resources:
- RESOURCES_MAP = json.load(resources)
- '''
- Class for manage authentication
- '''
- class Auth(http.Controller):
- # --------------------------------------------------------------------------
- # Generate JWT token based on username and password field
- # --------------------------------------------------------------------------
- @http.route(['/api/jwt'], type = 'http', auth = 'none', methods = ['POST'], cors = '*')
- def get_jwt(self, **args):
- try:
- user = request.env['res.users'].sudo().search([('login', '=', args['username']), ('active', '=', True)])
- if not user:
- self.make_warn_log('Invalid user received')
- return self.make_response({ 'error': 'Invalid user' }, 400) # bad request
- if not self.get_crypt_context().verify(args['password'], user.password_crypt):
- self.make_warn_log('invalid password received')
- return self.make_response({ 'error': 'Invalid password' }, 400) # bad request
- payload = {
- 'uid': user.id,
- 'password': args['password']
- }
- encoded = jwt.encode(payload, JWT_SECRET_KEY, algorithm = 'HS256')
- user.write({ 'jwt_token': encoded })
- self.make_info_log('To send token')
- return self.make_response({'token': encoded})
- except Exception, e:
- self.make_error_log('Fields required to generate token')
- return self.make_response({ 'error': 'fields required' }, 400) # bad request
- # --------------------------------------------------------------------------
- # Check JWT token auth
- # --------------------------------------------------------------------------
- @http.route(['/api/check'], type = 'http', auth = 'none', methods = ['POST'], cors = '*')
- def check_token(self, **args):
- try:
- user = request.env['res.users'].sudo().search([('jwt_token', '=', args['token'])])
- if not user:
- self.make_warn_log('Invalid token received')
- return self.make_response({ 'error' : 'invalid token' }, 400) # bad request
- decoded = jwt.decode(args['token'], JWT_SECRET_KEY, algorithms = ['HS256'])
- if not self.get_crypt_context().verify(decoded['password'], user.password_crypt):
- self.make_warn_log('Invalid token received')
- return self.make_response({ 'error' : 'invalid token' }, 400) # bad request
- self.make_info_log('Token received is valid')
- return self.make_response({ 'token': 'valid' })
- except Exception, e:
- self.make_error_log('Token not received')
- return self.make_response({ 'error': 'token required' }, 400) # bad request
- # --------------------------------------------------------------------------
- # Get context for encryption
- # --------------------------------------------------------------------------
- def get_crypt_context(self):
- return CRYPT_CONTEXT
- # --------------------------------------------------------------------------
- # Make JSON response
- # --------------------------------------------------------------------------
- def make_response(self, data, status = 200):
- return Response(json.dumps(data), status = status, content_type = 'application/json')
- # --------------------------------------------------------------------------
- # Make log for warnings
- # --------------------------------------------------------------------------
- def make_warn_log(self, log):
- LOGGER.warning(log)
- # --------------------------------------------------------------------------
- # Make log for infos
- # --------------------------------------------------------------------------
- def make_info_log(self, log):
- LOGGER.info(log)
- # --------------------------------------------------------------------------
- # Make log for errors
- # --------------------------------------------------------------------------
- def make_error_log(self, log):
- LOGGER.error(log)
- '''
- Class for manage rest api interaction
- '''
- class ApiManager(http.Controller):
- # --------------------------------------------------------------------------
- # Generate routes from resources
- # --------------------------------------------------------------------------
- def routify():
- url_tmpl = Template('/api/<any($resources):resource>$uid')
- resources = '';
- uid_posfix = '/<int:uid>'
- for resource in dict(RESOURCES_MAP):
- resources += str(resource) + ','
- return [
- url_tmpl.substitute({ 'resources': resources[:-1], 'uid': '' }),
- url_tmpl.substitute({ 'resources': resources[:-1], 'uid': uid_posfix })
- ]
- # --------------------------------------------------------------------------
- # Restify your request
- # --------------------------------------------------------------------------
- @http.route(routify(),
- type = 'http',
- auth = 'none',
- cors = '*')
- def restify(self, **args):
- if not self.valid_token():
- return self.make_response({ 'error': 'unauthorized resource' }, 401) # access denied
- if not self.resource_exists(args['resource']):
- return self.make_response({ 'error': 'resource not available' }, 404) # not found
- http_verb = request.httprequest.method
- if http_verb == 'GET':
- return self.http_get(args)
- if http_verb == 'POST':
- return self.http_post(args)
- if http_verb == 'PUT' or http_verb == 'PATCH':
- return self.http_put_or_patch(args)
- if http_verb == 'DELETE':
- return self.http_delete(args)
- self.make_warn_log('Request method not allowed')
- return self.make_response({ 'error': 'method not allowed' }, 405) # method not allowed
- # --------------------------------------------------------------------------
- # Manage GET request
- # --------------------------------------------------------------------------
- def http_get(self, data):
- if len(data) > 2:
- return self.make_response({ 'error': 'cannot be process request' }, 400) # bad request
- resource = data['resource']
- model, filters, exclude = self.resource_inflater(resource)
- response_data = []
- if 'uid' in data:
- filters.append(('id', '=', data['uid']))
- result = request.env[model].sudo().search(filters)
- for item in result:
- response_data.append(item.serialize(exclude))
- self.make_info_log('To send data response')
- return self.make_response(response_data)
- # --------------------------------------------------------------------------
- # Manage POST request
- # --------------------------------------------------------------------------
- def http_post(self, data):
- if len(data) <= 1 or 'uid' in data:
- return self.make_response({ 'error': 'cannot be process request' }, 400) # bad request
- model = self.resource_inflater(data['resource'])[0]
- data = self.digest_data(data)
- try:
- print '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> POST'
- result = request.env[model].with_context(request.context).create(data)
- return self.make_response(result.id)
- except Exception, e:
- return self.make_response({' response': False })
- # --------------------------------------------------------------------------
- # Manage PUT or PATCH request
- # --------------------------------------------------------------------------
- def http_put_or_patch(self, data):
- if len(data) <= 2 or not 'uid' in data:
- return self.make_response({ 'error': 'cannot be process request' }, 400) # bad request
- uid = data['uid']
- model = self.resource_inflater(data['resource'])[0]
- data = self.digest_data(data)
- try:
- model = request.env[model]
- result = model.sudo().browse(uid)
- if not result.exists():
- return self.make_response({ 'error': 'cannot be updated' })
- self.make_info_log('To update object')
- for item in result:
- deserialized_data = item.deserialize("update", data)
- ok = item.write(deserialized_data)
- if not ok:
- raise Exception('Cannot write')
- return self.make_response({ 'response': True })
- except Exception, e:
- print e
- return self.make_response({ 'response': False })
- # --------------------------------------------------------------------------
- # Digest data for POST request
- # --------------------------------------------------------------------------
- def digest_data(self, data):
- data = dict(data)
- if 'resource' in data:
- del data['resource']
- if 'uid' in data:
- del data['uid']
- if 'remote_id' in data:
- del data['remote_id']
- return data
- # --------------------------------------------------------------------------
- # Manage DELETE request
- # --------------------------------------------------------------------------
- def http_delete(self, data):
- if len(data) > 2 or not 'uid' in data:
- return self.make_response({ 'error': 'cannot be process request' }, 400) # bad request
- resource = data['resource']
- uid = data['uid']
- model = self.resource_inflater(resource)[0]
- result = request.env[model].sudo().browse(uid)
- if not result.exists():
- return self.make_response({ 'error': 'cannot be deleted' })
- self.make_info_log('To delete object')
- return self.make_response({ 'response': result.sudo().unlink() })
- # --------------------------------------------------------------------------
- # Make JSON response
- # --------------------------------------------------------------------------
- def make_response(self, data, status = 200):
- headers = [('Content-Type', 'application/json')]
- if status == 401:
- headers.append(('WWW-Authenticate', 'JWT'))
- return Response(json.dumps(data), status = status, headers = headers)
- # --------------------------------------------------------------------------
- # Check if resource is available
- # --------------------------------------------------------------------------
- def resource_exists(self, resource):
- try:
- model = RESOURCES_MAP[resource]['module']
- module_name = model.replace('.', '_')
- module = request.env['ir.module.module'].sudo().search([('name', '=', module_name)])
- self.make_info_log('To check resource availability')
- return True if module.state == 'installed' and len(module) != 0 else False
- except Exception, e:
- self.make_error_log('Requested resource is not available')
- return False;
- # --------------------------------------------------------------------------
- # Manage JWT token validity
- # --------------------------------------------------------------------------
- def valid_token(self):
- try:
- # print request.httprequest.user_agent.browser
- auth_header = request.httprequest.headers[JWT_HEADER]
- if not auth_header.startswith(JWT_HEADER_PREFIX):
- return False
- jwt_token = auth_header[4:]
- if not jwt_token:
- return False
- user = request.env['res.users'].sudo().search([('jwt_token', '=', jwt_token)])
- if not user:
- return False
- decoded = self.decode_token(jwt_token)
- if not self.get_crypt_context().verify(decoded['password'], user.password_crypt):
- return False
- self.make_info_log('Token is valid')
- request.context['lang'] = user.lang
- request.session['context']['lang'] = user.lang
- request.session['uid'] = user.id
- return True
- except Exception, e:
- print e
- self.make_error_log('Token is not valid')
- return False
- # --------------------------------------------------------------------------
- # Decode provide JWT token
- # --------------------------------------------------------------------------
- def decode_token(self, jwt_token):
- return jwt.decode(jwt_token, JWT_SECRET_KEY, algorithms = ['HS256'])
- # --------------------------------------------------------------------------
- # Manage GET request
- # --------------------------------------------------------------------------
- def resource_inflater(self, resource):
- try:
- model = str(RESOURCES_MAP[resource]['model'])
- filters = [tuple(f) for f in list(RESOURCES_MAP[resource]['filters'])]
- exclude = list(RESOURCES_MAP[resource]['exclude'])
- self.make_info_log('Successfully resource inflated')
- return (model, filters, exclude)
- except Exception, e:
- self.make_error_log('Cannot inflate resource')
- return (None, None, None)
- # --------------------------------------------------------------------------
- # Get context for encryption
- # --------------------------------------------------------------------------
- def get_crypt_context(self):
- return CRYPT_CONTEXT
- # --------------------------------------------------------------------------
- # Make log for warnings
- # --------------------------------------------------------------------------
- def make_warn_log(self, log):
- LOGGER.warning(log)
- # --------------------------------------------------------------------------
- # Make log for infos
- # --------------------------------------------------------------------------
- def make_info_log(self, log):
- LOGGER.info(log)
- # --------------------------------------------------------------------------
- # Make log for errors
- # --------------------------------------------------------------------------
- def make_error_log(self, log):
- LOGGER.error(log)
|