[FIX] event: add signed static maps api

Google now requires an API key for the static maps API.
As API calls cost money, and we cannot restrict the domain of the caller
with the API key in emails, as the domain may vary vastly,
the signing of static API URLs is implemented in the existing
'google_map_img' of partners.

This in turn can be used to get a url for an existing location.
While preventing anyone from stealing the API key for their own purposes

task - 3079113

Part-of: odoo/odoo#107200
This commit is contained in:
Renaud Thiry
2023-01-20 19:56:52 +01:00
parent 5cc86c3bb1
commit 83e4551492
2 changed files with 74 additions and 4 deletions
+9 -3
View File
@@ -225,7 +225,10 @@
<div>
<i class="fa fa-map-marker"/>
<a t-attf-href="https://maps.google.com/maps?q={{ location }}" target="new">
See location on Google Maps
<img t-if="event_address.static_map_url and event_address.static_map_url_is_valid"
t-att-src="event_address.static_map_url"
style="vertical-align:bottom; width: 100%;" alt="Google Maps"/>
<t t-else="">See location on Google Maps</t>
</a>
</div>
</td></tr></table>
@@ -442,11 +445,14 @@
<tr><td valign="top" style="font-size: 14px;">
<!-- GOOGLE MAPS LINK -->
<table t-if="event_address" style="width:100%;"><tr><td>
<table t-if="event_address and location" style="width:100%;"><tr><td>
<div>
<i class="fa fa-map-marker"/>
<a t-attf-href="https://maps.google.com/maps?q={{ location }}" target="new">
See location on Google Maps
<img t-if="event_address.static_map_url and event_address.static_map_url_is_valid"
t-attf-src="{{ event_address.static_map_url }}"
style="vertical-align:bottom; width: 100%;" alt="Google Maps"/>
<span t-else="">See location on Google Maps</span>
</a>
</div>
</td></tr></table>
+65 -1
View File
@@ -1,7 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
import base64
import binascii
import hmac
import requests
import werkzeug.urls
from odoo import api, fields, models
class ResPartner(models.Model):
@@ -9,14 +16,71 @@ class ResPartner(models.Model):
event_count = fields.Integer(
'# Events', compute='_compute_event_count', groups='event.group_event_registration_desk')
static_map_url = fields.Char(compute="_compute_static_map_url")
static_map_url_is_valid = fields.Boolean(compute="_compute_static_map_url_is_valid")
def _compute_event_count(self):
self.event_count = 0
for partner in self:
partner.event_count = self.env['event.event'].search_count([('registration_ids.partner_id', 'child_of', partner.ids)])
@api.depends('zip', 'city', 'country_id', 'street')
def _compute_static_map_url(self):
for partner in self:
partner.static_map_url = partner._google_map_signed_img(zoom=13, width=598, height=200)
def _compute_static_map_url_is_valid(self):
"""Compute whether the link is valid.
This should only remain valid for a relatively short time.
Here, for the duration it is in cache.
"""
session = requests.Session()
for partner in self:
url = self.static_map_url
if not url:
partner.static_map_url_is_valid = False
continue
is_valid = False
# If the response isn't strictly successful, assume invalid url
try:
res = session.get(url, timeout=2)
if res.ok and not res.headers.get('X-Staticmap-API-Warning'):
is_valid = True
except requests.exceptions.RequestException:
pass
partner.static_map_url_is_valid = is_valid
def action_event_view(self):
action = self.env["ir.actions.actions"]._for_xml_id("event.action_event_view")
action['context'] = {}
action['domain'] = [('registration_ids.partner_id', 'child_of', self.ids)]
return action
def _google_map_signed_img(self, zoom=13, width=298, height=298):
"""Create a signed static image URL for the location of this partner."""
GOOGLE_MAPS_STATIC_API_KEY = self.env['ir.config_parameter'].sudo().get_param('google_maps.signed_static_api_key')
GOOGLE_MAPS_STATIC_API_SECRET = self.env['ir.config_parameter'].sudo().get_param('google_maps.signed_static_api_secret')
if not GOOGLE_MAPS_STATIC_API_KEY or not GOOGLE_MAPS_STATIC_API_SECRET:
return None
# generate signature as per https://developers.google.com/maps/documentation/maps-static/digital-signature#server-side-signing
location_string = f"{self.street}, {self.city} {self.zip}, {self.country_id and self.country_id.display_name or ''}"
params = {
'center': location_string,
'markers': f'size:mid|{location_string}',
'size': f"{width}x{height}",
'zoom': zoom,
'sensor': "false",
'key': GOOGLE_MAPS_STATIC_API_KEY,
}
unsigned_path = '/maps/api/staticmap?' + werkzeug.urls.url_encode(params)
try:
api_secret_bytes = base64.urlsafe_b64decode(GOOGLE_MAPS_STATIC_API_SECRET + "====")
except binascii.Error:
return None
url_signature_bytes = hmac.digest(api_secret_bytes, unsigned_path.encode(), 'sha1')
params['signature'] = base64.urlsafe_b64encode(url_signature_bytes)
return 'https://maps.googleapis.com/maps/api/staticmap?' + werkzeug.urls.url_encode(params)