Merge pull request #97 from expsa/dev_odex25_mobile

Dev odex25 mobile
This commit is contained in:
AbuzarExp 2024-07-08 13:00:30 +03:00 committed by GitHub
commit ebb9e183cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 1024 additions and 39 deletions

49
.gitignore vendored Normal file
View File

@ -0,0 +1,49 @@
# sphinx build directories
_build/
# dotfiles
.*
!.gitignore
!.github
!.mailmap
# compiled python files
*.py[co]
__pycache__/
# setup.py egg_info
*.egg-info
# emacs backup files
*~
# hg stuff
*.orig
status
# odoo filestore
odoo/filestore
# maintenance migration scripts
odoo/addons/base/maintenance
# generated for windows installer?
install/win32/*.bat
install/win32/meta.py
# needed only when building for win32
setup/win32/static/less/
setup/win32/static/wkhtmltopdf/
setup/win32/static/postgresql*.exe
# js tooling
node_modules
jsconfig.json
tsconfig.json
package-lock.json
package.json
.husky
# various virtualenv
/bin/
/build/
/dist/
/include/
/lib/
/man/
/share/
/src/

View File

@ -185,7 +185,6 @@ class AuthenticationController(http.Controller):
return http_helper.response(code=400,message=_('User account with login {} not found').format(login),success=False)
uid = http_helper.is_authentic(login,password)
if not uid:
return http_helper.errcode(code=400, message=_('Unable to Sign In. invalid user password'))
token = validator.create_token(request.env.user)

View File

@ -314,10 +314,12 @@ class AttendanceController(http.Controller):
"You are not allowed to perform this operation. please check with one of your team admins"),
success=False)
try:
print("******************employee.message_sent:",employee.message_sent)
if json.loads(body['in_zone']):
records = employee.attendance_log_ids.sudo().filtered(lambda r: str(r.date) == str(datetime.today().date()) and r.old == False)
for r in records:
r.old = True
employee.message_sent = False
return http_helper.response(message="Old Record Done", data={'status': True})
else:
@ -326,7 +328,6 @@ class AttendanceController(http.Controller):
limit=1)
if attendance.action == 'sign_in':
records = employee.attendance_log_ids.sudo().filtered(lambda r: r.old == False and str(r.date) == str(datetime.today().date()))
# records = employee.attendance_log_ids.sudo().filtered(lambda r: r.old == False and r.date == str(datetime.today().date()))
if records:
n = len(records)
last = records[n - 1]
@ -335,37 +336,43 @@ class AttendanceController(http.Controller):
if now > last:
diff = now - last
diff = diff.seconds / 60
auto = request.env.user.company_id.auto_checkout if request.env.user.company_id.auto_checkout > 0 else 5
zone = http.request.env['attendance.zone'].search([('employee_ids', 'in', employee.id)],limit=1)
zone_general = http.request.env['attendance.zone'].search([('general', '=', True)],limit=1)
auto = zone.auto_checkout or zone_general.auto_checkout or request.env.user.company_id.auto_checkout or 20
if diff >= auto:
attendance = http.request.env['attendance.attendance'].create({
'employee_id': employee.id,
'action':'sign_out',
'action': 'sign_out',
'action_type': 'auto',
'name': fields.datetime.now(),
# 'device_id': body.get('device_id'),
'zone': "%s,%s" % (body.get('longitude'), body.get('latitude')),
'longitude': body.get('longitude'),
'latitude': body.get('latitude'),
})
msg = (_("Auto Checkout successfully"))
subject = (_("Auto Checkout"))
msg = _("Auto Checkout successfully")
subject = _("Auto Checkout")
self.send_msg(employee, msg, subject)
records = employee.attendance_log_ids.sudo().filtered(
lambda r: str(r.date) == str(datetime.today().date()) and r.old == False)
for r in records:
r.old = True
return http_helper.response(message="Auto Checkout successfully", data={'status': True})
employee.message_sent = False
return http_helper.response(message="Auto Checkout successfully", data={'status': True})
else:
msg = (_("You are out of attendance zone you will be auto sin out "))
subject = (_("Auto Sign out"))
self.send_msg(employee, msg, subject)
return http_helper.response(message="Auto Checkout Fail and Send", data={'status': False})
if not employee.message_sent:
msg = _("You are out of attendance zone you will be auto sign out")
subject = _("Auto Sign out")
self.send_msg(employee, msg, subject)
employee.message_sent = True
return http_helper.response(message="Auto Checkout Fail and Send", data={'status': False})
else:
self.create_log(employee, body.get('longitude'), body.get('latitude'))
msg = (_("You are out of attendance zone you will be auto sin out "))
subject = (_("Auto Sign out"))
self.send_msg(employee, msg, subject)
return http_helper.response(message="Auto Checkout Fail and Send", data={'status': False})
if not employee.message_sent:
msg = _("You are out of attendance zone you will be auto sign out")
subject = _("Auto Sign out")
self.send_msg(employee, msg, subject)
employee.message_sent = True
return http_helper.response(message="Auto Checkout Fail and Send", data={'status': False})
else:
return http_helper.response(message="You are not Checked in yet", data={'status': True})
except Exception as e:
@ -375,6 +382,7 @@ class AttendanceController(http.Controller):
return http_helper.errcode(code=403, message=message)
def send_msg(self, emp, msg, subject):
print("*****************************send")
if emp.user_id.partner_id:
partner_id = emp.user_id.partner_id
partner_id.send_notification(subject, msg, data=None, all_device=True)

View File

@ -186,7 +186,6 @@ class AuthenticationController(http.Controller):
return http_helper.response(code=400,message=_('User account with login {} not found').format(login),success=False)
uid = http_helper.is_authentic(login,password)
if not uid:
return http_helper.errcode(code=400, message=_('Unable to Sign In. invalid user password'))
token = validator.create_token(request.env.user)

View File

@ -25,7 +25,10 @@ class JwtAccessToken(models.Model):
token.is_expired = datetime.now() > token.expires
def access_token_cron(self):
self.search([("is_expired", "=", True)]).unlink()
# self.search([("is_expired", "=", True)]).unlink()
current_time = datetime.now()
expired_tokens = self.search([('expires', '<', current_time)])
expired_tokens.unlink()
return True
def set_env(self,env):

View File

@ -26,6 +26,8 @@ class AttendanceZone(models.Model):
loc_ch_dist = fields.Integer('Location Change Distance - Meter', default=100)
srv_ch_tmout = fields.Integer('Services Change Timeout - Minutes', default=5)
auto_checkout = fields.Integer(string="Auto Checkout After" ,default=10)
@api.constrains('start','end')
def start_end(self):
for rec in self:

View File

@ -13,7 +13,7 @@ class HrEmployee(models.Model):
device_id = fields.Char(string="Employee Device ")
fcm_token = fields.Char(string='FCM Token')
attendance_log_ids = fields.One2many('attendance.log','employee_id',string="Attendance Log")
message_sent = fields.Boolean(string="Message Sent", default=False)
def user_push_notification(self, notification):
url = "https://fcm.googleapis.com/fcm/send"
header = {

View File

@ -97,3 +97,22 @@ class Users(models.Model):
groups.append("group_department_manager")
return groups
@api.model
def create(self, vals):
res = super(Users, self).create(vals)
if 'password' in vals or vals.get('active') is False:
self._invalidate_tokens(res)
return res
def write(self, vals):
result = super(Users, self).write(vals)
if 'password' in vals or 'active' in vals and not vals['active']:
self._invalidate_tokens(self)
return result
def _invalidate_tokens(self, users):
token_model = self.env['jwt_provider.access_token']
for user in users:
token_model.search([('user_id', '=', user.id)]).unlink()

View File

@ -192,9 +192,17 @@ class Validator:
_logger.error(traceback.format_exc())
except (jwt.InvalidTokenError, Exception) as e:
result['code'] = 497
result['message'] = 'Token invalid or expired'
_logger.error(traceback.format_exc())
record = request.env['jwt_provider.access_token'].sudo().search([
('token', '=', token)
])
if not record:
result['message'] = 'Token not found'
result['code'] = 497
return result
else:
result['code'] = 498
result['message'] = 'Token invalid'
_logger.error(traceback.format_exc())
return result
def refresh_token(self, token):
@ -237,9 +245,17 @@ class Validator:
_logger.error(traceback.format_exc())
except (jwt.InvalidTokenError, Exception) as e:
result['code'] = 497
result['message'] = 'Token invalid'
_logger.error(traceback.format_exc())
record = request.env['jwt_provider.access_token'].sudo().search([
('token', '=', token)
])
if not record:
result['message'] = 'Token not found'
result['code'] = 497
return result
else:
result['code'] = 498
result['message'] = 'Token invalid'
_logger.error(traceback.format_exc())
return result
@ -284,9 +300,17 @@ class Validator:
return result
except (jwt.InvalidTokenError, Exception) as e:
result['code'] = 497
result['message'] = 'Token invalid'
_logger.error(traceback.format_exc())
record = request.env['jwt_provider.access_token'].sudo().search([
('token', '=', token)
])
if not record:
result['message'] = 'Token not found'
result['code'] = 497
return result
else:
result['code'] = 498
result['message'] = 'Token invalid'
_logger.error(traceback.format_exc())
return result

View File

@ -15,10 +15,10 @@
</h2>
</div>
<group col="4" colspan="2">
<field name="latitude" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
<field name="latitude" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
<field name="longitude" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
<field name="zone" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
<field name="allowed_range" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
<field name="zone" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
<field name="allowed_range" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
<field name="active_check" readonly="1" force_save="1" />
</group>
<group>
@ -29,8 +29,9 @@
<group>
<field name="loc_ch_intv" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}" class="oe_inline"/>
<field name="loc_ch_dist" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
<field name="srv_ch_tmout" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
<field name="loc_ch_dist" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
<field name="srv_ch_tmout" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
<field name="auto_checkout" />
</group>
</group>
@ -70,11 +71,7 @@
<field name="view_mode">tree,form</field>
</record>
<!-- <menuitem-->
<menuitem id="menu_attendance_zone"
name="Attendance Zone"
parent="attendances.menu_hr_attendance_configurations"
sequence="3"
action="attendance_zone_action"/>
<menuitem id="menu_attendance_zone" name="Attendance Zone" parent="attendances.menu_hr_attendance_configurations" sequence="3" action="attendance_zone_action"/>
<!-- config-->

View File

@ -0,0 +1,2 @@
from . import models
from . import controllers

View File

@ -0,0 +1,20 @@
{
'name': 'Odex Web App API',
'version': '1.0',
'license': 'AGPL-3',
'category': 'Odex25-Mobile/Odex25-Mobile',
'author': 'Expert Co. Ltd.',
'website': 'http://exp-sa.com',
'summary': "All Mopile Web App Api and Configurations",
'depends': ['hr'],
'external_dependencies': {
'python': ['jwt', ],
},
'data': [
'security/ir.model.access.csv',
'views/attendance_zone_config_view.xml',
'views/hr_employee_view.xml',
],
'installable': True,
'application': False,
}

View File

@ -0,0 +1,3 @@
from . import authentication
from . import web

View File

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
import werkzeug
from odoo import http, tools
from odoo.http import request, Response
from odoo.addons.auth_signup.models.res_users import SignupError
from odoo.exceptions import UserError
import base64
from ..validator import validator
from ..http_helper import http_helper
import json
import logging
from odoo.tools.translate import _
_logger = logging.getLogger(__name__)
SENSITIVE_FIELDS = ['password', 'password_crypt', 'new_password', 'create_uid', 'write_uid']
class AuthenticationController(http.Controller):
@http.route('/rest_api/web/login', type='http', auth='none', csrf=False, cors='*', methods=['POST'])
def login_phone(self, **kw):
login = kw.get('login')
password = kw.get('password')
if not login:
return http_helper.response(code=400, message=_('username or email is missing'), success=False)
if not password:
return http_helper.response(code=400, message=_('Password is missing'), success=False)
if not kw.get('device_id'):
return http_helper.response(code=400, message=_('Device id is missing'), success=False)
# check fcm_token
if not kw.get('fcm_token_web'):
return http_helper.response(code=400, message=_('FCM Token is missing'), success=False)
user = request.env['res.users'].sudo().search([('login', '=', login)], limit=1)
if not user or not user.login:
return http_helper.response(code=400, message=_('User account with login {} not found').format(login),
success=False)
uid = http_helper.is_authentic(login, password)
if not uid:
return http_helper.errcode(code=400, message=_('Unable to Sign In. invalid user password'))
token = validator.create_token(request.env.user)
dic = request.env.user.to_dict(True)
employee = http.request.env['hr.employee'].sudo().search([('user_id', '=', user.id)], limit=1)
if employee and kw.get('device_id') and not employee.device_id:
employee.sudo().write({'device_id': kw.get('device_id')})
# write fcm_token and fcm_token_web in employee
fcm_token_web = kw.get('fcm_token_web')
if employee and fcm_token_web:
employee.sudo().write({'fcm_token_web': fcm_token_web})
dic['token'] = token
http_helper.cleanup();
return http_helper.response(data=dic, message=_("User log in successfully"))
@http.route('/rest_api/web/validate',type='http', auth='none', csrf=False, cors='*',methods=['POST'])
def validate_token(self, **kw):
http_method, body, headers, token = http_helper.parse_request()
result = validator.validate_token(token)
if result['code'] == 497 or result['code'] == 498:
return http_helper.errcode(code=result['code'], message=result['message'])
return http_helper.response(message="uploaded success",data=result['data'])
@http.route('/rest_api/web/refresh',type='http', auth='none', csrf=False, cors='*',methods=['POST'])
def refresh_token(self, **kw):
http_method, body, headers, token = http_helper.parse_request()
result = validator.refresh_token(token)
if result['code'] == 497:
return http_helper.errcode(code=result['code'], message=result['message'])
return http_helper.response(message="uploaded success",data=result['data'])
# Reet password with email
@http.route(['/rest_api/web/reset'], type='http', auth='none', csrf=False, methods=['POST'])
def reset_email(self, **kw):
http_method, body, headers, token = http_helper.parse_request()
if not body.get('email'):
return http_helper.response(code=400, message="Email must not be empty", success=False)
user = http.request.env['res.users'].sudo().search([('login', '=', kw.get('email'))])
if user:
user.sudo().action_reset_password()
return http_helper.response(message=_("A verification link has been sent to you email account"), data={})
else:
return http_helper.errcode(code=403, message="Password reset failed")
@http.route('/rest_api/web/users/password',type='http', auth='none', csrf=False, cors='*',methods=['PUT'])
def change_password(self, **kw):
http_method, body, headers, token = http_helper.parse_request()
if not body.get('old_password') or not body.get('new_password'):
return http_helper.errcode(code=400, message='Password must not be empty')
result = validator.verify_token(token)
if not result['status']:
return http_helper.errcode(code=400, message='Invalid passwords')
user = validator.verify(token)
if not user:
return http_helper.errcode(code=400, message=_("You are not allowed to perform this operation. please check with one of your team admins"))
if not http_helper.is_authentic(user.login, body.get('old_password')):
return http_helper.errcode(code=400, message='Invalid passwords')
request.env.user.write({
'password':str(body.get('new_password')).strip()
})
request.session.logout()
return http_helper.response(message=_("password changed successfully"),data={'id':user.id})
@http.route('/rest_api/web/logout', type='http', auth='none', csrf=False, cors='*', methods=['POST'])
def logout(self, **kw):
http_method, body, headers, token = http_helper.parse_request()
result = validator.verify_token(token)
if not result['status']:
return http_helper.errcode(code=result['code'], message=result['message'])
http_helper.do_logout(token)
return http_helper.response()
@http.route('/rest_api/web/users', type='http', auth='none', csrf=False, cors='*', methods=['GET'])
def info(self, **kw):
http_method, body, headers, token = http_helper.parse_request()
result = validator.verify_token(token)
if not result['status']:
return http_helper.errcode(code=result['code'], message=result['message'])
user = validator.verify(token)
if not user:
return http_helper.response(code=400, message=_("You are not allowed to perform this operation. please check with one of your team admins"), success=False)
return http_helper.response(data=user.to_dict(True))

View File

@ -0,0 +1,18 @@
import odoo
from odoo import http
from odoo.http import request
class WebController(http.Controller):
@http.route('/web/session/authenticate', type='json', auth="none")
def authenticate(self, login, password, base_location=None):
db = odoo.tools.config.get('db_name')
if not db:
response_data = {
"error": "Database name should be specified in Conf File",
"status": 400
}
return response_data
request.session.authenticate(db, login, password)
return request.env['ir.http'].session_info()

View File

@ -0,0 +1,128 @@
from odoo import http
from odoo.http import request, Response
from .validator import validator
import simplejson as json
import logging
_logger = logging.getLogger(__name__)
return_fields = ['id', 'login', 'name', 'company_id','state']
class HttpHelper:
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:
s = None
finally:
return s
def response(self, success=True, message=None, data=None, code=200,errors=None):
"""
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
"""
payload = json.dumps({
'success': success,
'message': message,
'data': data,
'code':code
})
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_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 is_authentic(self, login, password):
state = self.get_state()
name = login.strip()
pwd = password.strip()
return request.session.authenticate(state['d'], name, pwd)
def do_login(self, login, password):
# get current db
state = self.get_state()
name = login.strip()
pwd = password.strip()
uid = request.session.authenticate(state['d'], name, pwd)
if not uid:
return self.errcode(code=400, message='incorrect login')
# login success, generate token
token = validator.create_token(request.env.user)
dic = request.env.user.to_dict(True)
dic['token'] = token
return self.response(data=dic)
def do_logout(self, token):
request.session.logout()
request.env['jwt_provider.access_token'].sudo().search([
('token', '=', token)
]).unlink()
def cleanup(self):
# Clean up things after success request
# use logout here to make request as stateless as possible
request.session.logout()
http_helper = HttpHelper()

View File

@ -0,0 +1,5 @@
from . import hr_employee
from . import attendence_zone_config
from . import mail_thread
from . import access_token
from . import res_users

View File

@ -0,0 +1,35 @@
from odoo import models, fields, api
from datetime import datetime, timedelta
from dateutil import parser
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
import logging
_logger = logging.getLogger(__name__)
str_fmt = '%d/%m/%Y %H:%M:%S'
class JwtAccessToken(models.Model):
_name = 'jwt_provider.access_token'
_description = 'Store user access token for one-time-login'
token = fields.Char('Access Token', required=True)
user_id = fields.Many2one('res.users', string='User', required=True, ondelete='cascade')
expires = fields.Datetime('Expires', required=True)
is_expired = fields.Boolean(compute='_compute_is_expired')
@api.depends('expires')
def _compute_is_expired(self):
ctr = datetime.now().strftime(str_fmt)
_logger.info(ctr)
for token in self:
token.is_expired = datetime.now() > token.expires
def access_token_cron(self):
# self.search([("is_expired", "=", True)]).unlink()
current_time = datetime.now()
expired_tokens = self.search([('expires', '<', current_time)])
expired_tokens.unlink()
return True
def set_env(self,env):
self.env = env

View File

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
import datetime
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
web_fcm_server_key = fields.Char(string='Server Key:', related="company_id.web_fcm_server_key", readonly=False)
web_sender_id = fields.Char(string='Sender ID:', related="company_id.web_sender_id", readonly=False)
class ResCompany(models.Model):
_inherit = 'res.company'
web_fcm_server_key = fields.Char(string='Server Key')
web_sender_id = fields.Char(string='Sender ID')

View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from datetime import datetime, timedelta
from odoo import models,fields,api,_
from odoo.exceptions import ValidationError
import random
import json
import json, requests
class HrEmployee(models.Model):
_inherit = 'hr.employee'
device_id = fields.Char(string="Employee Device ")
fcm_token_web = fields.Char(string='FCM Web Token')
def user_push_notification_web(self, notification):
url = "https://fcm.googleapis.com/fcm/send"
header = {
'Content-Type': 'application/json',
'Authorization': 'key=%s' % (self.env.user.company_id.web_fcm_server_key)
}
body = json.dumps({
"to": self.fcm_token_web,
"direct_boot_ok": True,
"notification": {
"title": "Message",
"body": notification
}
})
try:
respons = requests.post(url=url, data=body, headers=header)
return True
except Exception as e:
return False

View File

@ -0,0 +1,22 @@
from odoo import fields, models, api
class MailThread(models.AbstractModel):
_inherit = 'mail.thread'
@api.returns('mail.message', lambda value: value.id)
def message_post(self, *, message_type='notification', **kwargs):
if self._name in ['mail.channel']:
notification_body = kwargs.get('body', '')
attachments = len(kwargs.get('attachment_ids', []))
if notification_body and attachments:
notification_body += '\n{} File(s)'.format(attachments)
elif attachments:
notification_body = '{} File(s)'.format(attachments)
partners_to_notify = self.channel_partner_ids.filtered(lambda r: r.id != self.env.user.partner_id.id)
for employee_id in self.env['hr.employee'].sudo().search([('user_id', 'in', partners_to_notify.user_ids.ids)]):
push_notify = employee_id.user_push_notification_web(notification_body)
return super(MailThread, self).message_post(message_type=message_type, **kwargs)

View File

@ -0,0 +1,99 @@
import werkzeug
from odoo.exceptions import AccessDenied
from odoo import api, models, fields, SUPERUSER_ID
import logging
_logger = logging.getLogger(__name__)
from ..validator import validator
class Users(models.Model):
_inherit = "res.users"
access_token_ids = fields.One2many(
string="Access Tokens",
comodel_name="jwt_provider.access_token",
inverse_name="user_id",
)
avatar = fields.Char(compute="_compute_avatar")
# is_verified = fields.Boolean("Verified" , default=False)
@classmethod
def _login(cls, db, login, password, user_agent_env):
user_id = super(Users, cls)._login(
db, login, password, user_agent_env=user_agent_env
)
if user_id:
return user_id
uid = validator.verify(password)
return uid
@api.model
def check_credentials(self, password):
try:
super(Users, self).check_credentials(password)
except AccessDenied:
# verify password as token
if not validator.verify(password):
raise
@api.depends("image_1024")
def _compute_avatar(self):
base = self.env["ir.config_parameter"].sudo().get_param("web.base.url")
for u in self:
u.avatar = werkzeug.urls.url_join(base, "rest_api/web/avatar/%d" % u.id)
# @api.multi
def to_dict(self, single=False):
res = []
for u in self:
d = u.read(["email", "name", "avatar", "mobile", "phone", "partner_id"])[0]
d["user_id"] = self.id
d["partner_id"] = self.partner_id.id
d["lang"] = self.partner_id.lang
groups = self.user_groups()
d["groups"] = groups
employee = (
self.env["hr.employee"]
.sudo()
.search([("user_id", "=", self.id)], limit=1)
)
# attendance_status = validator.get_attendance_check(employee)
d["job"] = employee.job_id.name if employee and employee.job_id else None
d["employe_id"] = employee.id if employee and employee.id else None
# d["attendance_status"] = attendance_status if attendance_status else None
res.append(d)
return res[0] if single else res
def user_groups(self):
groups = []
if self.has_group("base.group_user"):
groups.append("group_user")
if self.has_group("hr_base.group_division_manager"):
groups.append("group_division_manager")
if self.has_group("hr.group_hr_manager"):
groups.append("group_hr_manager")
if self.has_group("hr_base.group_executive_manager"):
groups.append("group_executive_manager")
if self.has_group("hr_loans_salary_advance.group_loan_user"):
groups.append("group_loan_user")
if self.has_group("hr_base.group_general_manager"):
groups.append("group_general_manager")
if self.has_group("hr_base.group_account_manager"):
groups.append("group_account_manager")
if self.has_group("hr.group_hr_user"):
groups.append("group_hr_user")
if self.has_group("hr_timesheet.group_timesheet_manager"):
groups.append("group_timesheet_manager")
if self.has_group("hr_holidays.group_hr_holidays_user"):
groups.append("group_hr_holidays_user")
if self.has_group("hr_base.group_department_manager"):
groups.append("group_department_manager")
return groups

View File

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_jwt_access_token,Read.jwt.access.token,model_jwt_provider_access_token,,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_jwt_access_token Read.jwt.access.token model_jwt_provider_access_token 1 0 0 0

View File

@ -0,0 +1,293 @@
import logging
import jwt
import re
import datetime
import traceback
from odoo import http, service, registry, SUPERUSER_ID,_
from odoo.http import request
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
import phonenumbers
import math
from math import ceil
from odoo.service import security
import logging
_logger = logging.getLogger(__name__)
SECRET_KEY = "skjdfe48ueq739rihesdio*($U*WIO$u8"
regex = r"^[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
class Validator:
def get_page_pagination(self,page):
limit = 20
page = int(page)
page = 1 if page<1 else page
offset = (page - 1) * limit
prev = False if page -1<=0 else page -1
return page,offset,limit,prev
def get_page_pagination_next(self,page,count):
page = int(page)
page = page+1
next = math.ceil(count / 20)
if next <page:
return False
else:
return page
def get_attendance_check(self,employee):
last = http.request.env['attendance.attendance'].sudo().search([('employee_id', '=', employee.id), ], order='name desc',
limit=1)
if last:
return last.action
else:
return 'sign_out'
def get_state_name(self,obj,state):
state_name = dict(obj.fields_get(["state"],['selection'])['state']["selection"]).get(obj.state)
return state_name
def is_valid_name(self, name):
return len (name) > 6
def is_valid_password(self, password):
return len (password) > 5
def is_valid_location(self, location):
try:
lat , lat = [float(x) for x in location.split(',') if x.replace('.','',1).isdigit()]
if lat and lat:
return True
except :
pass
return False
def is_valid_email(self, email):
return re.search(regex, email)
def is_valid_phone(self,phone):
try:
if not phone.startswith('+'):
phone = '+'+phone
number = phonenumbers.parse(phone, None)
isvalid = phonenumbers.is_valid_number(number)
return isvalid
except :
pass
return False
def get_server_error(self,e,user):
x= False
if len(e.args) == 2:
x, y = e.args
x = re.sub('\n', '', x)
else:
x = e.args
text = ""
if user.lang == 'en_US':
text =(_("contact admin or edit it Manually"))
else:
text = "الرجاء التواصل مع مدير النظام او استخدام الموقع الالكترونى"
message = "%s, %s" % (x,text)
return message
def create_token(self, user):
try:
exp = datetime.datetime.utcnow() + datetime.timedelta(days=7)
payload = {
'exp': exp,
'iat': datetime.datetime.utcnow(),
'sub': user['id'],
'lgn': user['login'],
}
token = jwt.encode(
payload,
SECRET_KEY,
algorithm='HS256'
)
# payload = jwt.decode(token, SECRET_KEY ,algorithms='HS256')
print(token)
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
record.set_env(request.env)
return record.user_id
def verify_token(self, token):
try:
result = {
'status': False,
'message': None,
}
record = request.env['jwt_provider.access_token'].sudo().search([
('token', '=', token)
])
if len(record) != 1:
result['message'] = 'Token not found'
result['code'] = 497
return result
if record.is_expired:
result['message'] = 'Token has expired'
result['code'] = 498
return result
payload = jwt.decode(token, SECRET_KEY ,algorithms='HS256')
uid =self.verify(token)
user = uid
request.session.uid = user.id
request.session.login = user.login
request.session.session_token = user.id and security.compute_session_token(
request.session, request.env
)
request.uid = user.id
request.disable_db = False
request.session.get_context()
# Set user's context
user_context = request.env(request.cr, request.session.uid)['res.users'].context_get().copy()
request.session.context = request.context = user_context
request.env.user = uid
# uid = request.session.authenticate(request.session.db, uid=payload['sub'], password=token)
if not uid:
result['message'] = 'Token invalid or expired'
result['code'] = 498
return result
result['status'] = True
return result
except jwt.ExpiredSignatureError as ex :
result['code'] = 498
result['message'] = 'Signature has expired'
_logger.error(traceback.format_exc())
except (jwt.InvalidTokenError, Exception) as e:
result['code'] = 497
result['message'] = 'Token invalid or expired'
_logger.error(traceback.format_exc())
return result
def refresh_token(self, token):
try:
result = {
'status': False,
'message': None,
'code':200
}
record = request.env['jwt_provider.access_token'].sudo().search([
('token', '=', token)
])
if len(record) != 1 or not record:
result['message'] = 'Token not found'
result['code'] = 497
return result
payload = jwt.decode(token, SECRET_KEY ,algorithms='HS256')
user = request.env['res.users'].sudo().search([('id', '=',payload['sub'])], limit=1)
if not user:
result['message'] = 'Token for user not found'
result['code'] = 497
return result
request.env['jwt_provider.access_token'].sudo().search([
('token', '=', token)
]).unlink()
result['status'] = True
result['code'] == 200
result['data'] = {'token':self.create_token(user)}
return result
except jwt.ExpiredSignatureError as ex :
request.env['jwt_provider.access_token'].sudo().search([
('token', '=', token)
]).unlink()
result['status'] = True
result['code'] == 200
result['data'] = {'token':self.create_token(user)}
_logger.error(traceback.format_exc())
except (jwt.InvalidTokenError, Exception) as e:
result['code'] = 497
result['message'] = 'Token invalid'
_logger.error(traceback.format_exc())
return result
def validate_token(self, token):
try:
result = {
'status': False,
'message': None,
'code':200
}
record = request.env['jwt_provider.access_token'].sudo().search([
('token', '=', token)
])
if len(record) != 1 or not record:
result['message'] = 'Token not found'
result['code'] = 497
return result
payload = jwt.decode(token, SECRET_KEY ,algorithms='HS256')
user = request.env['res.users'].sudo().search([('id', '=',payload['sub'])], limit=1)
if not user:
result['message'] = 'Token for user not found'
result['code'] = 497
return result
uid =self.verify(token) #request.session.finalize()
# uid = request.session.authenticate(request.session.db, uid=payload['sub'], password=token)
if not uid:
result['message'] = 'Token invalid or expired'
result['code'] = 498
return result
result['data'] = {}
return result
except jwt.ExpiredSignatureError as ex :
request.env['jwt_provider.access_token'].sudo().search([
('token', '=', token)
]).unlink()
result['message'] = 'Token invalid or expired'
result['code'] = 498
_logger.error(traceback.format_exc())
return result
except (jwt.InvalidTokenError, Exception) as e:
result['code'] = 497
result['message'] = 'Token invalid'
_logger.error(traceback.format_exc())
return result
validator = Validator()

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<!-- config-->
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">mobile.res.config.settings.view.form</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside">
<div class="app_settings_block" data-string="Web App Configuration" string="Web App" data-key="spl">
<h2>WebMobile Configuration</h2>
<div id="web_fcm_settings">
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="web_fcm_server_key"/>
<field name="web_fcm_server_key"/>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="web_sender_id"/>
<field name="web_sender_id"/>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<!--hr-->
<record id="employee_web_mobile_inherited_form" model="ir.ui.view">
<field name="name">employee.web.mobile.inherited.form</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="priority">200</field>
<field name="arch" type="xml">
<field name="user_id" position="after">
<field name="fcm_token_web"/>
</field>
</field>
</record>
</data>
</odoo>

View File

@ -245,9 +245,12 @@ class RestApi(Controller):
context = request.env.context.copy()
context.update({"active_model": btn.model})
reject_reason = kw.get('reason_msg')
if reject_reason:
context.update({"reject_reason": reject_reason})
context.update({"active_id": int(active_id)})
request.env.context = context
btn._run_code(active_id, btn.model, request.env)
btn.with_context(context)._run_code(active_id, btn.model, request.env)
res = obj.read(["id", "state"])[0]
state = res["state"]
btn_new = (