Browse Source

Revert to Odoo API key

* This eliminates the need to create an additional unsecure object in the database.
* Using the API token also enables users with 2FA to authenticate against Odoo server
* Added ACL for the Rest API view to allow access to only certain users
* Removed Postman sample as the new methods has been simplified
* Update the module info to reflect changes above
pull/345/head
drpsyko101 9 months ago
parent
commit
8909c65fde
  1. BIN
      rest_api_odoo/Postman Collections/Odoo REST Api.postman_collection.zip
  2. 2
      rest_api_odoo/__manifest__.py
  3. 98
      rest_api_odoo/controllers/main.py
  4. 1
      rest_api_odoo/models/__init__.py
  5. 45
      rest_api_odoo/models/res_users.py
  6. 32
      rest_api_odoo/security/res_api_odoo_security.xml
  7. BIN
      rest_api_odoo/static/description/assets/screenshots/screenshot_2.png
  8. 81
      rest_api_odoo/static/description/index.html
  9. 18
      rest_api_odoo/views/res_users_views.xml

BIN
rest_api_odoo/Postman Collections/Odoo REST Api.postman_collection.zip

Binary file not shown.

2
rest_api_odoo/__manifest__.py

@ -33,7 +33,7 @@
"depends": ['base', 'web'],
"data": [
'security/ir.model.access.csv',
'views/res_users_views.xml',
'security/res_api_odoo_security.xml',
'views/connection_api_views.xml'
],
'images': ['static/description/banner.png'],

98
rest_api_odoo/controllers/main.py

@ -24,27 +24,43 @@ import json
import logging
from datetime import datetime
from odoo import http
from odoo import http, models
from odoo.http import request, Response
from ast import literal_eval
_logger = logging.getLogger(__name__)
class RestApi(http.Controller):
"""This is a controller which is used to generate responses based on the
api requests"""
class IrHttp(models.AbstractModel):
"""This model is used to authenticate the api-key when sending a request"""
_inherit = "ir.http"
def auth_api_key(self, api_key):
@classmethod
def _auth_method_rest_api(cls):
"""This function is used to authenticate the api-key when sending a
request"""
user_id = request.env["res.users"].search([("api_key", "=", api_key)])
if api_key is not None and user_id:
return Response(json.dumps({"message": "Authorized"}), status=200)
elif not user_id:
return Response(json.dumps({"message": "Invalid API Key"}), status=401)
return Response(json.dumps({"message": "No API Key Provided"}), status=400)
try:
api_key = request.httprequest.headers.get("Authorization").lstrip("Bearer ")
if not api_key:
raise PermissionError("Invalid API key provided.")
user_id = request.env["res.users.apikeys"]._check_credentials(
scope="rpc", key=api_key
)
request.update_env(user_id)
except Exception as e:
_logger.error(e)
return Response(
json.dumps({"message": e.args[0]}),
status=401,
content_type="application/json",
)
class RestApi(http.Controller):
"""This is a controller which is used to generate responses based on the
api requests"""
def simplest_type(self, input):
"""Try cast input into native Python class, otherwise return as string"""
@ -123,7 +139,8 @@ class RestApi(http.Controller):
)
return Response(
json.dumps({"records": self.sanitize_records(partner_records)})
json.dumps({"records": self.sanitize_records(partner_records)}),
content_type="application/json",
)
if method == "POST":
if not option.is_post:
@ -139,6 +156,7 @@ class RestApi(http.Controller):
return Response(
json.dumps({"new_record": self.sanitize_records(partner_records)}),
status=201,
content_type="application/json",
)
if method == "PUT":
if not option.is_put:
@ -162,7 +180,8 @@ class RestApi(http.Controller):
return Response(
json.dumps(
{"updated_record": self.sanitize_records(partner_records)}
)
),
content_type="application/json",
)
if method == "DELETE":
if not option.is_delete:
@ -188,12 +207,17 @@ class RestApi(http.Controller):
}
),
status=202,
content_type="application/json",
)
# If not using any method above, simply return an error
raise NotImplementedError()
except ValueError as e:
return Response(json.dumps({"message": e.args[0]}), status=403)
return Response(
json.dumps({"message": e.args[0]}),
status=403,
content_type="application/json",
)
except NotImplementedError as e:
return Response(
json.dumps(
@ -202,16 +226,20 @@ class RestApi(http.Controller):
}
),
status=405,
content_type="application/json",
)
except Exception:
except Exception as e:
_logger.error(e)
return Response(
json.dumps({"message": "Internal server error"}), status=500
json.dumps({"message": f"Internal server error. {e.args[0]}"}),
status=500,
content_type="application/json",
)
@http.route(
["/send_request"],
type="http",
auth="none",
auth="rest_api",
methods=["GET", "POST", "PUT", "DELETE"],
csrf=False,
)
@ -221,12 +249,7 @@ class RestApi(http.Controller):
generate the result"""
http_method = request.httprequest.method
api_key = request.httprequest.headers.get("api-key")
auth_api = self.auth_api_key(api_key)
model = kw.pop("model")
username = request.httprequest.headers.get("login")
password = request.httprequest.headers.get("password")
request.session.authenticate(request.session.db, username, password)
model_id = request.env["ir.model"].search([("model", "=", model)])
if not model_id:
return Response(
@ -236,34 +259,7 @@ class RestApi(http.Controller):
}
),
status=403,
content_type="application/json",
)
if auth_api.status_code == 200:
result = self.generate_response(http_method, model=model_id.id, **kw)
return result
else:
return auth_api
@http.route(
["/odoo_connect"], type="http", auth="none", csrf=False, methods=["GET"]
)
def odoo_connect(self, **kw):
"""This is the controller which initializes the api transaction by
generating the api-key for specific user and database"""
username = request.httprequest.headers.get("login")
password = request.httprequest.headers.get("password")
db = request.httprequest.headers.get("db")
try:
request.session.update(http.get_default_session(), db=db)
auth = request.session.authenticate(request.session.db, username, password)
user = request.env["res.users"].browse(auth)
api_key = request.env.user.generate_api(username)
datas = json.dumps(
{"Status": "auth successful", "User": user.name, "api-key": api_key}
)
return Response(datas)
except Exception:
return Response(
json.dumps({"message": "wrong login credentials"}), status=401
)
return self.generate_response(http_method, model=model_id.id, **kw)

1
rest_api_odoo/models/__init__.py

@ -21,4 +21,3 @@
#############################################################################
from . import connection_api
from . import res_users

45
rest_api_odoo/models/res_users.py

@ -1,45 +0,0 @@
# -*- coding:utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import uuid
from odoo import fields, models
class UserLogin(models.Model):
"""This class is used to inherit users and add api key generation"""
_inherit = 'res.users'
api_key = fields.Char(string="API Key", readonly=True,
help="Api key for connecting with the "
"Database.The key will be "
"generated when authenticating "
"rest api.")
def generate_api(self, username):
"""This function is used to generate api-key for each user"""
users = self.env['res.users'].sudo().search([('login', '=', username)])
if not users.api_key:
users.api_key = str(uuid.uuid4())
key = users.api_key
else:
key = users.api_key
return key

32
rest_api_odoo/security/res_api_odoo_security.xml

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record model="ir.module.category" id="module_category_rest_api_odoo">
<field name="name">REST API</field>
<field name="description">Helps you manage your REST API records.</field>
<field name="sequence">17</field>
</record>
<record id="group_rest_api_odoo_manager" model="res.groups">
<field name="name">Manager</field>
<field name="category_id"
ref="rest_api_odoo.module_category_rest_api_odoo" />
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]" />
</record>
<record id="base.default_user" model="res.users">
<field name="groups_id"
eval="[(4,ref('rest_api_odoo.group_rest_api_odoo_manager'))]" />
</record>
<record id="rest_api_odoo_rule_manager" model="ir.rule">
<field name="name">All REST APIs</field>
<field name="model_id" ref="model_connection_api" />
<field name="domain_force">[(1,'=',1)]</field>
<field name="groups"
eval="[(4, ref('rest_api_odoo.group_rest_api_odoo_manager'))]" />
</record>
</data>
</odoo>

BIN
rest_api_odoo/static/description/assets/screenshots/screenshot_2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

81
rest_api_odoo/static/description/index.html

@ -23,7 +23,7 @@
<div class="col-sm-12 col-md-12 col-lg-12">
<!-- APP HERO -->
<h1 style="color: #FFFFFF; font-weight: bolder; font-size: 50px; text-align: center; margin-top: 50px;">
Odoo rest API</h1>
Odoo Rest API</h1>
<img src="./assets/screenshots/hero.gif" class="img-responsive"
width="100%" height="auto"/>
@ -138,7 +138,7 @@
<div class="d-flex align-items-center"
style="margin-top: 30px; margin-bottom: 30px">
<img src="assets/misc/check-box.png" class="mr-2"/>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Authentication using the generated api key.</span>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Authentication using the generated API key.</span>
</div>
</div>
<div class="col-sm-12 col-md-6">
@ -223,23 +223,15 @@
uninstall the module , you have to remove this parameter.
<br/>- Next we can install the module.
</li>
<li>After installing the Rest api app we can see a new api key
<li>After installing the Rest API app we can see a new API key
field in users.
</li>
- Next we have to generate the api-key for the current
- Next we have to generate the API-key for the current
user.<br/>
<li>You can import the postman collections provided in the app
folder for authentication and interacting with database in
various methods.
</li>
</ul>
</p>
<img src="assets/screenshots/screenshot_1.png"
class="img-thumbnail">
<hr>
<center>
<img src="assets/screenshots/screenshot_9.png"
class="img-thumbnail"></center>
</div>
<div style="display: block; margin: 30px auto;">
@ -248,27 +240,8 @@
</h3>
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">
<ul>
<li>Next you have to select the database and login.</li>
<li>We have attached <b>Postman collections</b> through which
you can
authenticate REST api.
</li>
<li>First, extract the <b>zip</b> file. Then, you will obtain the JSON-format file, which you can directly import into <b>POSTMAN.</b></li>
<li>The url format will be like this - <code>http://cybrosys:8016/odoo_connect</code>
Replace 'cybrosys:8016' with your localhost port number.
</li>
<li>You have to provide database name, username and password
through the headers while sending request.
</li>
<li>If the authentication is successful , an api key will be
generated for the current user.
</li>
<li>This key will be used when sending api requests to
database.
</li>
<li>The response will be like this - <code>{"Status": "auth
successful", "User": "Mitchell Admin", "api-key":
"66c2ebab-d4dc-42f0-87d0-d1646e887569"}</code>.</li>
<li>Next you have to generate the API key under <b>My Profile</b> > <b>Account Security</b>.</li>
<li>Set the name of the API key according to your needs.</li>
</ul>
</p>
<img src="assets/screenshots/screenshot_2.png"
@ -276,17 +249,17 @@
</div>
<div style="display: block; margin: 30px auto;">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">
Create records in Rest api app
Create records in Rest API app
</h3>
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">
<ul>
<li>After rest api authentication, we can create records in the
rest api app.
<li>After creating the API key, we can create records in the
Rest API app.
</li>
<li>Here we can choose the model, and also we can
choose the http methods.
</li>
<li>The api response will be based on these records.</li>
<li>The API response will be based on these records.</li>
</ul>
</p>
<img src="assets/screenshots/screenshot_3.png"
@ -310,17 +283,14 @@
<li>You can send <code>GET</code> request to retrieve data from the
database.
</li>
<li>The postman collection has been provided with app files for
sending request from postman.
</li>
<li>You have to provide username, password and api key through
the header.
<li>You have to provide the API key in the header: <code>Authorization: Bearer {API_KEY}</code>
where <code>{API_KEY}</code> should be replaced with the actual API key.</li>
</li>
<li>Model can be passed as argument as the technical name , and
also if you want
specific record you can provide any related fields to the module as well.
<li>Model can be passed as argument as the technical name, and
also if you want to get a specific record you can provide any
of the related fields to the module as well.
</li>
<li>The format for GET method will be like this - <code>http://cybrosys:8016/send_request?model=res.partner&Id=10.</code>
<li>The format for GET method will be like this - <code>http://cybrosys:8016/send_request?model=res.partner&id=10.</code>
</li>
<li>We can specify the fields inside the JSON data, and it will
be like this - <code>{"fields": ["name", "email"]}</code>.
@ -329,7 +299,7 @@
Pagination can also be used by adding <code>{"limit": 10, "offset": 0}</code>
to the query parameter above.
</li>
<li>This is the format of api response - <code>{"records": [{"id":
<li>This is the format of API response - <code>{"records": [{"id":
10, "email": "deco.addict82@example.com", "name": "Deco
Addict"}]}</code>.
</li>
@ -346,16 +316,13 @@
<li>Using <code>POST</code> method , you can create new records in the
database.
</li>
<li>Just make sure you enabled POST method for the model record
in rest api app , otherwise you will get <b>'method not
<li>Just make sure you enabled <code>POST</code> method for the model record
in Rest API app , otherwise you will get <b>'method not
allowed'</b> message.
</li>
<li>For creating record you have to provide the JSON data along
with the model.
</li>
<li>You can make use of the postman collection that we have
added with app files.
</li>
<li>The format for sending POST request will be like this - <code>http://cybrosys:8016/send_request?model=res.partner.</code>
</li>
<li>This is the format for JSON data - <code>{
@ -384,11 +351,7 @@
<li>You have to provide the model and also the id or the record
that you want to update.
</li>
<li>You can use the Postman collection that we have provided and , you
will be always have to send request with your login
credentials. Otherwise, it will be showing access denied.
</li>
<li>The format for sending PUT request will be like this - <code>http://cybrosys:8016/send_request?model=res.partner&Id=46.</code>
<li>The format for sending <code>PUT</code> request will be like this - <code>http://cybrosys:8016/send_request?model=res.partner&Id=46.</code>
</li>
<li>Here too you have to provide the JSON data through which the
updates will be done.
@ -413,7 +376,7 @@
id that we want to delete.
</li>
<li>Make sure you have permission to delete files for the
selected model in the rest api record.
selected model in the Rest API record.
</li>
<li>The delete request format will be like this - <code>http://cybrosys:8016/send_request?model=res.partner&Id=46.</code>
</li>
@ -848,7 +811,7 @@
<div>
<h4>WhatsApp</h4>
<p style="line-height: 100%;">Say hi to us on WhatsApp!</p>
<a href="https://api.whatsapp.com/send?phone=918606827707">
<a href="https://API.whatsapp.com/send?phone=918606827707">
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;">
+91 86068
27707</p>

18
rest_api_odoo/views/res_users_views.xml

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Inherited user view for Adding API key. -->
<record id="view_users_form" model="ir.ui.view">
<field name="name">view.users.form.inherit.rest.api.odoo</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="model">res.users</field>
<field name="arch" type="xml">
<xpath expr="//page[@name='access_rights']" position="after">
<page string="API" name="rest-api">
<group>
<field name="api_key" groups="base.group_user"/>
</group>
</page>
</xpath>
</field>
</record>
</odoo>
Loading…
Cancel
Save