# -*- coding: utf-8 -*- from openerp import http from openerp.http import request from passlib.context import CryptContext from werkzeug.wrappers import Response import os import jwt import 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): # -------------------------------------------------------------------------- # Restify your request # -------------------------------------------------------------------------- @http.route([ '/api/', '/api//' ], type = 'http', auth = 'none', cors = '*') def restify(self, **args): print args print "----------------------------------------------------------------" 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 = 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.dump()) 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, filters = self.resource_inflater(data['resource']) data = self.digest_data(data) try: result = request.env[model].sudo().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, filters = self.resource_inflater(data['resource']) data = self.digest_data(data) try: result = request.env[model].sudo().browse(uid) if not result.exists(): return self.make_response({'error': 'cannot be updated'}) self.make_info_log('To update object') return self.make_response({'response': result.sudo().write(data)}) except Exception, 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'] 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, filters = self.resource_inflater(resource) 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') return True except Exception, 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 = RESOURCES_MAP[resource]['model'] filters = [] for i in range(len(RESOURCES_MAP[resource]['filters'])): filters.append(tuple(RESOURCES_MAP[resource]['filters'][i])) self.make_info_log('Successfully resource inflated') return (model, filters) except Exception, e: self.make_error_log('Cannot inflate resource') return (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)