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 based login is currently disabled and users will be able to login only with the enabled SSO options. +

+
+

Google Login

+ + + {{$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

- +
- +
+ ng-change="$ctrl.update('auth_saml_nameid_format')" ng-model-options="{ debounce: 200 }">

- + \ 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) +