# -*- 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/$uid') resources = ''; uid_posfix = '/' 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)