@@ -0,0 +1,22 @@ | |||
name: 'Python Dependency Check' | |||
on: | |||
pull_request: | |||
workflow_dispatch: | |||
push: | |||
branches: [ develop ] | |||
permissions: | |||
contents: read | |||
jobs: | |||
deps-vulnerable-check: | |||
name: 'Vulnerable Dependency' | |||
runs-on: ubuntu-latest | |||
steps: | |||
- uses: actions/setup-python@v4 | |||
with: | |||
python-version: 3.8 | |||
- uses: actions/checkout@v3 | |||
- run: pip install pip-audit | |||
- run: pip-audit ${GITHUB_WORKSPACE} |
@@ -2231,14 +2231,14 @@ def get_website_settings(key): | |||
if not hasattr(local, "website_settings"): | |||
local.website_settings = db.get_singles_dict("Website Settings", cast=True) | |||
return local.website_settings[key] | |||
return local.website_settings.get(key) | |||
def get_system_settings(key): | |||
if not hasattr(local, "system_settings"): | |||
local.system_settings = db.get_singles_dict("System Settings", cast=True) | |||
return local.system_settings[key] | |||
return local.system_settings.get(key) | |||
def get_active_domains(): | |||
@@ -403,13 +403,12 @@ def modify_query(query): | |||
def modify_values(values): | |||
def stringify_value(value): | |||
if isinstance(value, int): | |||
def modify_value(value): | |||
if isinstance(value, (list, tuple)): | |||
value = tuple(modify_values(value)) | |||
elif isinstance(value, int): | |||
value = str(value) | |||
elif isinstance(value, float): | |||
truncated_float = int(value) | |||
if value == truncated_float: | |||
value = str(truncated_float) | |||
return value | |||
@@ -418,14 +417,15 @@ def modify_values(values): | |||
if isinstance(values, dict): | |||
for k, v in values.items(): | |||
values[k] = stringify_value(v) | |||
values[k] = modify_value(v) | |||
elif isinstance(values, (tuple, list)): | |||
new_values = [] | |||
for val in values: | |||
new_values.append(stringify_value(val)) | |||
new_values.append(modify_value(val)) | |||
values = new_values | |||
else: | |||
values = stringify_value(values) | |||
values = modify_value(values) | |||
return values | |||
@@ -123,8 +123,23 @@ class BaseDocument(object): | |||
return meta | |||
def __getstate__(self): | |||
self._meta = None | |||
return self.__dict__ | |||
""" | |||
Called when pickling. | |||
Returns a copy of `__dict__` excluding unpicklable values like `_meta`. | |||
More info: https://docs.python.org/3/library/pickle.html#handling-stateful-objects | |||
""" | |||
# Always use the dict.copy() method to avoid modifying the original state | |||
state = self.__dict__.copy() | |||
self.remove_unpicklable_values(state) | |||
return state | |||
def remove_unpicklable_values(self, state): | |||
"""Remove unpicklable values before pickling""" | |||
state.pop("_meta", None) | |||
def update(self, d): | |||
"""Update multiple fields of a doctype using a dictionary of key-value pairs. | |||
@@ -323,10 +323,7 @@ class OAuthWebRequestValidator(RequestValidator): | |||
# Check whether frappe server URL is set | |||
id_token_header = {"typ": "jwt", "alg": "HS256"} | |||
user = frappe.get_doc( | |||
"User", | |||
frappe.session.user, | |||
) | |||
user = frappe.get_doc("User", request.user) | |||
if request.nonce: | |||
id_token["nonce"] = request.nonce | |||
@@ -107,5 +107,4 @@ import "./frappe/utils/dashboard_utils.js"; | |||
import "./frappe/ui/chart.js"; | |||
import "./frappe/ui/datatable.js"; | |||
import "./frappe/ui/driver.js"; | |||
import "./frappe/ui/plyr.js"; | |||
import "./frappe/scanner"; |
@@ -33,13 +33,16 @@ frappe.help.show_video = function (youtube_id, title) { | |||
dialog.show(); | |||
dialog.$wrapper.addClass("video-modal"); | |||
let plyr = new frappe.Plyr(video[0], { | |||
hideControls: true, | |||
resetOnEnd: true, | |||
}); | |||
let plyr; | |||
frappe.utils.load_video_player().then(() => { | |||
plyr = new frappe.Plyr(video[0], { | |||
hideControls: true, | |||
resetOnEnd: true, | |||
}); | |||
}) | |||
dialog.onhide = () => { | |||
plyr.destroy(); | |||
plyr?.destroy(); | |||
}; | |||
} | |||
@@ -1528,5 +1528,9 @@ Object.assign(frappe.utils, { | |||
return [doctype, filter, val[0], val[1], false]; | |||
}); | |||
} | |||
}, | |||
load_video_player() { | |||
return frappe.require("video_player.bundle.js"); | |||
} | |||
}); |
@@ -5,6 +5,7 @@ frappe.provide("frappe.utils"); | |||
export default class OnboardingWidget extends Widget { | |||
async refresh() { | |||
frappe.utils.load_video_player(); | |||
this.new && await this.get_onboarding_data(); | |||
this.set_title(); | |||
this.set_actions(); | |||
@@ -156,7 +157,6 @@ export default class OnboardingWidget extends Widget { | |||
}; | |||
toggle_content(); | |||
// toggle_video(); | |||
} | |||
go_to_page(step) { | |||
@@ -1,3 +1,3 @@ | |||
import Plyr from "plyr/dist/plyr.polyfilled"; | |||
frappe.Plyr = Plyr; | |||
frappe.Plyr = Plyr; |
@@ -33,14 +33,16 @@ | |||
{% else %} | |||
{% if parent %} | |||
{% set url = item.url or '' %} | |||
{% set url = url if url.startswith('#') else url | abs_url %} | |||
<li class="nav-item"> | |||
<a class="nav-link" href="{{ (item.url or '')|abs_url }}" | |||
<a class="nav-link" href="{{ url }}" | |||
{% if item.open_in_new_tab %} target="_blank" {% endif %}> | |||
{{ _(item.label) }} | |||
</a> | |||
</li> | |||
{% else %} | |||
<a class="dropdown-item" href="{{ (item.url or '') | abs_url }}" | |||
<a class="dropdown-item" href="{{ url }}" | |||
{% if item.open_in_new_tab %} target="_blank" {% endif %}> | |||
{{ _(item.label) }} | |||
</a> | |||
@@ -11,7 +11,9 @@ | |||
{%- if web_template_type == 'Section' -%} | |||
{%- if not web_block.hide_block -%} | |||
<section class="section {{ classes }}" data-section-idx="{{ web_block.idx | e }}" | |||
<section class="section {{ classes }}" | |||
{% if web_block.section_id %} id="{{ web_block.section_id }}" {% endif %} | |||
data-section-idx="{{ web_block.idx | e }}" | |||
data-section-template="{{ web_block.web_template | e }}" | |||
{% if web_block.add_background_image -%} | |||
style="background: url({{ web_block.background_image}}) no-repeat center center; background-size: cover;" | |||
@@ -699,7 +699,7 @@ class TestBenchBuild(BaseTestCommands): | |||
self.assertEqual(result.exit_code, 0) | |||
self.assertEqual(result.exception, None) | |||
CURRENT_SIZE = 3.7 # MB | |||
CURRENT_SIZE = 3.5 # MB | |||
JS_ASSET_THRESHOLD = 0.1 | |||
hooks = frappe.get_hooks() | |||
@@ -511,6 +511,37 @@ class TestDB(unittest.TestCase): | |||
frappe.db.rollback() | |||
@run_only_if(db_type_is.POSTGRES) | |||
def test_modify_query(self): | |||
from frappe.database.postgres.database import modify_query | |||
query = "select * from `tabtree b` where lft > 13 and rgt <= 16 and name =1.0 and parent = 4134qrsdc and isgroup = 1.00045" | |||
self.assertEqual( | |||
"select * from \"tabtree b\" where lft > '13' and rgt <= '16' and name = '1' and parent = 4134qrsdc and isgroup = 1.00045", | |||
modify_query(query), | |||
) | |||
query = ( | |||
'select locate(".io", "frappe.io"), locate("3", cast(3 as varchar)), locate("3", 3::varchar)' | |||
) | |||
self.assertEqual( | |||
'select strpos( "frappe.io", ".io"), strpos( cast(3 as varchar), "3"), strpos( 3::varchar, "3")', | |||
modify_query(query), | |||
) | |||
@run_only_if(db_type_is.POSTGRES) | |||
def test_modify_values(self): | |||
from frappe.database.postgres.database import modify_values | |||
self.assertEqual( | |||
{"a": "23", "b": 23.0, "c": 23.0345, "d": "wow", "e": ("1", "2", "3", "abc")}, | |||
modify_values({"a": 23, "b": 23.0, "c": 23.0345, "d": "wow", "e": [1, 2, 3, "abc"]}), | |||
) | |||
self.assertEqual( | |||
["23", 23.0, 23.00004345, "wow", ("1", "2", "3", "abc")], | |||
modify_values((23, 23.0, 23.00004345, "wow", [1, 2, 3, "abc"])), | |||
) | |||
@run_only_if(db_type_is.MARIADB) | |||
class TestDDLCommandsMaria(unittest.TestCase): | |||
@@ -813,32 +844,6 @@ class TestDDLCommandsPost(unittest.TestCase): | |||
) | |||
self.assertEqual(len(indexs_in_table), 1) | |||
def test_modify_query(self): | |||
from frappe.database.postgres.database import modify_query | |||
query = "select * from `tabtree b` where lft > 13 and rgt <= 16 and name =1.0 and parent = 4134qrsdc and isgroup = 1.00045" | |||
self.assertEqual( | |||
"select * from \"tabtree b\" where lft > '13' and rgt <= '16' and name = '1' and parent = 4134qrsdc and isgroup = 1.00045", | |||
modify_query(query), | |||
) | |||
query = ( | |||
'select locate(".io", "frappe.io"), locate("3", cast(3 as varchar)), locate("3", 3::varchar)' | |||
) | |||
self.assertEqual( | |||
'select strpos( "frappe.io", ".io"), strpos( cast(3 as varchar), "3"), strpos( 3::varchar, "3")', | |||
modify_query(query), | |||
) | |||
def test_modify_values(self): | |||
from frappe.database.postgres.database import modify_values | |||
self.assertEqual( | |||
{"abcd": "23", "efgh": "23", "ijkl": 23.0345, "mnop": "wow"}, | |||
modify_values({"abcd": 23, "efgh": 23.0, "ijkl": 23.0345, "mnop": "wow"}), | |||
) | |||
self.assertEqual(["23", "23", 23.00004345, "wow"], modify_values((23, 23.0, 23.00004345, "wow"))) | |||
def test_sequence_table_creation(self): | |||
from frappe.core.doctype.doctype.test_doctype import new_doctype | |||
@@ -16,7 +16,9 @@ class TestOAuth20(unittest.TestCase): | |||
def setUp(self): | |||
make_test_records("OAuth Client") | |||
make_test_records("User") | |||
self.client_id = frappe.get_all("OAuth Client", fields=["*"])[0].get("client_id") | |||
client = frappe.get_all("OAuth Client", fields=["*"])[0] | |||
self.client_id = client.get("client_id") | |||
self.client_secret = client.get("client_secret") | |||
self.form_header = {"content-type": "application/x-www-form-urlencoded"} | |||
self.scope = "all openid" | |||
self.redirect_uri = "http://localhost" | |||
@@ -90,6 +92,9 @@ class TestOAuth20(unittest.TestCase): | |||
self.assertTrue(bearer_token.get("token_type") == "Bearer") | |||
self.assertTrue(check_valid_openid_response(bearer_token.get("access_token"))) | |||
decoded_token = self.decode_id_token(bearer_token.get("id_token")) | |||
self.assertEqual(decoded_token["email"], "test@example.com") | |||
def test_login_using_authorization_code_with_pkce(self): | |||
update_client_for_auth_code_grant(self.client_id) | |||
@@ -142,6 +147,9 @@ class TestOAuth20(unittest.TestCase): | |||
self.assertTrue(bearer_token.get("access_token")) | |||
self.assertTrue(bearer_token.get("id_token")) | |||
decoded_token = self.decode_id_token(bearer_token.get("id_token")) | |||
self.assertEqual(decoded_token["email"], "test@example.com") | |||
def test_revoke_token(self): | |||
client = frappe.get_doc("OAuth Client", self.client_id) | |||
client.grant_type = "Authorization Code" | |||
@@ -316,16 +324,19 @@ class TestOAuth20(unittest.TestCase): | |||
# Parse bearer token json | |||
bearer_token = token_response.json() | |||
id_token = bearer_token.get("id_token") | |||
payload = jwt.decode( | |||
payload = self.decode_id_token(bearer_token.get("id_token")) | |||
self.assertEqual(payload["email"], "test@example.com") | |||
self.assertTrue(payload.get("nonce") == nonce) | |||
def decode_id_token(self, id_token): | |||
return jwt.decode( | |||
id_token, | |||
audience=client.client_id, | |||
key=client.client_secret, | |||
audience=self.client_id, | |||
key=self.client_secret, | |||
algorithms=["HS256"], | |||
) | |||
self.assertTrue(payload.get("nonce") == nonce) | |||
def check_valid_openid_response(access_token=None): | |||
"""Return True for valid response.""" | |||
@@ -279,7 +279,7 @@ def get_email_body_for_qr_code(kwargs_dict): | |||
"""Get QRCode email body.""" | |||
body_template = _( | |||
"Please click on the following link and follow the instructions on the page. {0}" | |||
).format("<br><br> {{qrcode_link}}") | |||
).format("<br><br> <a href='{{qrcode_link}}'>{{qrcode_link}}</a>") | |||
body = frappe.render_template(body_template, kwargs_dict) | |||
return body | |||
@@ -36,6 +36,7 @@ def web_block(template, values=None, **kwargs): | |||
def web_blocks(blocks): | |||
import frappe | |||
from frappe import _, _dict, throw | |||
from frappe.website.doctype.web_page.web_page import get_web_blocks_html | |||
@@ -62,8 +63,26 @@ def web_blocks(blocks): | |||
out = get_web_blocks_html(web_blocks) | |||
html = out.html | |||
for script in out.scripts: | |||
html += "<script>{}</script>".format(script) | |||
if not frappe.flags.web_block_scripts: | |||
frappe.flags.web_block_scripts = {} | |||
frappe.flags.web_block_styles = {} | |||
for template, scripts in out.scripts.items(): | |||
# deduplication of scripts when web_blocks methods are used in web pages | |||
# see render_dynamic method web_page.py | |||
if template not in frappe.flags.web_block_scripts: | |||
for script in scripts: | |||
html += f"<script data-web-template='{template}'>{script}</script>" | |||
frappe.flags.web_block_scripts[template] = True | |||
for template, styles in out.styles.items(): | |||
# deduplication of styles when web_blocks methods are used in web pages | |||
# see render_dynamic method web_page.py | |||
if template not in frappe.flags.web_block_styles: | |||
for style in styles: | |||
html += f"<style data-web-template='{template}'>{style}</style>" | |||
frappe.flags.web_block_styles[template] = True | |||
return html | |||
@@ -33,13 +33,18 @@ | |||
{% endblock %} | |||
{% block style %} | |||
<style> | |||
{{ style or "" }} | |||
</style> | |||
{%- for style in page_builder_styles -%} | |||
{%- if style -%} | |||
<style>{{ style }}</style> | |||
{%- endif -%} | |||
{%- for web_template, styles in (page_builder_styles or {}).items() -%} | |||
{%- if styles -%} | |||
{%- for style in styles -%} | |||
<style data-web-template="{{ web_template }}">{{ style }}</style> | |||
{%- endfor -%} | |||
{%- endif -%} | |||
{%- endfor -%} | |||
{% endblock %} | |||
{% block script %} | |||
@@ -47,7 +52,11 @@ | |||
<script>{{ script }}</script> | |||
{%- endif -%} | |||
{%- for script in page_builder_scripts -%} | |||
<script>{{ script }}</script> | |||
{%- for web_template, scripts in (page_builder_scripts or {}).items() -%} | |||
{%- if scripts -%} | |||
{%- for script in scripts -%} | |||
<script data-web-template="{{ web_template }}">{{ script }}</script> | |||
{%- endfor -%} | |||
{%- endif -%} | |||
{%- endfor -%} | |||
{% endblock %} |
@@ -79,15 +79,23 @@ class WebPage(WebsiteGenerator): | |||
def render_dynamic(self, context): | |||
# dynamic | |||
is_jinja = context.dynamic_template or "<!-- jinja -->" in context.main_section | |||
if is_jinja or ("{{" in context.main_section): | |||
is_jinja = ( | |||
context.dynamic_template | |||
or "<!-- jinja -->" in context.main_section | |||
or ("{{" in context.main_section) | |||
) | |||
if is_jinja: | |||
frappe.flags.web_block_scripts = {} | |||
frappe.flags.web_block_styles = {} | |||
try: | |||
context["main_section"] = render_template(context.main_section, context) | |||
if not "<!-- static -->" in context.main_section: | |||
context["no_cache"] = 1 | |||
except TemplateSyntaxError: | |||
if is_jinja: | |||
raise | |||
raise | |||
finally: | |||
frappe.flags.web_block_scripts = {} | |||
frappe.flags.web_block_styles = {} | |||
def set_breadcrumbs(self, context): | |||
"""Build breadcrumbs template""" | |||
@@ -193,9 +201,9 @@ def check_publish_status(): | |||
def get_web_blocks_html(blocks): | |||
"""Converts a list of blocks into Raw HTML and extracts out their scripts for deduplication""" | |||
out = frappe._dict(html="", scripts=[], styles=[]) | |||
extracted_scripts = [] | |||
extracted_styles = [] | |||
out = frappe._dict(html="", scripts={}, styles={}) | |||
extracted_scripts = {} | |||
extracted_styles = {} | |||
for block in blocks: | |||
web_template = frappe.get_cached_doc("Web Template", block.web_template) | |||
rendered_html = frappe.render_template( | |||
@@ -209,11 +217,15 @@ def get_web_blocks_html(blocks): | |||
html, scripts, styles = extract_script_and_style_tags(rendered_html) | |||
out.html += html | |||
if block.web_template not in extracted_scripts: | |||
out.scripts += scripts | |||
extracted_scripts.append(block.web_template) | |||
extracted_scripts.setdefault(block.web_template, []) | |||
extracted_scripts[block.web_template] += scripts | |||
if block.web_template not in extracted_styles: | |||
out.styles += styles | |||
extracted_styles.append(block.web_template) | |||
extracted_styles.setdefault(block.web_template, []) | |||
extracted_styles[block.web_template] += styles | |||
out.scripts = extracted_scripts | |||
out.styles = extracted_styles | |||
return out | |||
@@ -9,6 +9,7 @@ | |||
"edit_values", | |||
"web_template_values", | |||
"css_class", | |||
"section_id", | |||
"column_break_5", | |||
"add_container", | |||
"add_top_padding", | |||
@@ -103,11 +104,17 @@ | |||
"fieldname": "background_image", | |||
"fieldtype": "Attach Image", | |||
"label": "Background Image" | |||
}, | |||
{ | |||
"description": "IDs must contain only alphanumeric characters, not contain spaces, and should be unique.", | |||
"fieldname": "section_id", | |||
"fieldtype": "Data", | |||
"label": "Section ID" | |||
} | |||
], | |||
"istable": 1, | |||
"links": [], | |||
"modified": "2022-03-21 14:23:32.665108", | |||
"modified": "2022-06-21 20:00:17.025272", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Web Page Block", | |||
@@ -67,7 +67,11 @@ | |||
<i class='fa fa-phone'></i> <span itemprop="telephone">{{ phone }}</span><br> | |||
{% endif %} | |||
{% if email_id %} | |||
<i class='fa fa-envelope'></i> <span itemprop="email">{{ email_id }}</span><br> | |||
<i class='fa fa-envelope'></i> | |||
<span itemprop="email"> | |||
<a href="mailto:{{ email_id }}">{{ email_id }}</a> | |||
</span> | |||
<br> | |||
{% endif %} | |||
{% if skype %} | |||
<i class='fa fa-skype'></i> <span itemprop="email">{{ skype }}</span><br> | |||
@@ -14,7 +14,7 @@ dependencies = [ | |||
"GitPython~=3.1.14", | |||
"Jinja2~=3.1.2", | |||
"Pillow~=9.1.1", | |||
"PyJWT~=2.0.1", | |||
"PyJWT~=2.4.0", | |||
"PyMySQL~=1.0.2", | |||
"PyPDF2~=2.1.0", | |||
"PyPika~=0.48.9", | |||