Initial commit: Security packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:51 +02:00
commit bb469e4763
1399 changed files with 278378 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,116 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Google Calendar</h2>
<h3 class="oe_slogan">Get your meetings, your leaves... Get your calendar anywhere and never forget an event.</h3>
<div class="oe_span12">
<img src="the_calendar.png" class="oe_picture oe_screenshot">
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row">
<h2 class="oe_slogan">Keep an eye on your events</h2>
<div class="oe_span6">
<p class='oe_mt32'>
See easily the purpose of the meeting, the start time and also the attendee(s)... All that without click on anything...
</p>
</div>
<div class="oe_span6">
<img class="oe_picture oe_screenshot" src="an_event.png">
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row">
<h2 class="oe_slogan">Create so easily an event</h2>
<div class="oe_span6">
<img class="oe_picture oe_screenshot" src="create_quick.png">
</div>
<div class="oe_span6">
<p class='oe_mt32'>
In just one click you can create an event...<br/>
You can drag and drop your event if you want moved it to another timing.<br/>
You can shrink or extend the event if you need to change the start's hours or the duration of your meeting.
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row">
<h2 class="oe_slogan">Create recurrent event</h2>
<div class="oe_span6">
<p class='oe_mt32'>
You can also create recurrent events with only one event.<br/>
You need to create an event each monday of the week ? With only one it's possible, you could specify the recurrence and if one of this event is moved, or deleted, it's not a problem, you can untie your event from the others recurrences.
</p>
</div>
<div class="oe_span6">
<img class="oe_picture oe_screenshot" src="recurrent.png">
</div>
</div>
</section>
<section class="oe_container ">
<div class="oe_row">
<h2 class="oe_slogan">See all events you wants </h2>
<div class="oe_span6">
<img class="oe_picture oe_screenshot" src="coworker.png">
</div>
<div class="oe_span6">
<p class='oe_mt32'>
See in your calendar, the event from others peoples where your are attendee, but also their events by simply adding your favorites coworkers.<br/>
Every coworker will have their own color in your calendar, and every attendee will have their avatar in the event...<br/>
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row">
<h2 class="oe_slogan">Get an email</h2>
<div class="oe_span6">
<p class='oe_mt32'>
You will receive an email at creation of an event where you are attendee, but also when this event is updated for some fields as date start, ...
</p>
</div>
<div class="oe_span6">
<img class="oe_picture oe_screenshot" src="email.png">
</div>
</div>
</section>
<section class="oe_container ">
<div class="oe_row">
<h2 class="oe_slogan">Be notified </h2>
<div class="oe_span6">
<img class="oe_picture oe_screenshot" src="notification.png">
</div>
<div class="oe_span6">
<p class='oe_mt32'>
You can ask to have a alarm of type 'notification' in your Odoo.<br/>
You will have a notification in you Odoo which ever the page you are.
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row">
<h2 class="oe_slogan">Google Calendar</h2>
<div class="oe_span6">
<p class='oe_mt32'>
With the plugin Google_calendar, you can synchronize your Odoo calendar with Google Calendar.
</p>
</div>
<div class="oe_span6">
<img class="oe_picture oe_screenshot" src="calendar_in_action.png">
</div>
</div>
</section>

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,25 @@
.o_calendar_sidebar {
.bg-primary {
background-color: $o-enterprise-primary-color;
}
.o_google_sync_button {
padding: 0;
cursor: pointer;
font-size: 0.9em;
}
.o_google_sync_button_configured {
color: white;
height: 2em;
&:hover {
#google_check {
display: none;
}
}
&:not(:hover) {
#google_stop {
display: none;
}
}
}
}

View file

@ -0,0 +1,10 @@
/** @odoo-module **/
import { AttendeeCalendarCommonPopover } from "@calendar/views/attendee_calendar/common/attendee_calendar_common_popover";
import { patch } from "@web/core/utils/patch";
patch(AttendeeCalendarCommonPopover.prototype, "google_calendar_google_calendar_common_popover", {
get isEventArchivable() {
return this._super() || (this.isCurrentUserOrganizer && this.props.record.rawRecord.google_id);
},
});

View file

@ -0,0 +1,62 @@
/** @odoo-module **/
import { AttendeeCalendarController } from "@calendar/views/attendee_calendar/attendee_calendar_controller";
import { patch } from "@web/core/utils/patch";
import { useService } from "@web/core/utils/hooks";
import { ConfirmationDialog, AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
patch(AttendeeCalendarController.prototype, "google_calendar_google_calendar_controller", {
setup() {
this._super(...arguments);
this.dialog = useService("dialog");
this.notification = useService("notification");
},
async onGoogleSyncCalendar() {
await this.orm.call(
"res.users",
"restart_google_synchronization",
[[this.user.userId]],
);
const syncResult = await this.model.syncGoogleCalendar();
if (syncResult.status === "need_auth") {
window.location.assign(syncResult.url);
} else if (syncResult.status === "need_config_from_admin") {
if (this.isSystemUser) {
this.dialog.add(ConfirmationDialog, {
title: this.env._t("Configuration"),
body: this.env._t("The Google Synchronization needs to be configured before you can use it, do you want to do it now?"),
confirm: this.actionService.doAction.bind(this.actionService, syncResult.action),
});
} else {
this.dialog.add(AlertDialog, {
title: this.env._t("Configuration"),
body: this.env._t("An administrator needs to configure Google Synchronization before you can use it!"),
});
}
} else if (syncResult.status === "need_refresh") {
await this.model.load();
}
},
async onStopGoogleSynchronization() {
this.dialog.add(ConfirmationDialog, {
body: this.env._t("You are about to stop the synchronization of your calendar with Google. Are you sure you want to continue?"),
confirm: async () => {
await this.orm.call(
"res.users",
"stop_google_synchronization",
[[this.user.userId]],
);
this.notification.add(
this.env._t("The synchronization with Google calendar was successfully stopped."),
{
title: this.env._t("Success"),
type: "success",
},
);
await this.model.load();
},
});
}
});

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="google_calendar.GoogleCalendarController" t-inherit="calendar.AttendeeCalendarController" t-inherit-mode="extension" owl="1">
<xpath expr="//div[@id='calendar_sync_wrapper']" position="attributes">
<attribute name="t-if">true</attribute>
</xpath>
<xpath expr="//div[@id='google_calendar_sync']" position="replace">
<div id="google_calendar_sync" class="o_calendar_sync">
<button t-if="!model.googleIsSync" type="button" id="google_sync_pending" class="o_google_sync_button o_google_sync_pending btn btn-secondary btn" t-on-click="onGoogleSyncCalendar">
<b><i class='fa fa-refresh'/> Google</b>
</button>
<!-- class change on hover -->
<button t-else="" type="button" id="google_sync_configured" class="me-1 o_google_sync_button o_google_sync_button_configured btn text-bg-primary" t-on-click="onStopGoogleSynchronization"
t-on-mouseenter="(ev) => {ev.target.classList.remove('text-bg-primary');ev.target.classList.add('text-bg-danger');}"
t-on-mouseleave="(ev) => {ev.target.classList.remove('text-bg-danger');ev.target.classList.add('text-bg-primary');}">
<b>
<i id="google_check" class='fa fa-check'/>
<i id="google_stop" class='fa fa-times'/>
<span class="mx-1">Google</span>
</b>
</button>
</div>
</xpath>
</t>
</templates>

View file

@ -0,0 +1,64 @@
/** @odoo-module **/
import { AttendeeCalendarModel } from "@calendar/views/attendee_calendar/attendee_calendar_model";
import { patch } from "@web/core/utils/patch";
patch(AttendeeCalendarModel, "google_calendar_google_calendar_model", {
services: [...AttendeeCalendarModel.services, "rpc"],
});
patch(AttendeeCalendarModel.prototype, "google_calendar_google_calendar_model_functions", {
setup(params, { rpc }) {
this._super(...arguments);
this.rpc = rpc;
this.isAlive = params.isAlive;
this.googleIsSync = true;
this.googlePendingSync = false;
},
/**
* @override
*/
async updateData() {
const _super = this._super.bind(this);
if (this.googlePendingSync) {
return _super(...arguments);
}
try {
await Promise.race([
new Promise(resolve => setTimeout(resolve, 1000)),
this.syncGoogleCalendar(true)
]);
} catch (error) {
if (error.event) {
error.event.preventDefault();
}
console.error("Could not synchronize Google events now.", error);
this.googlePendingSync = false;
}
if (this.isAlive()) {
return _super(...arguments);
}
},
async syncGoogleCalendar(silent = false) {
this.googlePendingSync = true;
const result = await this.rpc(
"/google_calendar/sync_data",
{
model: this.resModel,
fromurl: window.location.href
},
{
silent,
},
);
if (["need_config_from_admin", "need_auth", "sync_stopped"].includes(result.status)) {
this.googleIsSync = false;
} else if (result.status === "no_new_event_from_google" || result.status === "need_refresh") {
this.googleIsSync = true;
}
this.googlePendingSync = false;
return result;
},
});

View file

@ -0,0 +1,18 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
patch(MockServer.prototype, "google_calendar_mock_server", {
/**
* Simulate the creation of a custom appointment type
* by receiving a list of slots.
* @override
*/
async _performRPC(route, args) {
if (route === '/google_calendar/sync_data') {
return Promise.resolve({status: 'no_new_event_from_google'});
}
return this._super(...arguments);
},
});

View file

@ -0,0 +1,216 @@
/** @odoo-module **/
import { click, getFixture, patchDate, makeDeferred, nextTick} from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import { registry } from "@web/core/registry";
import { userService } from "@web/core/user_service";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
const serviceRegistry = registry.category("services");
let target;
let serverData;
const uid = -1;
QUnit.module('Google Calendar', {
beforeEach: function () {
patchDate(2016, 11, 12, 8, 0, 0);
serverData = {
models: {
'calendar.event': {
fields: {
id: {string: "ID", type: "integer"},
user_id: {string: "user", type: "many2one", relation: 'user'},
partner_id: {string: "user", type: "many2one", relation: 'partner', related: 'user_id.partner_id'},
name: {string: "name", type: "char"},
start_date: {string: "start date", type: "date"},
stop_date: {string: "stop date", type: "date"},
start: {string: "start datetime", type: "datetime"},
stop: {string: "stop datetime", type: "datetime"},
allday: {string: "allday", type: "boolean"},
partner_ids: {string: "attendees", type: "one2many", relation: 'partner'},
type: {string: "type", type: "integer"},
},
records: [
{id: 5, user_id: uid, partner_id: 4, name: "event 1", start: "2016-12-13 15:55:05", stop: "2016-12-15 18:55:05", allday: false, partner_ids: [4], type: 2},
{id: 6, user_id: uid, partner_id: 5, name: "event 2", start: "2016-12-18 08:00:00", stop: "2016-12-18 09:00:00", allday: false, partner_ids: [4], type: 3}
],
check_access_rights: function () {
return Promise.resolve(true);
}
},
'appointment.type': {
fields: {},
records: [],
},
user: {
fields: {
id: {string: "ID", type: "integer"},
display_name: {string: "Displayed name", type: "char"},
partner_id: {string: "partner", type: "many2one", relation: 'partner'},
image_1920: {string: "image", type: "integer"},
},
records: [
{id: 4, display_name: "user 4", partner_id: 4},
]
},
partner: {
fields: {
id: {string: "ID", type: "integer"},
display_name: {string: "Displayed name", type: "char"},
image_1920: {string: "image", type: "integer"},
},
records: [
{id: 4, display_name: "partner 4", image_1920: 'DDD'},
{id: 5, display_name: "partner 5", image_1920: 'DDD'},
]
},
filter_partner: {
fields: {
id: {string: "ID", type: "integer"},
user_id: {string: "user", type: "many2one", relation: 'user'},
partner_id: {string: "partner", type: "many2one", relation: 'partner'},
partner_checked: {string: "checked", type: "boolean"},
},
records: [
{id: 3, user_id: uid, partner_id: 4, partner_checked: true}
]
},
},
views: {},
};
target = getFixture();
setupViewRegistries();
serviceRegistry.add(
"user",
{
...userService,
start() {
const fakeUserService = userService.start(...arguments);
Object.defineProperty(fakeUserService, "userId", {
get: () => uid,
});
return fakeUserService;
},
},
{ force: true }
);
}
}, function () {
QUnit.test('sync google calendar', async function (assert) {
assert.expect(11);
let id = 7;
await makeView({
type: "calendar",
resModel: 'calendar.event',
serverData,
arch:
'<calendar class="o_calendar_test" '+
'js_class="attendee_calendar" '+
'date_start="start" '+
'date_stop="stop" '+
'attendee="partner_ids" '+
'mode="month">'+
'<field name="name"/>'+
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
'</calendar>',
mockRPC: async function (route, args) {
if (route === '/google_calendar/sync_data') {
assert.step(route);
serverData.models['calendar.event'].records.push(
{id: id++, user_id: uid, partner_id: 4, name: "event from google calendar", start: "2016-12-28 15:55:05", stop: "2016-12-29 18:55:05", allday: false, partner_ids: [4], type: 4}
);
return Promise.resolve({status: 'need_refresh'});
} else if (route === '/web/dataset/call_kw/calendar.event/search_read') {
assert.step(route);
} else if (route === '/web/dataset/call_kw/res.partner/get_attendee_detail') {
return Promise.resolve([]);
} else if (route === '/web/dataset/call_kw/res.users/has_group') {
return Promise.resolve(true);
}
},
});
// select the partner filter
await click(target.querySelector('.o_calendar_filter_item[data-value=all] input'));
// sync_data was called a first time without filter, event from google calendar was created twice
assert.containsN(target, '.fc-event-container', 4, "should display 4 events on the month");
await click(target.querySelector('.o_calendar_button_next'));
await click(target.querySelector('.o_calendar_button_prev'));
assert.verifySteps([
'/google_calendar/sync_data',
'/web/dataset/call_kw/calendar.event/search_read',
'/google_calendar/sync_data',
'/web/dataset/call_kw/calendar.event/search_read',
'/google_calendar/sync_data',
'/web/dataset/call_kw/calendar.event/search_read',
'/google_calendar/sync_data',
'/web/dataset/call_kw/calendar.event/search_read',
], 'should do a search_read before and after the call to sync_data');
assert.containsN(target, '.fc-event-container', 6, "should now display 6 events on the month");
});
QUnit.test("component is destroyed while sync google calendar", async function (assert) {
assert.expect(4);
const def = makeDeferred();
serverData.actions = {
1: {
id: 1,
name: "Partners",
res_model: "calendar.event",
type: "ir.actions.act_window",
views: [
[false, "list"],
[false, "calendar"],
],
},
};
serverData.views = {
"calendar.event,false,calendar": `
<calendar class="o_calendar_test" js_class="attendee_calendar" date_start="start" date_stop="stop">
<field name="name"/>
<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>
</calendar>`,
"calendar.event,false,list": `<tree sample="1"/>`,
"calendar.event,false,search": `<search />`,
};
const webClient = await createWebClient({
serverData,
async mockRPC(route, args) {
if (route === '/google_calendar/sync_data') {
assert.step(route);
return def;
} else if (route === '/web/dataset/call_kw/calendar.event/search_read') {
assert.step(route);
} else if (route === '/web/dataset/call_kw/res.partner/get_attendee_detail') {
return Promise.resolve([]);
} else if (route === '/web/dataset/call_kw/res.users/has_group') {
return Promise.resolve(true);
}
},
});
await doAction(webClient, 1);
click(target.querySelector(".o_cp_switch_buttons .o_calendar"));
await nextTick();
click(target.querySelector(".o_cp_switch_buttons .o_calendar"));
await nextTick();
def.resolve();
await nextTick();
assert.verifySteps([
"/google_calendar/sync_data",
"/google_calendar/sync_data",
"/web/dataset/call_kw/calendar.event/search_read"
], "Correct RPC calls were made");
});
});