-
Notifications
You must be signed in to change notification settings - Fork 176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new settings to set the contact email address #2279
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this is WIP but I decided to leave a couple of comments.
girder/models/setting.py
Outdated
@setting_utilities.validator(SettingKey.CONTACT_EMAIL_ADDRESS) | ||
def validateCoreContactEmailAddress(doc): | ||
if not doc['value']: | ||
raise ValidationException('Email from address must not be blank.', 'value') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a configuration option for specifying a regex email addresses must conform to, that can probably be used here as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per the discussion here we probably want to support more than a simple email address regex (especially since the default EMAIL_FROM_ADDRESS
default already uses a proper name component).
The space and URL encoding described by the RFC also should be done, but probably not in the validator (as that will make the value hard to edit). The best place is probably in the Pug template.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I skimmed through RFC6068, and it appears that some degree of escaping is definitely necessary. I'm not yet clear on whether the syntax with a proper name is legal, though I expect most clients would tolerate it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need to address this.
events.bind('model.setting.save.after', CoreEventHandler.WEBROOT_SETTING_CHANGE, | ||
self._onSettingSave) | ||
|
||
def _onSettingSave(self, event): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sort of thing (which exists in other places in Girder thanks to yours truly) will not work in a distributed installation.
For the purposes of this PR this might be fine, but I think that #2274 might try to remove these patterns. For more detail about it there's a thread on girder-developers with the subject "Signalling between multiple Girder servers".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, as part of fully implementing #2274, the WebrootBase.indexHtml
should be switched to use a proper cache. I see nothing more that can be done on this PR.
@@ -1,6 +1,6 @@ | |||
.g-footer-links | |||
a#g-about-link(href="http://girder.readthedocs.org/en/latest/user-guide.html") About | |||
a#g-contact-link(href="mailto:kitware@kitware.com") Contact | |||
a#g-contact-link(href=href) Contact |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be something like contactHref
?
clients/web/src/views/App.js
Outdated
@@ -40,6 +40,7 @@ var App = View.extend({ | |||
this._started = false; | |||
settings = settings || {}; | |||
if (settings.start === undefined || settings.start) { | |||
this.contactEmail = settings.contactEmail; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should always store this setting in an instance variable, even if we're not starting the app.
render: function () { | ||
this.$el.html(LayoutFooterTemplate({ | ||
apiRoot: getApiRoot() | ||
apiRoot: getApiRoot(), | ||
href: this.emailHref |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be something like contactHref
?
@@ -9,9 +9,16 @@ import 'girder/stylesheets/layout/footer.styl'; | |||
* This view shows the footer in the layout. | |||
*/ | |||
var LayoutFooterView = View.extend({ | |||
|
|||
initialize: function (settings) { | |||
var mail = settings.contactEmail; // Has to change |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should have a fallback value, in case settings.contactEmail
is not provided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, use let
or const
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the fallback value can be the default email ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure, but maybe not. If a default is needed for this widget, it means that it's being used in another application that didn't pass enough parameters at all.
Maybe if no user is passed at all, then don't use an href
? What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used a blank ('') instead, but do you mean test if settings.contactEmail
is provided and if not don't use href and hide the contact link ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's a good idea.
This case will only apply when a some external code imports and reuses this view, but it should be handled gracefully.
This commit take both the client and the server side in adding new settings
This test verify the emailAddress setting in the webroot.mako
832ef92
to
5682b4c
Compare
e48b126
to
a0b2c2c
Compare
@@ -9,9 +9,18 @@ import 'girder/stylesheets/layout/footer.styl'; | |||
* This view shows the footer in the layout. | |||
*/ | |||
var LayoutFooterView = View.extend({ | |||
|
|||
initialize: function (settings) { | |||
const mail = settings.contactEmail || ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In initialize
, I think we should always set this.contactEmail
to be equal to settings.contactEmail
, with a default of null
if it's not provided.
Then, in render
, we should conditionally apply the concatenation with mailto:
(encoding it if necessary), then pass that result (or null
) into the template.
This will also LayoutFooterView
to be subclassed such that it takes the same parameters, but renders them in a different way.
clients/web/src/views/App.js
Outdated
@@ -39,6 +39,7 @@ var App = View.extend({ | |||
initialize: function (settings) { | |||
this._started = false; | |||
settings = settings || {}; | |||
this.contactEmail = settings.contactEmail || ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use null
for the value when no setting is provided, since that will never be confused with a case where a server sends a setting value that's purposely set to empty string.
@@ -42,6 +42,9 @@ def testAccessWebRoot(self): | |||
self.assertTrue('girder_app.min.js' in body) | |||
self.assertTrue('girder_lib.min.js' in body) | |||
|
|||
emailAddress = self.model('setting').get(SettingKey.CONTACT_EMAIL_ADDRESS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just going to get the default.
To ensure that changes to this setting are immediately respected, we should next set this setting to something totally different, then make another request to get the webroot, and ensure that the value is updated.
@@ -1,6 +1,7 @@ | |||
.g-footer-links | |||
a#g-about-link(href="http://girder.readthedocs.org/en/latest/user-guide.html") About | |||
a#g-contact-link(href="mailto:kitware@kitware.com") Contact | |||
if (contactHref !== '') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pug conditionals don't need parentheses.
I think you can check whether this is truthy at all (if contactHref
), which will fail if it's null
(intentionally not set) or an empty string (intentionally set, but empty).
input#g-core-contact-email-address.form-control.input-sm( | ||
type="text", value=settings['core.contact_email_address'] || '', | ||
placeholder=`Default: ${defaults['core.contact_email_address'] || 'none'}`, | ||
title="The contact email address for support.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about title="The contact email address for users to obtain support with this instance."
? I'm open to even better phrasing, but this is a little more descriptive.
girder/models/setting.py
Outdated
@setting_utilities.validator(SettingKey.CONTACT_EMAIL_ADDRESS) | ||
def validateCoreContactEmailAddress(doc): | ||
if not doc['value']: | ||
raise ValidationException('Email from address must not be blank.', 'value') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need to address this.
initialize: function (settings) { | ||
const mail = settings.contactEmail || ''; | ||
if (mail !== '') { | ||
this.emailHref = 'mailto:' + mail; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use an ES6 template literal for this concatenation.
d75c5d3
to
f74043a
Compare
After reading more of RFC6068 myself, it's clear that we cannot use a Although that spec also requires us to percent-encode many other characters that may appear in a valid email address (e.g. So, for now, I think you need to just replace the default and test email addresses with just the core |
Because:
I think it's justified to merge this without extensive validation, then fix things more globally in a separate PR. I don't want to derail this entire PR for a corner case, as long as we eventually fix the corner case. I'll file an issue for it now. |
f74043a
to
e834649
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A minor issue inline.
More importantly, I realized that this now contains an injection vulnerability. Try setting the contact email address to exactly
foo@bar.com'});});</script><script>$(function(){girder.app.render(); alert('Pwned');});</script><script>$(function(){new girder.views.App({parentView: null,dummy: '
via the settings page.
While we could attempt to sanitize some of this via a more sophisticated validator, the simplest and most reliable way is to probably encode the string to JSON programatically, which will escape any quotes for us. Any evil script tags, etc. would remain unescaped inside the string, but they would be escaped when Pug renders the footer link (since Pug escapes everything by default).
So, rather than include quotes in the webroot.mako
template, use Python's to convert the string value to JSON, and inject that result into webroot.mako
, and set it (without explicit quotes) as the value of contactEmail:
.
initialize: function (settings) { | ||
const mail = settings.contactEmail || null; | ||
if (mail !== null) { | ||
this.emailHref = `mailto: ${mail}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RFC6068 says that there's no space between mailto:
and the email address,
Also, in my opinion, it'd be cleaner overall to name this local variable const contactEmail
, and the instance variable this.contactHref
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you propose to convert into JSON you mean in the webroot.py ? Just like that :
contactEmail = json.dumps(ModelImporter.model('setting').get(SettingKey.CONTACT_EMAIL_ADDRESS))
And I just insert this into the template webroot.mako ?
307f0f3
to
de4ec3b
Compare
Two in one day! 🥂 |
Fixes #2266