Compare commits

...

5 Commits
12.0 ... 16.0

  1. 1
      galicea_base/README.md
  2. 2
      galicea_base/__init__.py
  3. 23
      galicea_base/__manifest__.py
  4. 6
      galicea_base/views/base_menu.xml
  5. 1
      galicea_environment_checkup/README.md
  6. 9
      galicea_environment_checkup/__init__.py
  7. 32
      galicea_environment_checkup/__manifest__.py
  8. 3
      galicea_environment_checkup/controllers/__init__.py
  9. 26
      galicea_environment_checkup/controllers/dashboard.py
  10. 4
      galicea_environment_checkup/environment_checkup/__init__.py
  11. 49
      galicea_environment_checkup/environment_checkup/core.py
  12. 30
      galicea_environment_checkup/environment_checkup/custom.py
  13. 183
      galicea_environment_checkup/environment_checkup/dependencies.py
  14. 24
      galicea_environment_checkup/environment_checkup/runtime.py
  15. 0
      galicea_environment_checkup/environment_checkup/utils.py
  16. 1
      galicea_environment_checkup/models/__init__.py
  17. 19
      galicea_environment_checkup/models/ext_module.py
  18. BIN
      galicea_environment_checkup/static/description/icon.png
  19. BIN
      galicea_environment_checkup/static/description/images/custom_screenshot.png
  20. BIN
      galicea_environment_checkup/static/description/images/dependencies_screenshot.png
  21. 79
      galicea_environment_checkup/static/description/index.html
  22. 146
      galicea_environment_checkup/static/src/js/environment_checkup.js
  23. 108
      galicea_environment_checkup/static/src/js/environment_checkup10.js
  24. 71
      galicea_environment_checkup/static/src/xml/templates.xml
  25. 9
      galicea_environment_checkup/views/data.xml
  26. 17
      galicea_environment_checkup/views/environment_checks.xml
  27. 20
      galicea_environment_checkup/views/views.xml
  28. 1
      galicea_git/README.md
  29. 5
      galicea_git/__init__.py
  30. 48
      galicea_git/__manifest__.py
  31. 3
      galicea_git/controllers/__init__.py
  32. 86
      galicea_git/controllers/main.py
  33. 12
      galicea_git/data/config.xml
  34. 63
      galicea_git/http_chunked_fix.py
  35. 4
      galicea_git/models/__init__.py
  36. 42
      galicea_git/models/config_settings.py
  37. 105
      galicea_git/models/repository.py
  38. 3
      galicea_git/security/ir.model.access.csv
  39. 47
      galicea_git/security/security.xml
  40. BIN
      galicea_git/static/description/icon.png
  41. BIN
      galicea_git/static/description/images/config_screenshot.png
  42. BIN
      galicea_git/static/description/images/console_screenshot.png
  43. BIN
      galicea_git/static/description/images/create_screenshot.png
  44. 19
      galicea_git/static/description/index.html
  45. 37
      galicea_git/system_checks.py
  46. 71
      galicea_git/views/views.org.xml
  47. 43
      galicea_git/views/views.xml
  48. 1
      galicea_git_oauth/README.md
  49. 4
      galicea_git_oauth/__init__.py
  50. 21
      galicea_git_oauth/__manifest__.py
  51. 1
      galicea_git_oauth/controllers/__init__.py
  52. 14
      galicea_git_oauth/controllers/ext_git_main.py
  53. 1
      galicea_git_oauth/models/__init__.py
  54. 24
      galicea_git_oauth/models/ext_repository.py
  55. 5
      galicea_openapi/__init__.py
  56. 33
      galicea_openapi/__manifest__.py
  57. 4
      galicea_openapi/controllers/__init__.py
  58. 56
      galicea_openapi/controllers/api.py
  59. 22
      galicea_openapi/doc/helloworld.py
  60. 41
      galicea_openapi/doc/test1.py
  61. 3
      galicea_openapi/models/__init__.py
  62. 52
      galicea_openapi/openapi.py
  63. 2
      galicea_openapi/security/ir.model.access.csv
  64. BIN
      galicea_openapi/static/description/icon.png
  65. 1
      galicea_openid_connect/__init__.py
  66. 71
      galicea_openid_connect/__manifest__.py
  67. 23
      galicea_openid_connect/api.py
  68. 7
      galicea_openid_connect/controllers/ext_web_login.py
  69. 111
      galicea_openid_connect/controllers/main.py
  70. 57
      galicea_openid_connect/models/client.py
  71. 45
      galicea_openid_connect/models/config_parameter.py
  72. 33
      galicea_openid_connect/security/__init__.py
  73. 24
      galicea_openid_connect/system_checks.py
  74. 22
      galicea_openid_connect/views/views.xml
  75. 30
      galicea_toolset/README.md
  76. 1
      galicea_toolset/__init__.py
  77. 20
      galicea_toolset/__manifest__.py
  78. 1
      galicea_toolset/static/src/js/.#one2many_flexible_widget.js
  79. 38
      galicea_toolset/static/src/js/client_actions.js
  80. 49
      galicea_toolset/static/src/js/iframe_widget.js
  81. 68
      galicea_toolset/static/src/js/one2many_flexible_widget.js
  82. 16
      galicea_toolset/utils.py
  83. 11
      galicea_toolset/views/data.xml

1
galicea_base/README.md

@ -1 +0,0 @@
Base menu for Odoo Galicea Ecosystem

2
galicea_base/__init__.py

@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-

23
galicea_base/__manifest__.py

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': "Base menu for Odoo Galicea Ecosystem",
'summary': """
Menu only
""",
'author': "Jurek Wawro",
'maintainer': "Galicea",
'website': "http://galicea.pl",
'category': 'Technical Settings',
'version': '12.0.1.0',
'depends': ['web',],
'data': [
'views/base_menu.xml',
],
'installable': True
}

6
galicea_base/views/base_menu.xml

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem name="Galicea" id="galicea_admin_menu"
parent="base.menu_administration" groups="base.group_erp_manager" />
</odoo>

1
galicea_environment_checkup/README.md

@ -1 +0,0 @@
[See add-on page on odoo.com](https://apps.odoo.com/apps/modules/10.0/galicea_environment_checkup/)

9
galicea_environment_checkup/__init__.py

@ -1,9 +0,0 @@
# -*- coding: utf-8 -*-
from . import models
from .environment_checkup.custom import custom_check
from .environment_checkup.core import CheckFail, CheckWarning, CheckSuccess
from . import controllers

32
galicea_environment_checkup/__manifest__.py

@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': "Galicea Environment Check-up",
'summary': """
Programmatically validate environment, including internal and external
dependencies""",
'author': "Maciej Wawro",
'maintainer': "Galicea",
'website': "http://galicea.pl",
'category': 'Technical Settings',
'version': '12.0.1.0',
'depends': ['web','galicea_base',],
'data': [
'views/data.xml',
'views/views.xml',
'views/environment_checks.xml'
],
'qweb': ['static/src/xml/templates.xml'],
'images': [
'static/description/images/custom_screenshot.png',
'static/description/images/dependencies_screenshot.png'
],
'installable': True
}

3
galicea_environment_checkup/controllers/__init__.py

@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import dashboard

26
galicea_environment_checkup/controllers/dashboard.py

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import http
from odoo.exceptions import AccessError
from odoo.http import request
from ..environment_checkup.runtime import all_installed_checks, display_data
from ..environment_checkup.core import CheckResult
class Dashboard(http.Controller):
@http.route('/galicea_environment_checkup/data', type='json', auth='user')
def data(self, request, **kw):
if not request.env.user.has_group('base.group_erp_manager'):
raise AccessError("Access Denied")
checks = all_installed_checks(request.env)
response = display_data(request.env, checks)
priority = {
CheckResult.FAIL: 0,
CheckResult.WARNING: 1,
CheckResult.SUCCESS: 2
}
response.sort(key=lambda res: (priority[res['result']], res['module']))
return response

4
galicea_environment_checkup/environment_checkup/__init__.py

@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
from . import core
from . import custom

49
galicea_environment_checkup/environment_checkup/core.py

@ -1,49 +0,0 @@
# -*- coding: utf-8 -*-
import logging
_logger = logging.getLogger(__name__)
class CheckResult(object):
SUCCESS = 'success'
WARNING = 'warning'
FAIL = 'fail'
def __init__(self, result, message, details = None):
super(CheckResult, self).__init__()
self.result = result
self.message = message
self.details = details
class CheckSuccess(CheckResult):
def __init__(self, message, **kwargs):
super(CheckSuccess, self).__init__(CheckResult.SUCCESS, message, **kwargs)
class CheckIssue(CheckResult, Exception):
def __init__(self, result, message, **kwargs):
Exception.__init__(self, message)
CheckResult.__init__(self, result, message, **kwargs)
class CheckFail(CheckIssue):
def __init__(self, message, **kwargs):
super(CheckFail, self).__init__(CheckResult.FAIL, message, **kwargs)
class CheckWarning(CheckIssue):
def __init__(self, message, **kwargs):
super(CheckWarning, self).__init__(CheckResult.WARNING, message, **kwargs)
class Check(object):
def __init__(self, module):
self.module = module
def run(self, env):
try:
return self._run(env)
except CheckIssue as issue:
return issue
except Exception as ex:
_logger.exception(ex)
return CheckFail('Check failed when processing: {}'.format(ex))
def _run(self, env):
raise NotImplementedError('Should be overriden by the subclass')

30
galicea_environment_checkup/environment_checkup/custom.py

@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
import collections
from .core import Check
custom_checks_per_module = collections.defaultdict(list)
class CustomCheck(Check):
def __init__(self, module, func):
super(CustomCheck, self).__init__(module)
self.func = func
def _run(self, env):
return self.func(env)
def custom_check(func):
try:
module = func.__module__.split('.')[2]
except IndexError:
module = ''
custom_checks_per_module[module].append(
CustomCheck(module=module, func=func)
)
return func
def get_checks_for_module(module_name):
return custom_checks_per_module[module_name]

183
galicea_environment_checkup/environment_checkup/dependencies.py

@ -1,183 +0,0 @@
# -*- coding: utf-8 -*-
import subprocess
import re
import cgi
from odoo.modules.module import load_information_from_description_file
from odoo.tools import which
from .core import Check, CheckSuccess, CheckWarning, CheckFail
class DependencyCheck(Check):
dependency_type = None
def __init__(self, module, dependency):
super(DependencyCheck, self).__init__(module)
self.dependency = dependency
def _dependency_installed(self, env, name):
raise NotImplementedError('Should be overriden by the subclass')
def _installed_version(self, env, name):
raise NotImplementedError('Should be overriden by the subclass')
def _details(self):
if 'install' in self.dependency:
return 'Install command: <pre>{}</pre>'.format(self.dependency['install'])
return None
def __has_required_version(self, installed_version, version_expression):
version_operator = '='
version = self.dependency['version']
if version[:1] in ['=', '~', '^']:
version_operator = version[:1]
version = version[1:]
elif version[:2] in ['>=']:
version_operator = version[:2]
version = version[2:]
# Py3 : map -> list(map
# https://stackoverflow.com/questions/33717314/attributeerror-map-obejct-has-no-attribute-index-python-3
try:
parsed_version = list(map(int, version.split('.')))
except ValueError:
raise CheckFail(
'Invalid version expression',
details = """
Allowed expressions are <pre>=x.y.z</pre>, <pre>&gt;=x.y.z</pre>, <pre>^x.z.y</pre>,
<pre>~x.y.z. Got <pre>{}</pre>""".format(cgi.escape(self.dependency['version']))
)
parsed_installed_version = list(map(int, installed_version.split('.')))
parsed_version.extend(0 for _ in range(len(parsed_installed_version) - len(parsed_version)))
parsed_installed_version.extend(0 for _ in range(len(parsed_version) - len(parsed_installed_version)))
if version_operator == '^':
if parsed_installed_version[:1] != parsed_version[:1]:
return False
version_operator = '>='
elif version_operator == '~':
if parsed_installed_version[:2] != parsed_version[:2]:
return False
version_operator = '>='
if version_operator == '>=':
return tuple(parsed_installed_version) >= tuple(parsed_version)
elif version_operator == '=':
return tuple(parsed_installed_version) == tuple(parsed_version)
assert False
def _run(self, env):
name = self.dependency['name']
if not self._dependency_installed(env, name):
raise CheckFail(
'Required {} - {} - is not installed.'.format(self.dependency_type, name),
details=self._details()
)
if 'version' in self.dependency:
version_expression = self.dependency['version']
installed_version = self._installed_version(env, name)
if not self.__has_required_version(installed_version, version_expression):
raise CheckWarning(
'Required {} - {} - has version {}, but {} is needed.'.format(
self.dependency_type,
name,
installed_version,
version_expression
),
details=self._details()
)
return CheckSuccess(
'Required {} - {} - is installed.'.format(self.dependency_type, name),
details=self._details()
)
class InternalDependencyCheck(DependencyCheck):
dependency_type = 'Odoo module'
def _dependency_installed(self, env, name):
return name in env.registry._init_modules
def _installed_version(self, env, name):
return env['ir.module.module'].sudo().search([('name', '=', name)]).latest_version
class PythonDependencyCheck(DependencyCheck):
dependency_type = 'Python module'
def _dependency_installed(self, env, name):
try:
__import__(name)
return True
except ImportError:
return False
def _installed_version(self, env, name):
try:
return __import__(name).__version__
except AttributeError:
raise CheckWarning(
'Could not detect version of the Python module: {}.'.format(name),
details=self._details()
)
class ExternalDependencyCheck(DependencyCheck):
dependency_type = 'system executable'
def _dependency_installed(self, env, name):
try:
which(name)
return True
except IOError:
return False
def _installed_version(self, env, name):
try:
exe = which(name)
out = str(subprocess.check_output([exe, '--version'])) # Py3 str()
match = re.search('[\d.]+', out)
if not match:
raise CheckWarning(
'Unable to detect version for executable {}'.format(name),
details="Command {} --version returned <pre>{}</pre>".format(exe, out)
)
return match.group(0)
except subprocess.CalledProcessError as e:
raise CheckWarning(
'Unable to detect version for executable {}: {}'.format(name, e),
details=self._details()
)
def get_checks_for_module(module_name):
result = []
manifest = load_information_from_description_file(module_name)
manifest_checks = manifest.get('environment_checkup') or {}
dependencies = manifest_checks.get('dependencies') or {}
for dependency in dependencies.get('python') or []:
result.append(PythonDependencyCheck(module_name, dependency))
for dependency in dependencies.get('external') or []:
result.append(ExternalDependencyCheck(module_name, dependency))
for dependency in dependencies.get('internal') or []:
result.append(InternalDependencyCheck(module_name, dependency))
return result
def get_checks_for_module_recursive(module):
class ModuleDFS(object):
def __init__(self):
self.visited_modules = set()
self.checks = []
def visit(self, module):
if module.name in self.visited_modules:
return
self.visited_modules.add(module.name)
self.checks += get_checks_for_module(module.name)
for module_dependency in module.dependencies_id:
if module_dependency.depend_id:
self.visit(module_dependency.depend_id)
return self
return ModuleDFS().visit(module).checks

24
galicea_environment_checkup/environment_checkup/runtime.py

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
from . import custom, dependencies
def all_installed_checks(env):
result = []
installed_modules = env.registry._init_modules
for module_name in installed_modules:
result += custom.get_checks_for_module(module_name)
result += dependencies.get_checks_for_module(module_name)
return result
def display_data(env, checks):
response = []
for check in checks:
result = check.run(env)
response.append({
'module': check.module,
'message': result.message,
'details': result.details,
'result': result.result
})
return response

0
galicea_environment_checkup/environment_checkup/utils.py

1
galicea_environment_checkup/models/__init__.py

@ -1 +0,0 @@
from . import ext_module

19
galicea_environment_checkup/models/ext_module.py

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
import json
from odoo import api, fields, models
from ..environment_checkup import dependencies
from ..environment_checkup.runtime import display_data
class Module(models.Model):
_inherit = 'ir.module.module'
dependency_checks = fields.Text(
compute='_compute_dependency_checks'
)
@api.one
def _compute_dependency_checks(self):
checks = dependencies.get_checks_for_module_recursive(self)
self.dependency_checks = json.dumps(display_data(self.env, checks))

BIN
galicea_environment_checkup/static/description/icon.png

Before

Width: 80  |  Height: 80  |  Size: 3.4 KiB

BIN
galicea_environment_checkup/static/description/images/custom_screenshot.png

Before

Width: 625  |  Height: 198  |  Size: 24 KiB

BIN
galicea_environment_checkup/static/description/images/dependencies_screenshot.png

Before

Width: 966  |  Height: 755  |  Size: 92 KiB

79
galicea_environment_checkup/static/description/index.html

@ -1,79 +0,0 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Galicea Environment Check-up</h2>
<h3 class="oe_slogan">
Programmatically validate Odoo environment, including internal and external dependencies of your add-on
</h3>
This add-on allows you to:
<ul>
<li>programmatically check software dependencies required by your add-on, as well as inform the Administrator as to how to meet them,</li>
<li>add custom verification for Odoo instance set-up and inform the Administrator about any inconsistencies.</li>
</ul>
<h2>Add-on dependency verification</h2>
<img class="oe_picture oe_screenshot" src="images/dependencies_screenshot.png" />
<h3>How-to</h3>
Just add <tt>'environment_checkup'</tt> entry to <tt>__manifest__.py</tt>.
<pre>
{
...
'environment_checkup': {
'dependencies': {
'python': [
{
'name': 'Crypto',
'version': '>=2.6.2',
'install': "pip install 'PyCrypto>=2.6.1'"
},
],
'external': [
{
'name': 'wkhtmltopdf',
'install': "apt install wkhtmltopdf"
},
{
'name': 'git',
'version': '^3.0.0',
'install': "apt install git"
}
],
'internal': [
{
'name': 'web',
'version': '~10.0.1.0'
}
]
}
}
}
</pre>
<h2>Custom environment verification</h2>
<img class="oe_picture oe_screenshot" src="images/custom_screenshot.png" />
<h3>How-to</h3>
1. Add the check, e.g. in the <tt>system_checks.py</tt> file:
<pre>
# -*- coding: utf-8 -*-
import cgi
from odoo.addons.galicea_environment_checkup import custom_check, CheckSuccess, CheckWarning, CheckFail
@custom_check
def check_mail(env):
users_without_emails = env['res.users'].sudo().search([('email', '=', False)])
if users_without_emails:
raise CheckWarning(
'Some users don\'t have their e-mails set up.',
details='See user <tt>{}</tt>.'.format(cgi.escape(users_without_emails[0].name))
)
return CheckSuccess('All users have their e-mails set.')
</pre>
2. Make sure it's loaded by <tt>__init__.py</tt>
<pre>
# -*- coding: utf-8 -*-
from . import system_checks
</pre>
</div>
</div>
</section>

146
galicea_environment_checkup/static/src/js/environment_checkup.js

@ -1,146 +0,0 @@
odoo.define('galicea_environment_checkup', function (require) {
"use strict";
//var SystrayMenu = require('web.SystrayMenu');
//var Model = require('web.Model');
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
//var framework = require('web.framework');
var session = require('web.session');
//var Widget = require('web.Widget');
//////////////////
var QWeb = core.qweb;
//var _t = core._t;
/* SystrayIcon - nie działa poprawnie ???
//https://www.odoo.com/documentation/12.0/reference/javascript_reference.html
var Model = require('web.Model');
var Users = new Model('res.users');
var SystrayIcon = Widget.extend({
tagName: 'li',
events: {
"click": "on_click",
},
start: function(){
this.load(this.all_dashboards);
return this._super();
},
load: function(dashboards){
var self = this;
var loading_done = new $.Deferred();
Users.call('has_group', ['base.group_erp_manager']).then(function(is_admin) {
if (is_admin) {
session.rpc('/galicea_environment_checkup/data', {})
.then(function (data) {
var counts = { 'success': 0, 'warning': 0, 'fail': 0 };
data.forEach(function (check) { ++counts[check.result]; });
var result;
if (counts['fail']) {
result = 'fail';
} else if (counts['warning']) {
result = 'warning';
} else {
result = 'success';
}
self.replaceElement(QWeb.render('GaliceaEnvironmentCheckupIcon', {
'result': result,
'count': counts['warning'] + counts['fail']
}));
loading_done.resolve();
});
} else {
loading_done.resolve();
}
});
return loading_done;
},
on_click: function (event) {
event.preventDefault();
this.do_action('galicea_environment_checkup.dashboard_action', {clear_breadcrumbs: true});
},
});
*/
/////////////////////////////
var Dashboard = AbstractAction.extend({
// v.10 var Dashboard = Widget.extend({
start: function(){
return this.load(this.all_dashboards);
},
load: function(dashboards) {
var self = this;
var loading_done = new $.Deferred();
session.rpc('/galicea_environment_checkup/data', {})
.then(function (data) {
self._replaceElement(QWeb.render('GaliceaEnvironmentCheckupDashboard', {'data': data})); // v.10: self.replaceElement
loading_done.resolve();
});
return loading_done;
},
});
//!JW - nowa propozycja: core.action_registry.add('galicea_environment_checkup.environment_checkup', Dashboard);
core.action_registry.add('galicea_environment_checkup.dashboard', Dashboard);
////////////////////
/* v.10
var FormWidget = form_common.AbstractField.extend({
init: function() {
this._super.apply(this, arguments);
this.set("value", "[]");
},
render_value: function() {
var data = JSON.parse(this.get('value'));
if (data.length == 0) {
this.replaceElement('<div />');
return;
}
this.replaceElement(QWeb.render('GaliceaEnvironmentCheckupFormWidget', {'data': data}));
},
});
core.form_widget_registry.add('environment_checks', FormWidget);
*/
var FormView = require('web.FormView');
var FormWidget = FormView.extend({
template: "environment_checks",
init: function() {
this._super.apply(this, arguments);
this.set("value", "[]");
},
events: {
},
render_value: function() {
var data = JSON.parse(this.get('value'));
if (data.length == 0) {
this._replaceElement('<div />');
return;
}
this._replaceElement(QWeb.render('GaliceaEnvironmentCheckupFormWidget', {'data': data}));
}
});
////////////////////
return {
//!! SystrayIcon: SystrayIcon,
Dashboard: Dashboard,
FormWidget: FormWidget
};
});

108
galicea_environment_checkup/static/src/js/environment_checkup10.js

@ -1,108 +0,0 @@
odoo.define('galicea_environment_checkup', function(require) {
"use strict";
var core = require('web.core');
var form_common = require('web.form_common');
var Widget = require('web.Widget');
var session = require('web.session');
var QWeb = core.qweb;
var SystrayMenu = require('web.SystrayMenu');
var Model = require('web.Model');
var Users = new Model('res.users');
var SystrayIcon = Widget.extend({
tagName: 'li',
events: {
"click": "on_click",
},
start: function(){
this.load(this.all_dashboards);
return this._super();
},
load: function(dashboards){
var self = this;
var loading_done = new $.Deferred();
Users.call('has_group', ['base.group_erp_manager']).then(function(is_admin) {
if (is_admin) {
session.rpc('/galicea_environment_checkup/data', {})
.then(function (data) {
var counts = { 'success': 0, 'warning': 0, 'fail': 0 };
data.forEach(function (check) { ++counts[check.result]; });
var result;
if (counts['fail']) {
result = 'fail';
} else if (counts['warning']) {
result = 'warning';
} else {
result = 'success';
}
self.replaceElement(QWeb.render('GaliceaEnvironmentCheckupIcon', {
'result': result,
'count': counts['warning'] + counts['fail']
}));
loading_done.resolve();
});
} else {
loading_done.resolve();
}
});
return loading_done;
},
on_click: function (event) {
event.preventDefault();
this.do_action('galicea_environment_checkup.dashboard_action', {clear_breadcrumbs: true});
},
});
SystrayMenu.Items.push(SystrayIcon);
var Dashboard = Widget.extend({
start: function(){
return this.load(this.all_dashboards);
},
load: function(dashboards) {
var self = this;
var loading_done = new $.Deferred();
session.rpc('/galicea_environment_checkup/data', {})
.then(function (data) {
self.replaceElement(QWeb.render('GaliceaEnvironmentCheckupDashboard', {'data': data}));
loading_done.resolve();
});
return loading_done;
},
});
core.action_registry.add('galicea_environment_checkup.dashboard', Dashboard);
var FormWidget = form_common.AbstractField.extend({
init: function() {
this._super.apply(this, arguments);
this.set("value", "[]");
},
render_value: function() {
var data = JSON.parse(this.get('value'));
if (data.length == 0) {
this.replaceElement('<div />');
return;
}
this.replaceElement(QWeb.render('GaliceaEnvironmentCheckupFormWidget', {'data': data}));
},
});
core.form_widget_registry.add('environment_checks', FormWidget);
return {
SystrayIcon: SystrayIcon,
Dashboard: Dashboard,
FormWidget: FormWidget
};
});

71
galicea_environment_checkup/static/src/xml/templates.xml

@ -1,71 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="GaliceaEnvironmentCheckupIcon">
<li>
<a href="#" t-att-title="result == 'success' ? 'No system setup issues' : 'Found '+count+' system setup issues'">
<t t-if="result == 'success'">
<i class="fa fa-check" style="opacity:0.5"></i>
</t>
<t t-if="result == 'warning'">
<i class="fa fa-exclamation-triangle" style="color: orange"></i>
</t>
<t t-if="result == 'fail'">
<i class="fa fa-exclamation-circle" style="color: red"></i>
</t>
<t t-if="result != 'success'">
<span><t t-raw="count" /></span>
</t>
</a>
</li>
</t>
<t t-name="GaliceaEnvironmentChecks">
<div class="row">
<t t-foreach="data" t-as="check">
<div class="col-md-12">
<div style="display: flex; background-color: white; padding: 10px; margin: 10px">
<div style="flex-grow: 0; flex-shrink: 0; width:50px; margin-right: 20px">
<t t-if="check.result == 'success'">
<i class="fa fa-check fa-4x" style="color: green" ></i>
</t>
<t t-if="check.result == 'warning'">
<i class="fa fa-exclamation-triangle fa-4x" style="color: orange" ></i>
</t>
<t t-if="check.result == 'fail'">
<i class="fa fa-exclamation-circle fa-4x" style="color: red" ></i>
</t>
</div>
<div style="flex-grow: 1; flex-shrink: 1">
Module: <t t-esc="check.module" />
<h4><t t-esc="check.message" /></h4>
<div>
<t t-raw="check.details" />
</div>
</div>
</div>
</div>
</t>
</div>
</t>
<t t-name="GaliceaEnvironmentCheckupDashboard">
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>Environment check-up</h2>
<t t-call="GaliceaEnvironmentChecks">
<t t-set="data" t-value="data" />
</t>
</div>
</div>
</div>
</t>
<t t-name="GaliceaEnvironmentCheckupFormWidget">
<h1>Module dependencies</h1>
<t t-call="GaliceaEnvironmentChecks">
<t t-set="data" t-value="data" />
</t>
</t>
</templates>

9
galicea_environment_checkup/views/data.xml

@ -1,9 +0,0 @@
<odoo>
<data>
<template id="assets_backend" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script src="/galicea_environment_checkup/static/src/js/environment_checkup.js" type="text/javascript" />
</xpath>
</template>
</data>
</odoo>

17
galicea_environment_checkup/views/environment_checks.xml

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="stale_modules">
<p>The following modules need to be upgraded:</p>
<ul>
<t t-foreach="modules" t-as="module">
<li>
<a t-att-href="'#id={}&amp;view_type=form&amp;model=ir.module.module'.format(module.id)">
<t t-esc="module.shortdesc" /> (technical name: <t t-esc="module.name" />;
version on disk: <strong><t t-esc="module.installed_version" /></strong>;
version in DB: <strong><t t-esc="module.latest_version" /></strong>)
</a>
</li>
</t>
</ul>
</template>
</odoo>

20
galicea_environment_checkup/views/views.xml

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="dashboard_action" model="ir.actions.client">
<field name="name">Environment check-up dashboard</field>
<field name="tag">galicea_environment_checkup.dashboard</field>
</record>
<menuitem name="Environment check-up" id="dashboard_menu"
action="dashboard_action" parent="galicea_base.galicea_admin_menu" groups="base.group_erp_manager" />
<record id="module_form" model="ir.ui.view">
<field name="name">module_form.checks</field>
<field name="model">ir.module.module</field>
<field name="inherit_id" ref="base.module_form"/>
<field name="arch" type="xml">
<field name="description_html" position="before">
<field name="dependency_checks" widget="environment_checks" />
</field>
</field>
</record>
</odoo>

1
galicea_git/README.md

@ -1 +0,0 @@
[See add-on page on odoo.com](https://apps.odoo.com/apps/modules/10.0/galicea_git/)

5
galicea_git/__init__.py

@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
from . import controllers
from . import models
from . import system_checks

48
galicea_git/__manifest__.py

@ -1,48 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': "Galicea Git hosting",
'summary': """Git repository hosting and per-user access checking""",
'author': "Maciej Wawro",
'maintainer': "Galicea",
'website': "http://galicea.pl",
'category': 'Technical Settings',
'version': '12.0.0.2',
'depends': ['web', 'galicea_environment_checkup','galicea_base'],
'external_dependencies': {
'bin': ['git']
},
'data': [
'security/security.xml',
'security/ir.model.access.csv',
'data/config.xml',
'views/views.xml',
],
'images': [
'static/description/images/create_screenshot.png',
'static/description/images/config_screenshot.png',
'static/description/images/console_screenshot.png',
],
'application': True,
'installable': True,
'environment_checkup': {
'dependencies': {
'external': [
{
'name': 'git',
'version': '>=2.1.4',
'install': "apt install git"
}
]
}
}
}

3
galicea_git/controllers/__init__.py

@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import main

86
galicea_git/controllers/main.py

@ -1,86 +0,0 @@
# -*- coding: utf-8 -*-
import subprocess, os, io
from odoo import http
from odoo.tools import config
import werkzeug
from ..http_chunked_fix import http_input_stream
class Main(http.Controller):
def authorize(self, request):
auth = request.httprequest.authorization
if auth:
request.session.authenticate(request.session.db, auth.username, auth.password)
@http.route(
[
'/git/<repo>',
'/git/<repo>/<path:path>',
],
auth='public',
csrf=False
)
def git(self, request, repo, **kw):
self.authorize(request)
if not request.env.uid or request.env.user.login == 'public':
return werkzeug.Response(
headers=[('WWW-Authenticate', 'Basic')],
status=401
)
try:
repository = request.env['galicea_git.repository'].search(
[('system_name', '=', repo)]
)
except AccessError:
return werkzeug.Response(
status=403
)
if not repository.exists():
return werkzeug.Response(
status=404
)
http_environment = request.httprequest.environ
git_env = {
'REQUEST_METHOD': http_environment['REQUEST_METHOD'],
'QUERY_STRING': http_environment['QUERY_STRING'],
'CONTENT_TYPE': request.httprequest.headers.get('Content-Type'),
'REMOTE_ADDR': http_environment['REMOTE_ADDR'],
'GIT_PROJECT_ROOT': os.path.join(config['data_dir'], 'git'),
'GIT_HTTP_EXPORT_ALL': '1',
'PATH_INFO': http_environment['PATH_INFO'][4:],
'REMOTE_USER': request.env.user.login
}
command_env = os.environ.copy()
for var in git_env:
command_env[var] = git_env[var]
git = subprocess.Popen(
['/usr/lib/git-core/git-http-backend'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=command_env,
shell=True
)
stdout, stderr = git.communicate(http_input_stream(request).read())
headers_str, body = stdout.split(b"\r\n\r\n", 2)
http_status_code = 200
headers = []
for header in headers_str.split(b"\r\n"):
name, value = header.split(b': ', 2)
if name == 'Status':
http_code = int(value.split(b' ')[0])
else:
headers.append((name.decode('ascii'), value.decode('ascii')))
return werkzeug.Response(
body,
status = http_status_code,
headers = headers
)

12
galicea_git/data/config.xml

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="config_git_backend_path" model="ir.config_parameter">
<field name="key">galicea_git.git_http_backend</field>
<field name="value">/usr/lib/git-core/git-http-backend</field>
<field name="group_ids" eval="[(4, ref('galicea_git.group_admin'))]" />
</record>
</data>
</odoo>

63
galicea_git/http_chunked_fix.py

@ -1,63 +0,0 @@
# -*- coding: utf-8 -*-
import io
# Werkzeug version bundled into odoo doesn't handle this kind of Transfer-Encoding
# correctly. We copy the fix from https://github.com/pallets/werkzeug/pull/1198/files
class DechunkedInput(io.RawIOBase):
"""An input stream that handles Transfer-Encoding 'chunked'"""
def __init__(self, rfile):
self._rfile = rfile
self._done = False
self._len = 0
def readable(self):
return True
def read_chunk_len(self):
try:
line = self._rfile.readline().decode('latin1')
_len = int(line.strip(), 16)
except ValueError:
raise IOError('Invalid chunk header')
if _len < 0:
raise IOError('Negative chunk length not allowed')
return _len
def readinto(self, buf):
read = 0
while not self._done and read < len(buf):
if self._len == 0:
# This is the first chunk or we fully consumed the previous
# one. Read the next length of the next chunk
self._len = self.read_chunk_len()
if self._len == 0:
# Found the final chunk of size 0. The stream is now exhausted,
# but there is still a final newline that should be consumed
self._done = True
if self._len > 0:
# There is data (left) in this chunk, so append it to the
# buffer. If this operation fully consumes the chunk, this will
# reset self._len to 0.
n = min(len(buf), self._len)
buf[read:read + n] = self._rfile.read(n)
self._len -= n
read += n
if self._len == 0:
# Skip the terminating newline of a chunk that has been fully
# consumed. This also applies to the 0-sized final chunk
terminator = self._rfile.readline()
if terminator not in (b'\n', b'\r\n', b'\r'):
raise IOError('Missing chunk terminating newline')
return read
def http_input_stream(request):
if request.httprequest.headers.get('Transfer-Encoding') == 'chunked' \
and not request.httprequest.environ.get('wsgi.input_terminated'):
return DechunkedInput(request.httprequest.environ['wsgi.input'])
return request.httprequest.stream

4
galicea_git/models/__init__.py

@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
from . import repository
from . import config_settings

42
galicea_git/models/config_settings.py

@ -1,42 +0,0 @@
# -*- coding: utf-8 -*-
import os
from odoo import models, fields, api
from odoo.exceptions import ValidationError
class ConfigSettings(models.TransientModel):
_name = 'galicea_git.config.settings'
_inherit = 'res.config.settings'
git_http_backend = fields.Char(
'Absolute path to Git HTTP backend',
required=True
)
git_http_backend_valid = fields.Boolean(
compute='_compute_git_http_backend_valid'
)
@api.one
@api.depends('git_http_backend')
def _compute_git_http_backend_valid(self):
self.git_http_backend_valid = self.git_http_backend and os.access(self.git_http_backend, os.X_OK)
@api.one
def set_params(self):
self.env['ir.config_parameter'].set_param('galicea_git.git_http_backend', self.git_http_backend)
@api.model
def get_default_values(self, fields):
return {
'git_http_backend': self.env['ir.config_parameter'].get_param('galicea_git.git_http_backend')
}
@api.multi
def execute(self):
self.ensure_one()
if not self.env.user.has_group('galicea_git.group_admin'):
raise AccessError("Only Git administrators can change those settings")
super(ConfigSettings, self.sudo()).execute()
act_window = self.env.ref('galicea_git.config_settings_action')
return act_window.read()[0]

105
galicea_git/models/repository.py

@ -1,105 +0,0 @@
# -*- coding: utf-8 -*-
import os
import random
import shutil
import string
import subprocess
try:
import git
except ImportError:
pass
from odoo import models, fields, api, http
from odoo.exceptions import ValidationError
from odoo.tools import config, which
class Repository(models.Model):
_name = 'galicea_git.repository'
state = fields.Selection(
[('draft', 'Draft'), ('created', 'Created')],
default='draft'
)
name = fields.Char('User-friendly name', required=True)
system_name = fields.Char(
'Directory name',
required=True,
readonly=True,
index=True,
states={'draft': [('readonly', False)]}
)
collaborator_ids = fields.Many2many(
'res.users',
string='Collaborators'
)
local_directory = fields.Char(
'Local directory on server',
compute='_compute_local_directory',
groups='galicea_git.group_admin'
)
url = fields.Char(
'Clone',
compute='_compute_url'
)
@api.one
@api.depends('system_name')
def _compute_url(self):
base_url = http.request.httprequest.host_url if http.request \
else env['ir.config_parameter'].get_param('web.base.url') + '/'
self.url = u'{}git/{}'.format(base_url, self.system_name)
@api.one
@api.depends('system_name')
def _compute_local_directory(self):
if self.system_name:
self.local_directory = os.path.join(config['data_dir'], 'git', self.system_name)
@api.constrains('system_name')
def _validate_system_name(self):
allowed_characters = string.ascii_lowercase + string.digits + '-_'
if not all(c in allowed_characters for c in self.system_name):
raise ValidationError(
'Only lowercase, digits and hyphens (-) are allowed in directory name'
)
@api.constrains('collaborator_ids')
def _validate_collaborator_ids(self):
invalid_collaborators = self.collaborator_ids.filtered(lambda c: not c.has_group('galicea_git.group_collaborator'))
if invalid_collaborators:
raise ValidationError(
'User {} does not have the {} role. Contact your Administrator'.format(
invalid_collaborators[0].name,
self.env.ref('galicea_git.group_collaborator').full_name
)
)
@api.model
def create(self, values):
values['state'] = 'created'
ret = super(Repository, self).create(values)
ret.__initialize_repo()
return ret
@api.multi
def unlink(selfs):
directories_to_move = selfs.mapped(lambda r: r.local_directory)
ret = super(Repository, selfs).unlink()
for directory in directories_to_move:
if os.path.exists(directory):
suffix = ''.join(random.choice(string.ascii_lowercase) for _ in range(8))
new_directory = directory + '-deleted-' + suffix
shutil.move(directory, new_directory)
@api.multi
def __initialize_repo(self):
self.ensure_one()
if os.path.exists(self.local_directory):
raise ValidationError(
'Repository {} already exists, choose a different name!'.format(self.system_name)
)
subprocess.check_call([which('git'), 'init', '--bare', self.local_directory])

3
galicea_git/security/ir.model.access.csv

@ -1,3 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_repository_collaborator,repository_collaborator,model_galicea_git_repository,galicea_git.group_collaborator,1,0,0,0
access_repository_admin,repository_admin,model_galicea_git_repository,galicea_git.group_admin,1,1,1,1

47
galicea_git/security/security.xml

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="module_category_git" model="ir.module.category">
<field name="name">Git</field>
</record>
<record id="group_collaborator" model="res.groups">
<field name="name">Collaborator</field>
<field name="category_id" ref="module_category_git" />
</record>
<record id="group_admin" model="res.groups">
<field name="name">Administrator</field>
<field name="category_id" ref="module_category_git" />
<field name="implied_ids" eval="[(4,ref('group_collaborator'))]" />
</record>
<record id="base.group_erp_manager" model="res.groups">
<field name="implied_ids" eval="[(4,ref('group_admin'))]" />
</record>
<record id="repository_collaborator_access_rule" model="ir.rule">
<field name="name">Collaborators can only access repositories they are assigned to</field>
<field name="model_id" ref="model_galicea_git_repository" />
<field name="groups" eval="[(4, ref('group_collaborator'))]"/>
<field name="domain_force">
[('collaborator_ids', 'in', user.id)]
</field>
<field eval="1" name="perm_read" />
<field eval="0" name="perm_write" />
<field eval="0" name="perm_create" />
<field eval="0" name="perm_unlink" />
</record>
<record id="repository_admin_access_rule" model="ir.rule">
<field name="name">Administrators can access any repositories</field>
<field name="model_id" ref="model_galicea_git_repository" />
<field name="groups" eval="[(4, ref('group_admin'))]"/>
<field name="domain_force">
[(1, '=', 1)]
</field>
<field eval="1" name="perm_read" />
<field eval="0" name="perm_write" />
<field eval="0" name="perm_create" />
<field eval="0" name="perm_unlink" />
</record>
</odoo>

BIN
galicea_git/static/description/icon.png

Before

Width: 80  |  Height: 80  |  Size: 3.8 KiB

BIN
galicea_git/static/description/images/config_screenshot.png

Before

Width: 800  |  Height: 354  |  Size: 42 KiB

BIN
galicea_git/static/description/images/console_screenshot.png

Before

Width: 738  |  Height: 359  |  Size: 78 KiB

BIN
galicea_git/static/description/images/create_screenshot.png

Before

Width: 805  |  Height: 273  |  Size: 31 KiB

19
galicea_git/static/description/index.html

@ -1,19 +0,0 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Galicea Git hosting</h2>
<h3 class="oe_slogan">
Simple Odoo-based HTTP interface for Git repository hosting
</h3>
<p>
This add-on allows you to create Git repositories hosted by Odoo, and add specific Odoo users as collaborators. Only those users will have access to the repository. It requires <tt>git</tt> package, including <tt>git-http-backend</tt>, installed in the system. For Ubuntu/Debian it's enough to call
<pre>sudo apt install git</pre>
</p>
<h3>Creating repositories</h3>
<img class="oe_picture oe_screenshot" src="images/create_screenshot.png" />
<img class="oe_picture oe_screenshot" src="images/config_screenshot.png" />
<h3>Interacting with the repository</h3>
<img class="oe_picture oe_screenshot" src="images/console_screenshot.png" />
</div>
</div>
</section>

37
galicea_git/system_checks.py

@ -1,37 +0,0 @@
# -*- coding: utf-8 -*-
import os
from odoo.addons.galicea_environment_checkup import \
custom_check, CheckWarning, CheckSuccess, CheckFail
from odoo import http
@custom_check
def check_single_db(env):
if not http.request:
raise CheckWarning('Could not detect DB settings.')
dbs = http.db_list(True, http.request.httprequest)
if len(dbs) == 1:
return CheckSuccess('Odoo runs in a single-DB mode.')
details = (
'<p>Odoo runs in a multi-DB mode, which will cause Git HTTP requests to fail.</p>'
'<p>Run Odoo with <tt>--dbfilter</tt> or <tt>--database</tt> flag.</p>'
)
return CheckFail(
'Odoo runs in a multi-DB mode.',
details=details
)
@custom_check
def check_http_backend(env):
backend_path = env['ir.config_parameter'].sudo().get_param(
'galicea_git.git_http_backend'
)
if not os.access(backend_path, os.X_OK):
raise CheckFail(
'Git HTTP backend not found',
details='<a href="http://galicea.mw-odoo:8080/web#action=galicea_git.config_settings_action">Check the configuration here</a>'
)
return CheckSuccess('Git HTTP backend was found')

71
galicea_git/views/views.org.xml

@ -1,71 +0,0 @@
<odoo>
<data>
<record id="repository_view_form" model="ir.ui.view">
<field name="model">galicea_git.repository</field>
<field name="arch" type="xml">
<form>
<group>
<field name="state" invisible="1" />
<field name="name" />
<field name="system_name" groups="galicea_git.group_admin" />
<field name="collaborator_ids" widget="many2many_tags" options="{'no_create': True}" />
</group>
<group class="oe_read_only">
<label for="url" />
<span style="font-family: monospace">git clone <field name="url" nolabel="True" /></span>
<field name="local_directory" style="font-family: monospace" />
</group>
</form>
</field>
</record>
<record id="repository_view_tree" model="ir.ui.view">
<field name="model">galicea_git.repository</field>
<field name="arch" type="xml">
<tree>
<field name="state" invisible="1" />
<field name="name" />
<field name="system_name" groups="galicea_git.group_admin" />
</tree>
</field>
</record>
<act_window id="repository_action"
name="Git repositories"
res_model="galicea_git.repository" />
<record id="config_settings_view_form" model="ir.ui.view">
<field name="model">galicea_git.config.settings</field>
<field name="arch" type="xml">
<form string="Git hosting settings" class="oe_form_configuration">
<header>
<button string="Save" type="object" name="execute" class="oe_highlight"/>
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
</header>
<field name="git_http_backend_valid" invisible="1" />
<group>
<label for="git_http_backend" />
<span>
<field name="git_http_backend" nolabel="True" class="oe_inline" style="min-width:300px; margin-right:5px" />
<i class="fa fa-check" aria-hidden="true" style="color: green"
attrs="{'invisible': [('git_http_backend_valid', '=', False)]}" />
<i class="fa fa-times" aria-hidden="true" style="color: red"
attrs="{'invisible': [('git_http_backend_valid', '=', True)]}" />
</span>
</group>
</form>
</field>
</record>
<act_window id="config_settings_action"
name="Settings"
res_model="galicea_git.config.settings"
parent="base.menu_administration"
view_mode="form" target="inline" />
<menuitem name="Git hosting" id="git_root_menu" sequence="20" />
<menuitem name="Repositories" id="repo_menu" parent="galicea_git.git_root_menu" action="repository_action" sequence="1" />
<menuitem name="Settings" id="settings_menu" parent="galicea_git.git_root_menu" action="config_settings_action" sequence="99"
groups="galicea_git.group_admin" />
</data>
</odoo>

43
galicea_git/views/views.xml

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="repository_view_form" model="ir.ui.view">
<field name="model">galicea_git.repository</field>
<field name="arch" type="xml">
<form>
<group>
<field name="state" invisible="1" />
<field name="name" />
<field name="system_name" groups="galicea_git.group_admin" />
<field name="collaborator_ids" widget="many2many_tags" options="{'no_create': True}" />
</group>
<group class="oe_read_only">
<label for="url" />
<span style="font-family: monospace">git clone <field name="url" nolabel="True" /></span>
<field name="local_directory" style="font-family: monospace" />
</group>
</form>
</field>
</record>
<record id="repository_view_tree" model="ir.ui.view">
<field name="model">galicea_git.repository</field>
<field name="arch" type="xml">
<tree>
<field name="state" invisible="1" />
<field name="name" />
<field name="system_name" groups="galicea_git.group_admin" />
</tree>
</field>
</record>
<act_window id="repository_action"
name="Git repositories"
res_model="galicea_git.repository" />
<menuitem name="Repositories" id="repo_menu" parent="galicea_base.galicea_admin_menu"
action="repository_action" sequence="1" />
</data>
</odoo>

1
galicea_git_oauth/README.md

@ -1 +0,0 @@
[See add-on page on odoo.com](https://apps.odoo.com/apps/modules/12.0/galicea_git_oauth/)

4
galicea_git_oauth/__init__.py

@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
from . import models
from . import controllers

21
galicea_git_oauth/__manifest__.py

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': "Galicea Git OAuth",
'summary': """
Enables Git auth via OAuth token""",
'author': "Maciej Wawro",
'maintainer': "Galicea",
'website': "http://galicea.pl",
'category': 'Technical Settings',
'version': '12.0.1.0',
'depends': ['galicea_git', 'galicea_openid_connect'],
'data': [
],
'installable': True
}

1
galicea_git_oauth/controllers/__init__.py

@ -1 +0,0 @@
from . import ext_git_main

14
galicea_git_oauth/controllers/ext_git_main.py

@ -1,14 +0,0 @@
from odoo.addons.galicea_git.controllers.main import Main
class ExtMain(Main):
def authorize(self, req):
auth = req.httprequest.authorization
if auth and auth.password == 'bearer':
access_token = req.httprequest.authorization.username
token = req.env['galicea_openid_connect.access_token'].sudo().search(
[('token', '=', access_token)]
)
if token:
req.uid = token.user_id.id
return
super(ExtMain, self).authorize(req)

1
galicea_git_oauth/models/__init__.py

@ -1 +0,0 @@
from . import ext_repository

24
galicea_git_oauth/models/ext_repository.py

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
from urllib.parse import urlparse
from odoo import models
class Repository(models.Model):
_inherit = 'galicea_git.repository'
def authenticated_url(self, client):
"""
@param application galicea_openid.application"""
token = self.env['galicea_openid_connect.access_token'].sudo().retrieve_or_create(
self.env.user.id,
client.id
)
unauthenticated_url = self.url
url_parts = urlparse(unauthenticated_url)
return '{}://{}:bearer@{}{}'.format(
url_parts.scheme,
token.token,
url_parts.netloc,
url_parts.path,
)

5
galicea_openapi/__init__.py

@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
#from . import models
from . import controllers

33
galicea_openapi/__manifest__.py

@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': "openapi",
'summary': """
Odoo Opnapi
UWAGA! Obecnie dekorator apiroute ma ograniczoną funkcjonalność.
M.in. tylko jeden URL
controllers/api.py zawiera przykład wykorzystania -
pod adresem /oapi/api zwraca dokumentację w JSON
""",
'description': """
""",
'author': 'Jerzy Wawro',
'maintainer': "Galicea",
'website': "http://www.galicea.pl",
'category': 'Tools',
'version': '12.0.0.1',
'depends': [
],
'external_dependencies': {
'python': [ 'fastapi', 'pydantic', 'starlette' ]
},
'data': [
],
'application': True,
'installable': True,
}

4
galicea_openapi/controllers/__init__.py

@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
from . import api

56
galicea_openapi/controllers/api.py

@ -1,56 +0,0 @@
# -*- coding: utf-8 -*-
import json
from fastapi.openapi.docs import get_swagger_ui_html
from odoo import http, _
from ..openapi import apiroute
from ..openapi import oapi
class OpenApiTest(http.Controller):
@http.route(['/oapi/tst1',], type='http', auth="user", website=True)
def tst1(self, **kw):
return "tst1"
@oapi.get('/oapi/tst2')
@http.route(['/oapi/tst2',], type='http', auth="user", website=True)
def tst2(self):
return 'ok test2'
@oapi.api_route('/oapi/tst3')
@http.route(['/oapi/tst3',], type='http', auth="user", website=True)
def tst3(self, par1="abc"):
return par1
@oapi.api_route('/oapi/tst4')
@http.route(['/oapi/tst4', ], type='http', auth="user", website=True)
def tst4(self,par1="444"):
return par1
@apiroute('/oapi/tst5')
def tst5(self, par1="555"):
return par1
@http.route(['/oapi/api',], type='http', auth="user", website=True)
def api(self, **kw):
return json.dumps(oapi.openapi())
# wynik możesz skopiować do https://editor.swagger.io/
@http.route(['/oapi/docs',], type='http', auth="user", website=True)
def api_UI(self, **kw):
response = get_swagger_ui_html(openapi_url = '/oapi/api', title = 'tytuł')
return response.body

22
galicea_openapi/doc/helloworld.py

@ -1,22 +0,0 @@
# pip install fastapi
# pip install email-validator
# pip install pydantic
# pip install starlette
# pip install uvicorn
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
def run_server():
uvicorn.run(app)
if __name__ == '__main__':
uvicorn.run(app, #'server:app',
host='127.0.0.1', port=8000, reload=True)

41
galicea_openapi/doc/test1.py

@ -1,41 +0,0 @@
# pip install fastapi
# pip install email-validator
# pip install pydantic
# pip install starlette
# pip install uvicorn
import uvicorn
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
app = FastAPI()
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Custom title",
version="2.5.0",
description="This is a very custom OpenAPI schema",
routes=app.routes,
)
openapi_schema["info"]["x-logo"] = {
"url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png"
}
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
if __name__ == '__main__':
print("see http://127.0.0.1:8000/docs")
uvicorn.run(app, #'server:app',
host='127.0.0.1', port=8000, reload=True)

3
galicea_openapi/models/__init__.py

@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-

52
galicea_openapi/openapi.py

@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
import functools
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
def custom_openapi():
if oapi.openapi_schema:
return oapi.openapi_schema
openapi_schema = get_openapi(
title="Custom title",
version="2.5.0",
description="This is a very custom OpenAPI schema",
routes=oapi.routes,
)
openapi_schema["info"]["x-logo"] = {
"url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png"
}
oapi.openapi_schema = openapi_schema
return oapi.openapi_schema
oapi = FastAPI()
oapi.openapi = custom_openapi
def apiroute(route=None, **kw):
routing = kw.copy()
def apidecorator(f):
if route:
if isinstance(route, list):
routes = route
else:
routes = [route]
routing['routes'] = routes
@functools.wraps(f)
def response_wrap(*args, **kw):
response = f(*args, **kw)
return response
oapi.add_api_route(routes[0], f, include_in_schema=True)
response_wrap.routing = routing
response_wrap.original_func = f
return response_wrap
return apidecorator
#

2
galicea_openapi/security/ir.model.access.csv

@ -1,2 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_galicea_openapi,galicea_openapi,model_galicea_openapi_openapi_script,galicea_openapi.script,1,1,1,0

BIN
galicea_openapi/static/description/icon.png

Before

Width: 128  |  Height: 128  |  Size: 10 KiB

1
galicea_openid_connect/__init__.py

@ -2,6 +2,5 @@
from . import controllers
from . import models
from . import system_checks
from . import api

71
galicea_openid_connect/__manifest__.py

@ -1,51 +1,38 @@
# -*- coding: utf-8 -*-
{
'name': "Galicea OpenID Connect Provider",
'summary': """OpenID Connect Provider and OAuth2 resource server""",
'author': "Maciej Wawro",
'maintainer': "Galicea",
'website': "http://galicea.pl",
'category': 'Technical Settings',
'version': '12.0.0.0',
'depends': ['web', 'galicea_environment_checkup', 'galicea_base' ],
'external_dependencies': {
'python': ['jwcrypto', 'cryptography']
},
'data': [
'security/security.xml',
'security/ir.model.access.csv',
# 'security/init.yml',
'security/init.xml',
'views/views.xml',
'views/templates.xml'
"name": "Galicea OpenID Connect Provider",
"summary": """OpenID Connect Provider and OAuth2 resource server""",
"author": "Maciej Wawro, Nicolas JEUDY",
"maintainer": "nj.0k.io, Galicea",
"website": "https://nj.0k.io",
"category": "Technical Settings",
"version": "16.0.0.0",
"depends": ["web"],
"external_dependencies": {"python": ["jwcrypto", "cryptography"]},
"data": [
"security/security.xml",
"security/ir.model.access.csv",
# 'security/init.yml',
"security/init.xml",
"views/views.xml",
"views/templates.xml",
],
'environment_checkup': {
'dependencies': {
'python': [
"environment_checkup": {
"dependencies": {
"python": [
{"name": "jwcrypto", "install": "pip install 'jwcrypto==0.5.0'"},
{
'name': 'jwcrypto',
'install': "pip install 'jwcrypto==0.5.0'"
"name": "cryptography",
"version": ">=2.3",
"install": "pip install 'cryptography>=2.3'",
},
{
'name': 'cryptography',
'version': '>=2.3',
'install': "pip install 'cryptography>=2.3'"
}
]
}
},
'images': [
'static/description/images/master_screenshot.png',
'static/description/images/client_screenshot.png',
'static/description/images/login_screenshot.png',
'static/description/images/error_screenshot.png'
]
"images": [
"static/description/images/master_screenshot.png",
"static/description/images/client_screenshot.png",
"static/description/images/login_screenshot.png",
"static/description/images/error_screenshot.png",
],
}

23
galicea_openid_connect/api.py

@ -5,6 +5,7 @@ import logging
from functools import wraps
from odoo import http
from odoo.http import request
import werkzeug
_logger = logging.getLogger(__name__)
@ -22,13 +23,13 @@ def resource(path, method, auth='user'):
def endpoint_decorator(func):
@http.route(path, auth='public', type='http', methods=[method, 'OPTIONS'], csrf=False)
@wraps(func)
def func_wrapper(self, req, **query):
def func_wrapper(self, **query):
cors_headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, X-Debug-Mode, Authorization',
'Access-Control-Max-Age': 60 * 60 * 24,
}
if req.httprequest.method == 'OPTIONS':
if request.httprequest.method == 'OPTIONS':
return http.Response(
status=200,
headers=cors_headers
@ -36,8 +37,8 @@ def resource(path, method, auth='user'):
try:
access_token = None
if 'Authorization' in req.httprequest.headers:
authorization_header = req.httprequest.headers['Authorization']
if 'Authorization' in request.httprequest.headers:
authorization_header = request.httprequest.headers['Authorization']
if authorization_header[:7] == 'Bearer ':
access_token = authorization_header.split(' ', 1)[1]
if access_token is None:
@ -48,7 +49,7 @@ def resource(path, method, auth='user'):
'invalid_request',
)
if auth == 'user':
token = req.env['galicea_openid_connect.access_token'].sudo().search(
token = request.env['galicea_openid_connect.access_token'].sudo().search(
[('token', '=', access_token)]
)
if not token:
@ -56,9 +57,9 @@ def resource(path, method, auth='user'):
'access_token is invalid',
'invalid_request',
)
req.uid = token.user_id.id
request.update_env(user=token.user_id.id)
elif auth == 'client':
token = req.env['galicea_openid_connect.client_access_token'].sudo().search(
token = request.env['galicea_openid_connect.client_access_token'].sudo().search(
[('token', '=', access_token)]
)
if not token:
@ -66,13 +67,11 @@ def resource(path, method, auth='user'):
'access_token is invalid',
'invalid_request',
)
req.uid = token.client_id.system_user_id.id
request.update_env(user=token.client_id.system_user_id.id)
ctx = req.context.copy()
ctx.update({'client_id': token.client_id.id})
req.context = ctx
request.update_env(context={'client_id': token.client_id.id})
response = func(self, req, **query)
response = func(self, **query)
return werkzeug.Response(
response=json.dumps(response),
headers=cors_headers,

7
galicea_openid_connect/controllers/ext_web_login.py

@ -3,15 +3,16 @@
import time
from odoo import http
from odoo.http import request
from odoo.addons import web
class Home(web.controllers.main.Home):
class Home(web.controllers.home.Home):
@http.route('/web/login', type='http', auth="none")
def web_login(self, redirect=None, **kw):
result = super(Home, self).web_login(redirect, **kw)
if result.is_qweb and 'force_auth_and_redirect' in kw:
result.qcontext['redirect'] = kw['force_auth_and_redirect']
if http.request.params.get('login_success'):
http.request.session['auth_time'] = int(time.time())
if request.params.get('login_success'):
request.session['auth_time'] = int(time.time())
return result

111
galicea_openid_connect/controllers/main.py

@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
import json
import logging
import time
import os
import base64
from odoo import http
from odoo.http import request
import werkzeug
from werkzeug.urls import url_encode
from .. api import resource
@ -17,6 +20,8 @@ try:
except ImportError:
pass
_logger = logging.getLogger(__name__)
def jwk_from_json(json_key):
key = jwk.JWK()
key.import_key(**json.loads(json_key))
@ -53,24 +58,24 @@ class OAuthException(Exception):
self.type = type
class Main(http.Controller):
def __get_authorization_code_jwk(self, req):
return jwk_from_json(req.env['ir.config_parameter'].sudo().get_param(
def __get_authorization_code_jwk(self):
return jwk_from_json(request.env['ir.config_parameter'].sudo().get_param(
'galicea_openid_connect.authorization_code_jwk'
))
def __get_id_token_jwk(self, req):
return jwk_from_json(req.env['ir.config_parameter'].sudo().get_param(
def __get_id_token_jwk(self):
return jwk_from_json(request.env['ir.config_parameter'].sudo().get_param(
'galicea_openid_connect.id_token_jwk'
))
def __validate_client(self, req, **query):
def __validate_client(self, **query):
if 'client_id' not in query:
raise OAuthException(
'client_id param is missing',
OAuthException.INVALID_CLIENT,
)
client_id = query['client_id']
client = req.env['galicea_openid_connect.client'].sudo().search(
client = request.env['galicea_openid_connect.client'].sudo().search(
[('client_id', '=', client_id)]
)
if not client:
@ -80,7 +85,7 @@ class Main(http.Controller):
)
return client
def __validate_redirect_uri(self, client, req, **query):
def __validate_redirect_uri(self, client, **query):
if 'redirect_uri' not in query:
raise OAuthException(
'redirect_uri param is missing',
@ -96,7 +101,7 @@ class Main(http.Controller):
return redirect_uri
def __validate_client_secret(self, client, req, **query):
def __validate_client_secret(self, client, **query):
if 'client_secret' not in query or query['client_secret'] != client.secret:
raise OAuthException(
'client_secret param is not valid',
@ -104,7 +109,7 @@ class Main(http.Controller):
)
@http.route('/.well-known/openid-configuration', auth='public', type='http')
def metadata(self, req, **query):
def metadata(self, **query):
base_url = http.request.httprequest.host_url
data = {
'issuer': base_url,
@ -122,14 +127,14 @@ class Main(http.Controller):
return json.dumps(data)
@http.route('/oauth/jwks', auth='public', type='http')
def jwks(self, req, **query):
def jwks(self, **query):
keyset = jwk.JWKSet()
keyset.add(self.__get_id_token_jwk(req))
keyset.add(self.__get_id_token_jwk())
return keyset.export(private_keys=False)
@resource('/oauth/userinfo', method='GET')
def userinfo(self, req, **query):
user = req.env.user
def userinfo(self, **query):
user = request.env.user
values = {
'sub': str(user.id),
# Needed in case the client is another Odoo instance
@ -141,22 +146,22 @@ class Main(http.Controller):
return values
@resource('/oauth/clientinfo', method='GET', auth='client')
def clientinfo(self, req, **query):
client = req.env['galicea_openid_connect.client'].browse(req.context['client_id'])
def clientinfo(self, **query):
client = request.env['galicea_openid_connect.client'].browse(request.context['client_id'])
return {
'name': client.name
}
@http.route('/oauth/authorize', auth='public', type='http', csrf=False)
def authorize(self, req, **query):
def authorize(self, **query):
# First, validate client_id and redirect_uri params.
try:
client = self.__validate_client(req, **query)
redirect_uri = self.__validate_redirect_uri(client, req, **query)
client = self.__validate_client(**query)
redirect_uri = self.__validate_redirect_uri(client, **query)
except OAuthException as e:
# If those are not valid, we must not redirect back to the client
# - instead, we display a message to the user
return req.render('galicea_openid_connect.error', {'exception': e})
return request.render('galicea_openid_connect.error', {'exception': e})
scopes = query['scope'].split(' ') if query.get('scope') else []
is_openid_request = 'openid' in scopes
@ -194,7 +199,7 @@ class Main(http.Controller):
if not response_mode:
response_mode = 'query' if response_type == 'code' else 'fragment'
user = req.env.user
user = request.env.user
# In case user is not logged in, we redirect to the login page and come back
needs_login = user.login == 'public'
# Also if they didn't authenticate recently enough
@ -202,14 +207,14 @@ class Main(http.Controller):
needs_login = True
if needs_login:
params = {
'force_auth_and_redirect': '/oauth/authorize?{}'.format(werkzeug.url_encode(query))
'force_auth_and_redirect': '/oauth/authorize?{}'.format(url_encode(query))
}
return self.__redirect('/web/login', params, 'query')
response_types = response_type.split()
extra_claims = {
'sid': http.request.httprequest.session.sid,
'sid': http.request.session.sid,
}
if 'nonce' in query:
extra_claims['nonce'] = query['nonce']
@ -227,10 +232,10 @@ class Main(http.Controller):
'exp': int(time.time()) + 60
}
payload.update(extra_claims)
key = self.__get_authorization_code_jwk(req)
key = self.__get_authorization_code_jwk()
response_params['code'] = jwt_encode(payload, key)
if 'token' in response_types:
access_token = req.env['galicea_openid_connect.access_token'].sudo().retrieve_or_create(
access_token = request.env['galicea_openid_connect.access_token'].sudo().retrieve_or_create(
user.id,
client.id
).token
@ -243,23 +248,24 @@ class Main(http.Controller):
#extra_claims['at_hash'] = base64.urlsafe_b64encode(at_hash[:16]).strip('=')
extra_claims['at_hash'] = base64.urlsafe_b64encode(at_hash[:16])
if 'id_token' in response_types:
response_params['id_token'] = self.__create_id_token(req, user.id, client, extra_claims)
response_params['id_token'] = self.__create_id_token(user.id, client, extra_claims)
return self.__redirect(redirect_uri, response_params, response_mode)
@http.route('/oauth/token', auth='public', type='http', methods=['POST', 'OPTIONS'], csrf=False)
def token(self, req, **query):
def token(self, **query):
cors_headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, X-Debug-Mode, Authorization',
'Access-Control-Max-Age': 60 * 60 * 24,
}
if req.httprequest.method == 'OPTIONS':
_logger.info("Test1 %s" % request.httprequest.method)
if request.httprequest.method == 'OPTIONS':
return http.Response(
status=200,
headers=cors_headers
)
_logger.info("Test2 %s" % query)
try:
if 'grant_type' not in query:
raise OAuthException(
@ -267,12 +273,15 @@ class Main(http.Controller):
OAuthException.INVALID_REQUEST,
)
if query['grant_type'] == 'authorization_code':
return json.dumps(self.__handle_grant_type_authorization_code(req, **query))
_logger.info("Test3")
return json.dumps(self.__handle_grant_type_authorization_code(**query))
elif query['grant_type'] == 'client_credentials':
return json.dumps(self.__handle_grant_type_client_credentials(req, **query))
_logger.info("Test4")
return json.dumps(self.__handle_grant_type_client_credentials(**query))
elif query['grant_type'] == 'password':
_logger.info("Test5")
return werkzeug.Response(
response=json.dumps(self.__handle_grant_type_password(req, **query)),
response=json.dumps(self.__handle_grant_type_password(**query)),
headers=cors_headers
)
else:
@ -284,10 +293,10 @@ class Main(http.Controller):
body = json.dumps({'error': e.type, 'error_description': e})
return werkzeug.Response(response=body, status=400, headers=cors_headers)
def __handle_grant_type_authorization_code(self, req, **query):
client = self.__validate_client(req, **query)
redirect_uri = self.__validate_redirect_uri(client, req, **query)
self.__validate_client_secret(client, req, **query)
def __handle_grant_type_authorization_code(self, **query):
client = self.__validate_client(**query)
redirect_uri = self.__validate_redirect_uri(client, **query)
self.__validate_client_secret(client, **query)
if 'code' not in query:
raise OAuthException(
@ -295,7 +304,7 @@ class Main(http.Controller):
OAuthException.INVALID_GRANT,
)
try:
payload = jwt_decode(query['code'], self.__get_authorization_code_jwk(req))
payload = jwt_decode(query['code'], self.__get_authorization_code_jwk())
except jwt.JWTExpired:
raise OAuthException(
'Code expired',
@ -318,7 +327,7 @@ class Main(http.Controller):
)
# Retrieve/generate access token. We currently only store one per user/client
token = req.env['galicea_openid_connect.access_token'].sudo().retrieve_or_create(
token = request.env['galicea_openid_connect.access_token'].sudo().retrieve_or_create(
payload['user_id'],
client.id
)
@ -328,11 +337,11 @@ class Main(http.Controller):
}
if 'openid' in payload['scopes']:
extra_claims = { name: payload[name] for name in payload if name in ['sid', 'nonce'] }
response['id_token'] = self.__create_id_token(req, payload['user_id'], client, extra_claims)
response['id_token'] = self.__create_id_token(payload['user_id'], client, extra_claims)
return response
def __handle_grant_type_password(self, req, **query):
client = self.__validate_client(req, **query)
def __handle_grant_type_password(self, **query):
client = self.__validate_client(**query)
if not client.allow_password_grant:
raise OAuthException(
'This client is not allowed to perform password flow',
@ -345,8 +354,8 @@ class Main(http.Controller):
'{} is required'.format(param),
OAuthException.INVALID_REQUEST
)
user_id = req.env['res.users'].authenticate(
req.env.cr.dbname,
user_id = request.env['res.users'].authenticate(
request.env.cr.dbname,
query['username'],
query['password'],
None
@ -359,7 +368,7 @@ class Main(http.Controller):
scopes = query['scope'].split(' ') if query.get('scope') else []
# Retrieve/generate access token. We currently only store one per user/client
token = req.env['galicea_openid_connect.access_token'].sudo().retrieve_or_create(
token = request.env['galicea_openid_connect.access_token'].sudo().retrieve_or_create(
user_id,
client.id
)
@ -368,19 +377,19 @@ class Main(http.Controller):
'token_type': 'bearer'
}
if 'openid' in scopes:
response['id_token'] = self.__create_id_token(req, user_id, client, {})
response['id_token'] = self.__create_id_token(user_id, client, {})
return response
def __handle_grant_type_client_credentials(self, req, **query):
client = self.__validate_client(req, **query)
self.__validate_client_secret(client, req, **query)
token = req.env['galicea_openid_connect.client_access_token'].sudo().retrieve_or_create(client.id)
def __handle_grant_type_client_credentials(self, **query):
client = self.__validate_client(**query)
self.__validate_client_secret(client, **query)
token = request.env['galicea_openid_connect.client_access_token'].sudo().retrieve_or_create(client.id)
return {
'access_token': token.token,
'token_type': 'bearer'
}
def __create_id_token(self, req, user_id, client, extra_claims):
def __create_id_token(self, user_id, client, extra_claims):
claims = {
'iss': http.request.httprequest.host_url,
'sub': str(user_id),
@ -396,14 +405,14 @@ class Main(http.Controller):
if 'at_hash' in extra_claims:
claims['at_hash'] = extra_claims['at_hash']
key = self.__get_id_token_jwk(req)
key = self.__get_id_token_jwk()
return jwt_encode(claims, key)
def __redirect(self, url, params, response_mode):
location = '{}{}{}'.format(
url,
'?' if response_mode == 'query' else '#',
werkzeug.url_encode(params)
url_encode(params)
)
return werkzeug.Response(
headers={'Location': location},

57
galicea_openid_connect/models/client.py

@ -3,65 +3,68 @@
from odoo import models, fields, api
from .. import random_tokens
class Client(models.Model):
_name = 'galicea_openid_connect.client'
_description = 'OpenID Connect client'
_name = "galicea_openid_connect.client"
_description = "OpenID Connect client"
name = fields.Char(required=True)
auth_redirect_uri = fields.Char('Redirect URI for user login')
auth_redirect_uri = fields.Char("Redirect URI for user login")
client_id = fields.Char(
string='Client ID',
string="Client ID",
required=True,
readonly=True,
readonly=False,
index=True,
default=lambda _: random_tokens.lower_case(16),
)
secret = fields.Char(
string='Client secret',
string="Client secret",
required=True,
readonly=True,
readonly=False,
default=lambda _: random_tokens.alpha_numeric(32),
groups='galicea_openid_connect.group_admin'
groups="galicea_openid_connect.group_admin",
)
system_user_id = fields.Many2one(
'res.users',
'Artificial user representing the client in client credentials requests',
"res.users",
"Artificial user representing the client in client credentials requests",
readonly=True,
required=True,
ondelete='restrict'
ondelete="restrict",
)
allow_password_grant = fields.Boolean(
string='Allow OAuth2 password grant',
string="Allow OAuth2 password grant",
default=False,
)
@api.model
def __system_user_name(self, client_name):
return '{} - API system user'.format(client_name)
return "{} - API system user".format(client_name)
@api.model
def create(self, values):
if 'name' in values:
system_user = self.env['res.users'].create({
'name': self.__system_user_name(values['name']),
'login': random_tokens.lower_case(8),
'groups_id': [(4, self.env.ref('galicea_openid_connect.group_system_user').id)]
})
if "name" in values:
system_user = self.env["res.users"].create(
{
"name": self.__system_user_name(values["name"]),
"login": random_tokens.lower_case(8),
"groups_id": [
(4, self.env.ref("galicea_openid_connect.group_system_user").id)
],
}
)
# Do not include in the "Pending invitations" list
system_user.sudo(system_user.id)._update_last_login()
values['system_user_id'] = system_user.id
system_user._update_last_login()
values["system_user_id"] = system_user.id
return super(Client, self).create(values)
@api.multi
def write(selfs, values):
super(Client, selfs).write(values)
if 'name' in values:
selfs.mapped(lambda client: client.system_user_id).write({
'name': selfs.__system_user_name(values['name'])
})
if "name" in values:
selfs.mapped(lambda client: client.system_user_id).write(
{"name": selfs.__system_user_name(values["name"])}
)
return True
@api.multi
def unlink(selfs):
users_to_unlink = selfs.mapped(lambda client: client.system_user_id)
ret = super(Client, selfs).unlink()

45
galicea_openid_connect/models/config_parameter.py

@ -2,27 +2,40 @@
from odoo import models, fields, api
from .. import random_tokens
try:
from jwcrypto import jwk
except ImportError:
pass
class ConfigParameter(models.Model):
_inherit = 'ir.config_parameter'
_inherit = "ir.config_parameter"
@api.model
def openid_init_keys(self):
keys = {
'galicea_openid_connect.authorization_code_jwk': lambda: \
jwk.JWK.generate(kty='oct', size=256, kid=random_tokens.alpha_numeric(16), use='sig', alg='HS256').export(),
'galicea_openid_connect.id_token_jwk': lambda: \
jwk.JWK.generate(kty='RSA', size=2054, kid=random_tokens.alpha_numeric(16), use='sig', alg='RS256').export()
}
@api.model
def openid_init_keys(self):
keys = {
"galicea_openid_connect.authorization_code_jwk": lambda: jwk.JWK.generate(
kty="oct",
size=256,
kid=random_tokens.alpha_numeric(16),
use="sig",
alg="HS256",
).export(),
"galicea_openid_connect.id_token_jwk": lambda: jwk.JWK.generate(
kty="RSA",
size=2054,
kid=random_tokens.alpha_numeric(16),
use="sig",
alg="RS256",
).export(),
}
for key, gen in iter(keys.items()):
if not self.search([('key', '=', key)]):
self.create({
'key': key,
'value': gen(),
'group_ids': [(4, self.env.ref('base.group_erp_manager').id)]
})
for key, gen in iter(keys.items()):
if not self.search([("key", "=", key)]):
self.create(
{
"key": key,
"value": gen(),
}
)

33
galicea_openid_connect/security/__init__.py

@ -1,23 +1,36 @@
# -*- coding: utf-8 -*-
from .. import random_tokens
try:
from jwcrypto import jwk
except ImportError:
pass
def init_keys(IrConfigParameter):
keys = {
'galicea_openid_connect.authorization_code_jwk': lambda: \
jwk.JWK.generate(kty='oct', size=256, kid=random_tokens.alpha_numeric(16), use='sig', alg='HS256').export(),
'galicea_openid_connect.id_token_jwk': lambda: \
jwk.JWK.generate(kty='RSA', size=2054, kid=random_tokens.alpha_numeric(16), use='sig', alg='RS256').export()
"galicea_openid_connect.authorization_code_jwk": lambda: jwk.JWK.generate(
kty="oct",
size=256,
kid=random_tokens.alpha_numeric(16),
use="sig",
alg="HS256",
).export(),
"galicea_openid_connect.id_token_jwk": lambda: jwk.JWK.generate(
kty="RSA",
size=2054,
kid=random_tokens.alpha_numeric(16),
use="sig",
alg="RS256",
).export(),
}
for key, gen in keys.iteritems():
if not IrConfigParameter.search([('key', '=', key)]):
IrConfigParameter.create({
'key': key,
'value': gen(),
'group_ids': [(4, IrConfigParameter.env.ref('base.group_erp_manager').id)]
})
if not IrConfigParameter.search([("key", "=", key)]):
IrConfigParameter.create(
{
"key": key,
"value": gen(),
}
)

24
galicea_openid_connect/system_checks.py

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
from odoo.addons.galicea_environment_checkup import \
custom_check, CheckWarning, CheckSuccess, CheckFail
from odoo import http
@custom_check
def check_single_db(env):
if not http.request:
raise CheckWarning('Could not detect DB settings.')
dbs = http.db_list(True, http.request.httprequest)
if len(dbs) == 1:
return CheckSuccess('Odoo runs in a single-DB mode.')
details = (
'<p>Odoo runs in a multi-DB mode, which will cause API request routing to fail.</p>'
'<p>Run Odoo with <tt>--dbfilter</tt> or <tt>--database</tt> flag.</p>'
)
return CheckFail(
'Odoo runs in a multi-DB mode.',
details=details
)

22
galicea_openid_connect/views/views.xml

@ -21,11 +21,11 @@
<form>
<group>
<field name="name" />
<field name="create_date" invisible="1" />
<field name="client_id"
<field name="create_date" invisible="1"/>
<field name="client_id" readonly="1"
attrs="{'invisible':[('create_date', '==', False)]}" />
<label for="secret" class="oe_read_only" string="Client Secret" />
<button class="oe_read_only" string="Show" type="action" name="%(client_action_secret)d" />
<label for="secret" string="Client Secret" />
<field name="secret" readonly="1" nolabel="1" />
<field name="auth_redirect_uri" />
<field name="allow_password_grant" />
</group>
@ -33,18 +33,6 @@
</field>
</record>
<record id="client_view_form_secret" model="ir.ui.view">
<field name="inherit_id" ref="galicea_openid_connect.client_view_form" />
<field name="priority">99</field>
<field name="model">galicea_openid_connect.client</field>
<field name="mode">primary</field>
<field name="arch" type="xml">
<button name="%(client_action_secret)d" position="replace">
<field class="oe_read_only" name="secret" nolabel="1" />
</button>
</field>
</record>
<record id="client_view_tree" model="ir.ui.view">
<field name="model">galicea_openid_connect.client</field>
<field name="arch" type="xml">
@ -61,6 +49,6 @@
res_model="galicea_openid_connect.client" />
<menuitem name="OpenID Connect Provider" id="client_menu"
parent="galicea_base.galicea_admin_menu" action="client_action" />
parent="base.menu_users" action="client_action" />
</data>
</odoo>

30
galicea_toolset/README.md

@ -1,30 +0,0 @@
Widgets
=======
<field name="url_field" widget="iframe" style="width: 100%" iframe_style="width: 100%" />
Creates an iframe with ``url_field`` value as a source.
<field name="article_ids" widget="one2many_flexible" click_target="current" />
Allows changing the target for the item click action.
Functions
=========
odoo.addons.galicea_toolset.utils.get_base_url(env)
Client actions
==============
@api.multi
def button_action(self):
return {
'type': 'ir.actions.client',
'tag': 'galicea_toolset.open_edit_dialog',
'params': { 'res_id': <id>, 'res_model': <model>, 'title': <title>}
};

1
galicea_toolset/__init__.py

@ -1 +0,0 @@
# -*- coding: utf-8 -*-

20
galicea_toolset/__manifest__.py

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': "galicea toolset",
'summary': """
A couple of small convenience widgets and functions""",
'author': "Maciej Wawro",
'maintainer': "Galicea",
'website': "http://www.galicea.pl",
'category': 'Technical Settings',
'version': '12.0.0.1',
'depends': ['base'],
'data': [
'views/data.xml'
],
}

1
galicea_toolset/static/src/js/.#one2many_flexible_widget.js

@ -1 +0,0 @@
jurek@jurek.15022

38
galicea_toolset/static/src/js/client_actions.js

@ -1,38 +0,0 @@
odoo.define('galicea_toolset.client_actions', function(require) {
var Widget = require('web.Widget');
var core = require('web.core');
var common = require('web.form_common');
var ActionManager = require('web.ActionManager');
var OpenEditDialogAction = Widget.extend({
init: function(parent, context) {
this._super.apply(this, arguments);
this.context = context;
if (parent instanceof ActionManager) {
this.am = parent;
}
},
start: function () {
var params = this.context.params;
var popup = new common.FormViewDialog(self, {
title: params.title,
res_model: params.res_model,
res_id: params.res_id,
}).open();
popup.on('closed', this, function() {
this.am && this.am.history_back();
});
},
});
core.action_registry.add(
'galicea_toolset.open_edit_dialog',
OpenEditDialogAction
);
return {
open_edit_dialog_action: OpenEditDialogAction,
};
});

49
galicea_toolset/static/src/js/iframe_widget.js

@ -1,49 +0,0 @@
odoo.define('pwste_epub.iframe_widget', function(require) {
var AbstractField = require('web.AbstractField');
var fieldRegistry = require('web.field_registry');
var core = require('web.core');
var Widget= require('web.Widget');
var widgetRegistry = require('web.widget_registry');
var FieldManagerMixin = require('web.FieldManagerMixin');
var IFrameWidget = AbstractField.extend({
init: function () {
this._super.apply(this, arguments);
// this.set("value", "");
},
_renderReadonly: function() {
window.widget=this;
this.$el.html(
$('<iframe>', {
src: this.value || 'about:blank',
style: this.attrs.iframe_style
})
);
if (this.attrs.new_window_label && this.value) {
this.$el.prepend(
$('<a>', {
href: this.value,
target: '_blank',
style: 'float:right; margin-bottom: 10px',
'class': 'btn btn-primary',
}).html('<i class="fa fa-external-link-square" aria-hidden="true"></i> Otwórz w nowym oknie')
)
}
},
});
fieldRegistry.add(
'iframe', IFrameWidget
);
return {
IFrameWidget: IFrameWidget,
};
});

68
galicea_toolset/static/src/js/one2many_flexible_widget.js

@ -1,68 +0,0 @@
odoo.define('galicea_toolset.one2many_flexible_widget', function(require) {
var core = require('web.core');
/*
var view_dialogs = require('web.view_dialogs'),
relational_fields = require('web.relational_fields'),
rpc = require('web.rpc'),
field_registry = require('web.field_registry');*/
var form_relational = require('web.form_relational');
/* var X2ManyList = form_relational.X2ManyList;
var ListView = require('web.ListView');
var FieldOne2Many = field_registry.get('one2many');
var FieldOne2Many = relational_fields.FieldOne2Many;
var FormController = require('web.FormController');
*/
/*
ListView.include({
do_activate_record: function (index, id, dataset, view) {
var action = this.ViewManager.action;
if (!action || !action.context || !action.context.open_formview)
return this._super(index, id, dataset, view);
do_action(this, id, action.context);
}
});
var One2ManyListView = core.one2many_view_registry.get('list');
*/
var One2ManyFlexibleListView = form_relational.One2ManyListView.extend({
do_activate_record: function(index, id) {
var self = this;
if (!this.x2m.get("effective_readonly")) {
this._super.apply(this, arguments);
return;
}
this.do_action({
'type': 'ir.actions.act_window',
'views': [[false, 'form']],
'res_model': self.x2m.field.relation,
'res_id': id,
'target': self.x2m.node.attrs.click_target || 'current',
});
}
});
var FieldOne2Many = core.form_widget_registry.get('one2many');
var FieldOne2ManyFlexible = FieldOne2Many.extend({
init: function() {
this._super.apply(this, arguments);
this.x2many_views = {
kanban: core.view_registry.get('one2many_kanban'),
list: One2ManyFlexibleListView,
};
},
});
core.form_widget_registry.add('one2many_flexible', FieldOne2ManyFlexible);
return {
FieldOne2ManyFlexible: FieldOne2ManyFlexible,
};
});

16
galicea_toolset/utils.py

@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import http
def get_base_url(env):
"""
Better host name detection
@param env odoo.api.Environment"""
if http.request:
# Preferuj nazwę hosta, która została użyta do tego zapytania
return http.request.httprequest.host_url
else:
# Jeśli nie jesteśmy wewnątrz zapytania HTTP, zwróć domenę ostatnio użytą
# przez admina do zalogowania
return env['ir.config_parameter'].get_param('web.base.url') + '/'

11
galicea_toolset/views/data.xml

@ -1,11 +0,0 @@
<odoo>
<data>
<template id="assets_backend" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script src="/galicea_toolset/static/src/js/iframe_widget.js" type="text/javascript" />
<script src="/galicea_toolset/static/src/js/one2many_flexible_widget.js" type="text/javascript" />
<script src="/galicea_toolset/static/src/js/client_actions.js" type="text/javascript" />
</xpath>
</template>
</data>
</odoo>
Loading…
Cancel
Save