Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# generated from manifests external_dependencies
oauthlib
openupgradelib
requests
requests-oauthlib
responses
5 changes: 5 additions & 0 deletions webservice/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ The module introduces support for HTTP Request protocol. The webservice
HTTP call returns by default the content of the response. A context
'content_only' can be passed to get the full response object.

It builds on top of ``webservice_core`` (which provides the
``webservice.backend`` model with public/username-password/API key
authentication) to add OAuth2 authentication and ``server_environment``
support.

**Table of contents**

.. contents::
Expand Down
8 changes: 3 additions & 5 deletions webservice/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@
{
"name": "WebService",
"summary": """Defines webservice abstract definition to be used generally""",
"version": "18.0.1.1.2",
"version": "18.0.2.0.0",
"license": "AGPL-3",
"development_status": "Production/Stable",
"maintainers": ["etobella"],
"maintainers": ["etobella", "simahawk"],
"author": "Creu Blanca, Camptocamp, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/web-api",
"depends": ["component"],
"depends": ["webservice_core", "component"],
"external_dependencies": {"python": ["requests-oauthlib", "oauthlib", "responses"]},
"data": [
"security/ir.model.access.csv",
"security/ir_rule.xml",
"views/webservice_backend.xml",
],
"demo": [],
Expand Down
3 changes: 1 addition & 2 deletions webservice/components/request_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
from requests_oauthlib import OAuth2Session

from odoo.addons.component.core import Component

from ..utils import sanitize_url_for_log
from odoo.addons.webservice_core.utils import sanitize_url_for_log

_logger = logging.getLogger(__name__)

Expand Down
70 changes: 11 additions & 59 deletions webservice/models/webservice_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,23 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging

from odoo import _, api, exceptions, fields, models
from odoo import api, fields, models
from odoo.tools import config

_logger = logging.getLogger(__name__)


class WebserviceBackend(models.Model):
_name = "webservice.backend"
_inherit = ["collection.base"]
_description = "WebService Backend"
_inherit = [
"webservice.backend",
"collection.base",
]

name = fields.Char(required=True)
tech_name = fields.Char(required=True)
protocol = fields.Selection([("http", "HTTP Request")], required=True)
url = fields.Char(required=True)
auth_type = fields.Selection(
selection=[
("none", "Public"),
("user_pwd", "Username & password"),
("api_key", "API Key"),
("oauth2", "OAuth2"),
],
required=True,
selection_add=[("oauth2", "OAuth2")],
ondelete={"oauth2": "cascade"},
)
username = fields.Char(auth_type="user_pwd")
password = fields.Char(auth_type="user_pwd")
api_key = fields.Char(string="API Key", auth_type="api_key")
api_key_header = fields.Char(string="API Key header", auth_type="api_key")
oauth2_flow = fields.Selection(
[
("backend_application", "Backend Application (Client Credentials Grant)"),
Expand All @@ -58,47 +47,6 @@ class WebserviceBackend(models.Model):
help="random key generated when authorization flow starts "
"to ensure that no CSRF attack happen"
)
content_type = fields.Selection(
[
("application/json", "JSON"),
("application/xml", "XML"),
("application/x-www-form-urlencoded", "Form"),
],
)
company_id = fields.Many2one("res.company", string="Company")

@api.constrains("auth_type")
def _check_auth_type(self):
valid_fields = {
k: v for k, v in self._fields.items() if hasattr(v, "auth_type")
}
for rec in self:
if rec.auth_type == "none":
continue
_fields = [v for v in valid_fields.values() if v.auth_type == rec.auth_type]
missing = []
for _field in _fields:
if not rec[_field.name]:
missing.append(_field)
if missing:
raise exceptions.UserError(rec._msg_missing_auth_param(missing))

def _msg_missing_auth_param(self, missing_fields):
def get_selection_value(fname):
return self._fields.get(fname).convert_to_export(self[fname], self)

return _(
"Webservice '%(name)s' requires '%(auth_type)s' authentication. "
"However, the following field(s) are not valued: %(fields)s"
) % {
"name": self.name,
"auth_type": get_selection_value("auth_type"),
"fields": ", ".join([f.string for f in missing_fields]),
}

def _valid_field_parameter(self, field, name):
extra_params = ("auth_type",)
return name in extra_params or super()._valid_field_parameter(field, name)

@api.onchange("auth_type")
def _onchange_auth_type(self):
Expand All @@ -125,6 +73,10 @@ def write(self, vals):
return res

def call(self, method, *args, **kwargs):
if not self.auth_type.startswith("oauth2"):
return super().call(method, *args, **kwargs)
# NOTE: oauth2 still relies on `component` for now, until it gets
# extracted to its own module and reworked to drop that dependency too.
_logger.debug("backend %s: call %s %s %s", self.name, method, args, kwargs)
response = getattr(self._get_adapter(), method)(*args, **kwargs)
_logger.debug("backend %s: response: \n%s", self.name, response)
Expand Down
4 changes: 4 additions & 0 deletions webservice/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
This module creates WebService frameworks to be used globally.

The module introduces support for HTTP Request protocol. The webservice HTTP call returns by default the content of the response. A context 'content_only' can be passed to get the full response object.

It builds on top of ``webservice_core`` (which provides the ``webservice.backend``
model with public/username-password/API key authentication) to add OAuth2
authentication and ``server_environment`` support.
4 changes: 4 additions & 0 deletions webservice/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,10 @@ <h1>WebService</h1>
<p>The module introduces support for HTTP Request protocol. The webservice
HTTP call returns by default the content of the response. A context
‘content_only’ can be passed to get the full response object.</p>
<p>It builds on top of <tt class="docutils literal">webservice_core</tt> (which provides the
<tt class="docutils literal">webservice.backend</tt> model with public/username-password/API key
authentication) to add OAuth2 authentication and <tt class="docutils literal">server_environment</tt>
support.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
Expand Down
2 changes: 0 additions & 2 deletions webservice/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
from . import test_oauth2
from . import test_webservice
from . import test_utils
181 changes: 45 additions & 136 deletions webservice/views/webservice_backend.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,146 +3,55 @@
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.ui.view" id="webservice_backend_form_view">
<field name="name">webservice.backend.form (in webservice)</field>
<field name="name">webservice.backend.form (oauth2, in webservice)</field>
<field name="model">webservice.backend</field>
<field name="inherit_id" ref="webservice_core.webservice_backend_form_view" />
<field name="arch" type="xml">
<form>
<header>
<button
type="object"
name="button_authorize"
string="OAuth Authorize"
invisible="auth_type != 'oauth2' or oauth2_flow != 'web_application'"
/>
</header>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only" />
<h1>
<field name="name" />
</h1>
<label for="tech_name" class="oe_edit_only" />
<h2>
<field name="tech_name" />
</h2>
</div>
<group name="config">
<field name="company_id" groups="base.group_multi_company" />
<field name="protocol" />
<field name="url" />
<field name="content_type" />
</group>
<group name="auth">
<field name="auth_type" />
<field
name="username"
invisible="auth_type != 'user_pwd'"
required="auth_type == 'user_pwd'"
/>
<field
name="password"
invisible="auth_type != 'user_pwd'"
required="auth_type == 'user_pwd'"
password="True"
/>
<field
name="api_key"
invisible="auth_type != 'api_key'"
required="auth_type == 'api_key'"
password="True"
/>
<field
name="api_key_header"
invisible="auth_type != 'api_key'"
required="auth_type == 'api_key'"
/>
<field
name="oauth2_flow"
invisible="auth_type != 'oauth2'"
required="auth_type == 'oauth2'"
/>
<field
name="redirect_url"
invisible="auth_type != 'oauth2' and oauth2_flow != 'web_application'"
/>
<field
name="oauth2_clientid"
invisible="auth_type != 'oauth2'"
required="auth_type == 'oauth2'"
/>
<field
name="oauth2_client_secret"
invisible="auth_type != 'oauth2'"
required="auth_type == 'oauth2'"
/>
<field
name="oauth2_scope"
invisible="auth_type != 'oauth2' and oauth2_flow != 'web_application'"
required="auth_type == 'oauth2' and oauth2_flow == 'authorization_code'"
/>
<field
name="oauth2_authorization_url"
invisible="auth_type != 'oauth2' and oauth2_flow != 'web_application'"
required="auth_type == 'oauth2' and oauth2_flow == 'authorization_code'"
/>
<field
name="oauth2_token_url"
invisible="auth_type != 'oauth2'"
required="auth_type == 'oauth2'"
/>
<field
name="oauth2_audience"
invisible="auth_type != 'oauth2'"
/>
</group>
</sheet>
</form>
</field>
</record>

<record model="ir.ui.view" id="webservice_backend_search_view">
<field name="name">webservice.backend.search (in webservice)</field>
<field name="model">webservice.backend</field>
<field name="arch" type="xml">
<search>
<field name="name" />
<field name="url" />
<field name="protocol" />
<field name="tech_name" />
</search>
</field>
</record>

<record model="ir.ui.view" id="webservice_backend_tree_view">
<field name="name">webservice.backend.tree (in webservice)</field>
<field name="model">webservice.backend</field>
<field name="arch" type="xml">
<list>
<field name="name" />
<field name="tech_name" />
<field name="url" />
<field name="protocol" />
<xpath expr="//header" position="inside">
<button
type="object"
name="button_authorize"
string="OAuth Authorize"
invisible="auth_type != 'oauth2' or oauth2_flow != 'web_application'"
/>
</xpath>
<xpath expr="//field[@name='api_key_header']" position="after">
<field
name="company_id"
groups="base.group_multi_company"
optional="hide"
name="oauth2_flow"
invisible="auth_type != 'oauth2'"
required="auth_type == 'oauth2'"
/>
</list>
<field
name="redirect_url"
invisible="auth_type != 'oauth2' and oauth2_flow != 'web_application'"
/>
<field
name="oauth2_clientid"
invisible="auth_type != 'oauth2'"
required="auth_type == 'oauth2'"
/>
<field
name="oauth2_client_secret"
invisible="auth_type != 'oauth2'"
required="auth_type == 'oauth2'"
/>
<field
name="oauth2_scope"
invisible="auth_type != 'oauth2' and oauth2_flow != 'web_application'"
required="auth_type == 'oauth2' and oauth2_flow == 'authorization_code'"
/>
<field
name="oauth2_authorization_url"
invisible="auth_type != 'oauth2' and oauth2_flow != 'web_application'"
required="auth_type == 'oauth2' and oauth2_flow == 'authorization_code'"
/>
<field
name="oauth2_token_url"
invisible="auth_type != 'oauth2'"
required="auth_type == 'oauth2'"
/>
<field name="oauth2_audience" invisible="auth_type != 'oauth2'" />
</xpath>
</field>
</record>

<record model="ir.actions.act_window" id="webservice_backend_act_window">
<field name="name">WebService Backend</field>
<field name="res_model">webservice.backend</field>
<field name="view_mode">list,form</field>
<field name="domain">[]</field>
<field name="context">{}</field>
</record>

<record model="ir.ui.menu" id="webservice_backend_menu">
<field name="name">WebService Backend</field>
<field name="parent_id" ref="base.menu_custom" />
<field name="action" ref="webservice_backend_act_window" />
<field name="sequence" eval="100" />
</record>
</odoo>
Loading
Loading