mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[ADD] pms_api_rest: module created
This commit is contained in:
committed by
Darío Lodeiros
parent
75f453adec
commit
78da297eb4
@@ -7,8 +7,8 @@ from odoo.http import request
|
|||||||
|
|
||||||
from odoo.addons.auth_signup.models.res_users import SignupError
|
from odoo.addons.auth_signup.models.res_users import SignupError
|
||||||
|
|
||||||
from ..lib.jwt_http import jwt_http
|
from ..lib_jwt.jwt_http import jwt_http
|
||||||
from ..lib.validator import validator
|
from ..lib_jwt.validator import validator
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from odoo.addons.base_rest.controllers import main
|
from odoo.addons.base_rest.controllers import main
|
||||||
|
|
||||||
from ..lib.jwt_http import jwt_http
|
from ..lib_jwt.jwt_http import jwt_http
|
||||||
from ..lib.validator import validator
|
from ..lib_jwt.validator import validator
|
||||||
|
|
||||||
|
|
||||||
class BaseRestDemoPublicApiController(main.RestController):
|
class BaseRestDemoPublicApiController(main.RestController):
|
||||||
|
|||||||
129
pms_api_rest/lib_jwt/jwt_http.py
Normal file
129
pms_api_rest/lib_jwt/jwt_http.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
from odoo import http
|
||||||
|
from odoo.exceptions import AccessDenied
|
||||||
|
from odoo.http import Response, request
|
||||||
|
|
||||||
|
from .validator import validator
|
||||||
|
|
||||||
|
return_fields = ["id", "login", "name", "company_id"]
|
||||||
|
|
||||||
|
|
||||||
|
class JwtHttp:
|
||||||
|
def get_state(self):
|
||||||
|
return {"d": request.session.db}
|
||||||
|
|
||||||
|
def parse_request(self):
|
||||||
|
http_method = request.httprequest.method
|
||||||
|
try:
|
||||||
|
body = http.request.params
|
||||||
|
except Exception:
|
||||||
|
body = {}
|
||||||
|
|
||||||
|
headers = dict(list(request.httprequest.headers.items()))
|
||||||
|
if "wsgi.input" in headers:
|
||||||
|
del headers["wsgi.input"]
|
||||||
|
if "wsgi.errors" in headers:
|
||||||
|
del headers["wsgi.errors"]
|
||||||
|
if "HTTP_AUTHORIZATION" in headers:
|
||||||
|
headers["Authorization"] = headers["HTTP_AUTHORIZATION"]
|
||||||
|
|
||||||
|
# extract token
|
||||||
|
token = ""
|
||||||
|
if "Authorization" in headers:
|
||||||
|
try:
|
||||||
|
# Bearer token_string
|
||||||
|
token = headers["Authorization"].split(" ")[1]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return http_method, body, headers, token
|
||||||
|
|
||||||
|
def date2str(self, d, f="%Y-%m-%d %H:%M:%S"):
|
||||||
|
"""
|
||||||
|
Convert datetime to string
|
||||||
|
:param self:
|
||||||
|
:param d: datetime object
|
||||||
|
:param f='%Y-%m-%d%H:%M:%S': string format
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
s = d.strftime(f)
|
||||||
|
except Exception:
|
||||||
|
s = None
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
def response(self, success=True, message=None, data=None, code=200):
|
||||||
|
"""
|
||||||
|
Create a HTTP Response for controller
|
||||||
|
:param success=True indicate this response is successful or not
|
||||||
|
:param message=None message string
|
||||||
|
:param data=None data to return
|
||||||
|
:param code=200 http status code
|
||||||
|
"""
|
||||||
|
print('response')
|
||||||
|
print('response')
|
||||||
|
print('response')
|
||||||
|
print('response')
|
||||||
|
payload = json.dumps(
|
||||||
|
{
|
||||||
|
"success": success,
|
||||||
|
"message": message,
|
||||||
|
"data": data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
payload,
|
||||||
|
status=code,
|
||||||
|
headers=[
|
||||||
|
("Content-Type", "application/json"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def response_500(self, message="Internal Server Error", data=None):
|
||||||
|
return self.response(success=False, message=message, data=data, code=500)
|
||||||
|
|
||||||
|
def response_401(self, message="401 Unauthorized", data=None):
|
||||||
|
return self.response(success=False, message=message, data=data, code=401)
|
||||||
|
|
||||||
|
def response_404(self, message="404 Not Found", data=None):
|
||||||
|
return self.response(success=False, message=message, data=data, code=404)
|
||||||
|
|
||||||
|
def response_403(self, message="403 Forbidden", data=None):
|
||||||
|
return self.response(success=False, message=message, data=data, code=403)
|
||||||
|
|
||||||
|
def errcode(self, code, message=None):
|
||||||
|
return self.response(success=False, code=code, message=message)
|
||||||
|
|
||||||
|
def do_login(self, login, password):
|
||||||
|
# get current db
|
||||||
|
state = self.get_state()
|
||||||
|
try:
|
||||||
|
uid = request.session.authenticate(state["d"], login, password)
|
||||||
|
except AccessDenied:
|
||||||
|
return self.response_401()
|
||||||
|
if not uid:
|
||||||
|
return self.response_401()
|
||||||
|
|
||||||
|
# login success, generate token
|
||||||
|
user = request.env.user.read(return_fields)[0]
|
||||||
|
token = validator.create_token(user)
|
||||||
|
|
||||||
|
return self.response(data={"user": user, "token": token})
|
||||||
|
|
||||||
|
def do_logout(self, token):
|
||||||
|
request.session.logout()
|
||||||
|
request.env["jwt_provider.access_token"].sudo().search(
|
||||||
|
[("token", "=", token)]
|
||||||
|
).unlink()
|
||||||
|
return self.response()
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
# Clean up things after success request
|
||||||
|
# use logout here to make request as stateless as possible
|
||||||
|
request.session.logout()
|
||||||
|
return self.response()
|
||||||
|
|
||||||
|
|
||||||
|
jwt_http = JwtHttp()
|
||||||
85
pms_api_rest/lib_jwt/util.py
Normal file
85
pms_api_rest/lib_jwt/util.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
from dateutil.parser import parse
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Util:
|
||||||
|
addons_path = os.path.join(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.addons_path = self.addons_path.replace("jwt_provider", "")
|
||||||
|
|
||||||
|
def generate_verification_code(self, length=8):
|
||||||
|
return "".join(
|
||||||
|
random.choice(string.ascii_uppercase + string.digits) for _ in range(length)
|
||||||
|
)
|
||||||
|
|
||||||
|
def toDate(self, pgTimeStr):
|
||||||
|
return parse(pgTimeStr)
|
||||||
|
|
||||||
|
def path(self, *paths):
|
||||||
|
"""Make a path"""
|
||||||
|
return os.path.join(self.addons_path, *paths)
|
||||||
|
|
||||||
|
def add_branch(self, tree, vector, value):
|
||||||
|
"""
|
||||||
|
Given a dict, a vector, and a value, insert the value into the dict
|
||||||
|
at the tree leaf specified by the vector. Recursive!
|
||||||
|
|
||||||
|
Params:
|
||||||
|
data (dict): The data structure to insert the vector into.
|
||||||
|
vector (list): A list of values representing the path to the leaf node.
|
||||||
|
value (object): The object to be inserted at the leaf
|
||||||
|
|
||||||
|
Example 1:
|
||||||
|
tree = {'a': 'apple'}
|
||||||
|
vector = ['b', 'c', 'd']
|
||||||
|
value = 'dog'
|
||||||
|
|
||||||
|
tree = add_branch(tree, vector, value)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tree = { 'a': 'apple', 'b': { 'c': {'d': 'dog'}}}
|
||||||
|
|
||||||
|
Example 2:
|
||||||
|
vector2 = ['b', 'c', 'e']
|
||||||
|
value2 = 'egg'
|
||||||
|
|
||||||
|
tree = add_branch(tree, vector2, value2)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tree = { 'a': 'apple', 'b': { 'c': {'d': 'dog', 'e': 'egg'}}}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The dict with the value placed at the path specified.
|
||||||
|
|
||||||
|
Algorithm:
|
||||||
|
If we're at the leaf, add it as key/value to the tree
|
||||||
|
Else: If the subtree doesn't exist, create it.
|
||||||
|
Recurse with the subtree and the left shifted vector.
|
||||||
|
Return the tree.
|
||||||
|
|
||||||
|
"""
|
||||||
|
key = vector[0]
|
||||||
|
tree[key] = (
|
||||||
|
value
|
||||||
|
if len(vector) == 1
|
||||||
|
else self.add_branch(tree[key] if key in tree else {}, vector[1:], value)
|
||||||
|
)
|
||||||
|
return tree
|
||||||
|
|
||||||
|
def create_dict(self, d):
|
||||||
|
res = {}
|
||||||
|
for k, v in d.items():
|
||||||
|
ar = k.split(".")
|
||||||
|
filter(None, ar)
|
||||||
|
self.add_branch(res, ar, v)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
util = Util()
|
||||||
108
pms_api_rest/lib_jwt/validator.py
Normal file
108
pms_api_rest/lib_jwt/validator.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
import jwt
|
||||||
|
from jwt import InvalidSignatureError
|
||||||
|
|
||||||
|
from odoo.http import request
|
||||||
|
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
regex = (
|
||||||
|
r"^[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9]"
|
||||||
|
)
|
||||||
|
regex += r"(?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
|
||||||
|
|
||||||
|
|
||||||
|
class Validator:
|
||||||
|
def is_valid_email(self, email):
|
||||||
|
return re.search(regex, email)
|
||||||
|
|
||||||
|
def key(self):
|
||||||
|
# TODO: change this key before production (build an UI Form to maintain
|
||||||
|
# (in form company?)
|
||||||
|
return "CHANGE THIS KEY"
|
||||||
|
|
||||||
|
def create_token(self, user):
|
||||||
|
try:
|
||||||
|
exp = datetime.datetime.utcnow() + datetime.timedelta(days=30)
|
||||||
|
payload = {
|
||||||
|
"exp": exp,
|
||||||
|
"iat": datetime.datetime.utcnow(),
|
||||||
|
"sub": user["id"],
|
||||||
|
"lgn": user["login"],
|
||||||
|
}
|
||||||
|
token = jwt.encode(payload, self.key(), algorithm="HS256")
|
||||||
|
|
||||||
|
self.save_token(token, user["id"], exp)
|
||||||
|
return token
|
||||||
|
except Exception as ex:
|
||||||
|
_logger.error(ex)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def save_token(self, token, uid, exp):
|
||||||
|
request.env["jwt_provider.access_token"].sudo().create(
|
||||||
|
{
|
||||||
|
"user_id": uid,
|
||||||
|
"expires": exp.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||||
|
"token": token,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def verify(self, token):
|
||||||
|
record = (
|
||||||
|
request.env["jwt_provider.access_token"]
|
||||||
|
.sudo()
|
||||||
|
.search([("token", "=", token)])
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(record) != 1:
|
||||||
|
_logger.info("not found %s" % token)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if record.is_expired:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return record.user_id
|
||||||
|
|
||||||
|
def verify_token(self, token):
|
||||||
|
try:
|
||||||
|
result = {
|
||||||
|
"status": False,
|
||||||
|
"message": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not self.verify(token):
|
||||||
|
result["message"] = "Token invalid or expired"
|
||||||
|
result["code"] = 498
|
||||||
|
_logger.info("11111")
|
||||||
|
return result
|
||||||
|
|
||||||
|
payload = jwt.decode(token, self.key(), algorithms=["HS256"])
|
||||||
|
uid = request.session.authenticate(
|
||||||
|
request.session.db, login=payload["lgn"], password=token
|
||||||
|
)
|
||||||
|
if not uid:
|
||||||
|
result["message"] = "Token invalid or expired"
|
||||||
|
result["code"] = 498
|
||||||
|
_logger.info("2222")
|
||||||
|
return result
|
||||||
|
|
||||||
|
result["status"] = True
|
||||||
|
return result
|
||||||
|
except (
|
||||||
|
jwt.ExpiredSignatureError,
|
||||||
|
jwt.InvalidTokenError,
|
||||||
|
InvalidSignatureError,
|
||||||
|
Exception,
|
||||||
|
):
|
||||||
|
result["code"] = 498
|
||||||
|
result["message"] = "Token invalid or expired"
|
||||||
|
_logger.error(traceback.format_exc())
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
validator = Validator()
|
||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from odoo import _, api, fields, models
|
from odoo import _, api, fields, models
|
||||||
from odoo.exceptions import AccessDenied, ValidationError
|
from odoo.exceptions import AccessDenied, ValidationError
|
||||||
|
|
||||||
from ..lib.validator import validator
|
from ..lib_jwt.validator import validator
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user