diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint
index d71bc227bc..c6ce232603 100755
--- a/bin/docker-entrypoint
+++ b/bin/docker-entrypoint
@@ -72,6 +72,7 @@ case "$1" in
scheduler
;;
dev_server)
+ export FLASK_DEBUG=1
exec /app/manage.py runserver --debugger --reload -h 0.0.0.0
;;
shell)
diff --git a/client/app/assets/less/inc/contacts.less b/client/app/assets/less/inc/contacts.less
deleted file mode 100755
index a8f53bd0df..0000000000
--- a/client/app/assets/less/inc/contacts.less
+++ /dev/null
@@ -1,75 +0,0 @@
-.contacts {
- &:not(.c-profile) {
- padding: 0 3px;
- }
-
- & > [class*="col-"] {
- padding: 0 10px;
- }
-
- .c-item {
- border: 1px solid #e2e2e2;
- border-radius: 2px;
- margin-bottom: 24px;
-
- .ci-avatar {
- display: block;
-
- img {
- width: 100%;
- border-radius: 2px 2px 0 0;
- }
- }
- }
-
- .ci-avatar {
- margin: -1px -1px 0;
- }
-
- .c-info {
- text-align: center;
- margin-top: 15px;
- padding: 0 5px;
-
- strong {
- color: #000;
- font-size: 14px;
- font-weight: 500;
- }
-
- small {
- color: #999;
- margin-top: 3px;
- }
-
- strong,
- small {
- .text-overflow();
- display: block;
- }
- }
-
- .c-footer {
- border-top: 1px solid #e2e2e2;
- margin-top: 18px;
-
- & > button {
- padding: 4px 10px 3px;
- color: #333;
- display: block;
- width: 100%;
- text-align: center;
- color: #333;
- font-weight: 500;
- border-radius: 2px;
- background: #fff;
- border: 0;
-
- & > i {
- font-size: 16px;
- vertical-align: middle;
- margin-top: -3px;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/client/app/assets/less/inc/todo-list.less b/client/app/assets/less/inc/todo-list.less
deleted file mode 100755
index 75eec15f6b..0000000000
--- a/client/app/assets/less/inc/todo-list.less
+++ /dev/null
@@ -1,48 +0,0 @@
-.todo-lists {
- padding: 15px 0 0 0;
- position: relative;
-
- .list-group-item {
- margin: 0;
- min-height: 36px;
-
-
- &:not(:last-child) {
- border-bottom: 1px solid #E2EBFF;
- }
- }
-
- &:before {
- content: "";
- position: absolute;
- top: 0;
- background: #E2EBFF;
- left: 50px;
- width: 1px;
- height: 100%;
- z-index: 1;
- }
-
- .input-helper:before {
- background: lighten(@red, 10%) !important;
- }
-}
-
-.tl-item {
- padding-left: 47px !important;
-
- input:checked + .input-helper + span {
- text-decoration: line-through;
- color: #b5b5b5;
- }
-}
-
-.todo-footer {
- padding: 10px;
- margin-top: 20px;
- border-top: 1px solid #E2EBFF;
- text-align: center;
- position: relative;
- z-index: 2;
- background: #fff;
-}
diff --git a/client/app/assets/less/redash/redash-newstyle.less b/client/app/assets/less/redash/redash-newstyle.less
index 40f212307e..657cba1345 100644
--- a/client/app/assets/less/redash/redash-newstyle.less
+++ b/client/app/assets/less/redash/redash-newstyle.less
@@ -35,6 +35,20 @@ body {
clear: both;
}
+.callout {
+ padding: 20px;
+ border: 1px solid #eee;
+ border-left-width: 5px;
+ border-radius: 3px;
+}
+
+.callout-warning {
+ border-left-color: #aa6708;
+}
+
+.callout-info {
+ border-left-color: #1b809e;
+}
// Fixed width layout for specific pages
@media (min-width: 768px) {
diff --git a/client/app/filters/index.js b/client/app/filters/index.js
index 31d0fb6fd0..960afdb7a4 100644
--- a/client/app/filters/index.js
+++ b/client/app/filters/index.js
@@ -127,3 +127,11 @@ export function prettySize(bytes) {
return bytes.toFixed(3) + ' ' + units[unit];
}
+
+export function join(arr) {
+ if (arr === undefined || arr === null) {
+ return '';
+ }
+
+ return arr.join(' / ');
+}
diff --git a/client/app/pages/settings/organization.html b/client/app/pages/settings/organization.html
index f2698f1688..b1f32ef1a6 100644
--- a/client/app/pages/settings/organization.html
+++ b/client/app/pages/settings/organization.html
@@ -16,38 +16,64 @@
General
Authentication
-
- Password Login Enabled
+ Password Login Enabled
+
+
+
+
+
+ Password based login is currently disabled and users will be able to login only with the enabled SSO options.
+
+
+
Google Login
+
+ Allowed Google Apps Domains
+
+
+ {{$item}}
+
+
+ {{domain}}
+
+
+
+
+ Any user registered with a {{$ctrl.settings.auth_google_apps_domains | join}} Google Apps account will be able to login. If they don't have an existing user, a new user will be created and join the Default group.
+
+
+
SAML
-
- SAML Enabled
+ SAML Enabled
-
+
\ No newline at end of file
diff --git a/client/app/pages/settings/organization.js b/client/app/pages/settings/organization.js
index 3d744f00c0..cc17735593 100644
--- a/client/app/pages/settings/organization.js
+++ b/client/app/pages/settings/organization.js
@@ -1,7 +1,7 @@
import settingsMenu from '@/lib/settings-menu';
import template from './organization.html';
-function OrganizationSettingsCtrl($http, toastr, Events) {
+function OrganizationSettingsCtrl($http, toastr, clientConfig, Events) {
Events.record('view', 'page', 'org_settings');
this.settings = {};
@@ -13,10 +13,20 @@ function OrganizationSettingsCtrl($http, toastr, Events) {
$http.post('api/settings/organization', { [key]: this.settings[key] }).then((response) => {
this.settings = response.data.settings;
toastr.success('Settings changes saved.');
+
+ if (this.disablePasswordLoginToggle() && this.settings.auth_password_login_enabled === false) {
+ this.settings.auth_password_login_enabled = true;
+ this.update('auth_password_login_enabled');
+ }
}).catch(() => {
toastr.error('Failed saving changes.');
});
};
+
+ this.googleLoginEnabled = clientConfig.googleLoginEnabled;
+
+ this.disablePasswordLoginToggle = () =>
+ (clientConfig.googleLoginEnabled || this.settings.auth_saml_enabled) === false;
}
export default function init(ngModule) {
diff --git a/redash/handlers/authentication.py b/redash/handlers/authentication.py
index df45a6a325..0788dd8344 100644
--- a/redash/handlers/authentication.py
+++ b/redash/handlers/authentication.py
@@ -64,6 +64,7 @@ def render_token_login_page(template, org_slug, token):
show_saml_login=current_org.get_setting('auth_saml_enabled'),
show_remote_user_login=settings.REMOTE_USER_LOGIN_ENABLED,
show_ldap_login=settings.LDAP_LOGIN_ENABLED,
+ org_slug=org_slug,
user=user), status_code
@@ -172,7 +173,8 @@ def client_config():
'dateFormat': date_format,
'dateTimeFormat': "{0} HH:mm".format(date_format),
'mailSettingsMissing': settings.MAIL_DEFAULT_SENDER is None,
- 'dashboardRefreshIntervals': settings.DASHBOARD_REFRESH_INTERVALS
+ 'dashboardRefreshIntervals': settings.DASHBOARD_REFRESH_INTERVALS,
+ 'googleLoginEnabled': settings.GOOGLE_OAUTH_ENABLED
}
client_config.update(defaults)
diff --git a/redash/handlers/settings.py b/redash/handlers/settings.py
index 5f3bc1fcee..77dab014cf 100644
--- a/redash/handlers/settings.py
+++ b/redash/handlers/settings.py
@@ -1,12 +1,13 @@
from flask import request
-from redash.models import db
-from redash.handlers.base import BaseResource
+from redash.models import db, Organization
+from redash.handlers.base import BaseResource, record_event
from redash.permissions import require_admin
from redash.settings.organization import settings as org_settings
-def get_settings_with_defaults(defaults, values):
+def get_settings_with_defaults(defaults, org):
+ values = org.settings.get('settings', {})
settings = {}
for setting, default_value in defaults.iteritems():
@@ -19,14 +20,15 @@ def get_settings_with_defaults(defaults, values):
else:
settings[setting] = current_value
+ settings['auth_google_apps_domains'] = org.google_apps_domains
+
return settings
class OrganizationSettings(BaseResource):
@require_admin
def get(self):
- current_values = self.current_org.settings.get('settings', {})
- settings = get_settings_with_defaults(org_settings, current_values)
+ settings = get_settings_with_defaults(org_settings, self.current_org)
return {
"settings": settings
@@ -39,13 +41,27 @@ def post(self):
if self.current_org.settings.get('settings') is None:
self.current_org.settings['settings'] = {}
+ previous_values = {}
for k, v in new_values.iteritems():
- self.current_org.set_setting(k, v)
+ if k == 'auth_google_apps_domains':
+ previous_values[k] = self.current_org.google_apps_domains
+ self.current_org.settings[Organization.SETTING_GOOGLE_APPS_DOMAINS] = v
+ else:
+ previous_values[k] = self.current_org.get_setting(k, raise_on_missing=False)
+ self.current_org.set_setting(k, v)
db.session.add(self.current_org)
db.session.commit()
- settings = get_settings_with_defaults(org_settings, self.current_org.settings['settings'])
+ self.record_event({
+ 'action': 'edit',
+ 'object_id': self.current_org.id,
+ 'object_type': 'settings',
+ 'new_values': new_values,
+ 'previous_values': previous_values
+ })
+
+ settings = get_settings_with_defaults(org_settings, self.current_org)
return {
"settings": settings
diff --git a/redash/models.py b/redash/models.py
index 2566cd42d4..0944bd9da7 100644
--- a/redash/models.py
+++ b/redash/models.py
@@ -341,14 +341,17 @@ def set_setting(self, key, value):
self.settings['settings'][key] = value
flag_modified(self, 'settings')
- def get_setting(self, key):
+ def get_setting(self, key, raise_on_missing=True):
if key in self.settings.get('settings', {}):
return self.settings['settings'][key]
if key in org_settings:
return org_settings[key]
- raise KeyError(key)
+ if raise_on_missing:
+ raise KeyError(key)
+
+ return None
@property
def admin_group(self):
diff --git a/redash/templates/invite.html b/redash/templates/invite.html
index 1a660882ff..21705c79a4 100644
--- a/redash/templates/invite.html
+++ b/redash/templates/invite.html
@@ -29,15 +29,15 @@
{% endif %}
{% if show_saml_login %}
- SAML Login
+ SAML Login
{% endif %}
{% if show_remote_user_login %}
- Remote User Login
+ Remote User Login
{% endif %}
{% if show_ldap_login %}
- LDAP/SSO Login
+ LDAP/SSO Login
{% endif %}
{% if show_google_openid or show_saml_login or show_remote_user_login or show_ldap_login %}
diff --git a/tests/handlers/test_settings.py b/tests/handlers/test_settings.py
index 937a6a7490..4c73b1d253 100644
--- a/tests/handlers/test_settings.py
+++ b/tests/handlers/test_settings.py
@@ -12,4 +12,20 @@ def test_post(self):
rv = self.make_request('post', '/api/settings/organization', data={'auth_password_login_enabled': True}, user=admin)
updated_org = Organization.get_by_slug(self.factory.org.slug)
self.assertEqual(rv.json['settings']['auth_password_login_enabled'], True)
- self.assertEqual(updated_org.settings['settings']['auth_password_login_enabled'], True)
\ No newline at end of file
+ self.assertEqual(updated_org.settings['settings']['auth_password_login_enabled'], True)
+
+ def test_updates_google_apps_domains(self):
+ admin = self.factory.create_admin()
+ domains = ['example.com']
+ rv = self.make_request('post', '/api/settings/organization', data={'auth_google_apps_domains': domains}, user=admin)
+ updated_org = Organization.get_by_slug(self.factory.org.slug)
+ self.assertEqual(updated_org.google_apps_domains, domains)
+
+ def test_get_returns_google_appas_domains(self):
+ admin = self.factory.create_admin()
+ domains = ['example.com']
+ admin.org.settings[Organization.SETTING_GOOGLE_APPS_DOMAINS] = domains
+
+ rv = self.make_request('get', '/api/settings/organization', user=admin)
+ self.assertEqual(rv.json['settings']['auth_google_apps_domains'], domains)
+