From 19802f88a2b31ac7ddd9f6ac47b5e61026b8bce2 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Mon, 15 Aug 2016 19:48:35 +0200 Subject: [PATCH] Improved external user auth config. Fixed browser tests. --- .../controllers/_dashboard/first_steps.coffee | 2 +- .../app/controllers/_manage/security.coffee | 4 +- .../app/controllers/_settings/area.coffee | 179 +----- .../controllers/_settings/area_item.coffee | 78 +++ .../controllers/_settings/area_logo.coffee | 89 +++ .../controllers/_settings/area_switch.coffee | 97 +++ .../javascripts/app/controllers/login.coffee | 51 +- .../javascripts/app/lib/app_post/i18n.coffee | 7 +- .../first_steps_test_ticket_finish.jst.eco | 6 +- .../app/views/settings/switch.jst.eco | 19 + .../20160815000001_update_setting_auth.rb | 562 ++++++++++++++++++ db/seeds.rb | 251 +++++++- public/assets/tests/core.js | 18 + test/browser/setting_test.rb | 129 ++-- 14 files changed, 1219 insertions(+), 273 deletions(-) create mode 100644 app/assets/javascripts/app/controllers/_settings/area_item.coffee create mode 100644 app/assets/javascripts/app/controllers/_settings/area_logo.coffee create mode 100644 app/assets/javascripts/app/controllers/_settings/area_switch.coffee create mode 100644 app/assets/javascripts/app/views/settings/switch.jst.eco create mode 100644 db/migrate/20160815000001_update_setting_auth.rb diff --git a/app/assets/javascripts/app/controllers/_dashboard/first_steps.coffee b/app/assets/javascripts/app/controllers/_dashboard/first_steps.coffee index 21de356f3..9014da9d2 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/first_steps.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/first_steps.coffee @@ -72,7 +72,7 @@ class App.DashboardFirstSteps extends App.Controller ) $('.modal .modal-body').html(finish) ) - @delay(create, 1800) + @delay(create, 2800) template testTicketFinish: (data) -> diff --git a/app/assets/javascripts/app/controllers/_manage/security.coffee b/app/assets/javascripts/app/controllers/_manage/security.coffee index 37d245a4b..13a84ffaa 100644 --- a/app/assets/javascripts/app/controllers/_manage/security.coffee +++ b/app/assets/javascripts/app/controllers/_manage/security.coffee @@ -8,8 +8,8 @@ class Security extends App.ControllerTabs @tabs = [ { name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Security::Base' } } { name: 'Password', 'target': 'password', controller: App.SettingsArea, params: { area: 'Security::Password' } } - #{ name: 'Authentication', 'target': 'auth', controller: App.SettingsArea, params: { area: 'Security::Authentication' } } - { name: 'Third-Party Applications', 'target': 'third_party_auth', controller: App.SettingsThirdPartyAuthentication, params: { area: 'Security::ThirdPartyAuthentication' } } + #{ name: 'Authentication', 'target': 'auth', controller: App.SettingsArea, params: { area: 'Security::Authentication' } } + { name: 'Third-Party Applications', 'target': 'third_party_auth', controller: App.SettingsArea, params: { area: 'Security::ThirdPartyAuthentication' } } ] @render() diff --git a/app/assets/javascripts/app/controllers/_settings/area.coffee b/app/assets/javascripts/app/controllers/_settings/area.coffee index 234a258de..97b58a5d8 100644 --- a/app/assets/javascripts/app/controllers/_settings/area.coffee +++ b/app/assets/javascripts/app/controllers/_settings/area.coffee @@ -4,7 +4,11 @@ class App.SettingsArea extends App.Controller # check authentication @authenticateCheckRedirect() - @subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false) + if App.Setting.count() is 0 + App.Setting.fetchFull(@render) + return + @render() + #@subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false) render: => @@ -31,179 +35,10 @@ class App.SettingsArea extends App.Controller elements = [] for setting in settings - if setting.name is 'product_logo' - item = new App.SettingsAreaLogo(setting: setting) + if setting.preferences.controller && App[setting.preferences.controller] + item = new App[setting.preferences.controller](setting: setting) else item = new App.SettingsAreaItem(setting: setting) elements.push item.el @html elements - -class App.SettingsAreaItem extends App.Controller - events: - 'submit form': 'update' - - constructor: -> - super - @render() - - render: => - - # defaults - directValue = 0 - for item in @setting.options['form'] - directValue += 1 - if directValue > 1 - for item in @setting.options['form'] - item['default'] = @setting.state_current.value[item.name] - else - item['default'] = @setting.state_current.value - - # form - @configure_attributes = @setting.options['form'] - - # item - @html App.view('settings/item')( - setting: @setting - ) - - new App.ControllerForm( - el: @el.find('.form-item'), - model: { configure_attributes: @configure_attributes, className: '' } - autofocus: false - ) - - update: (e) => - e.preventDefault() - @formDisable(e) - params = @formParam(e.target) - - directValue = 0 - directData = undefined - for item in @setting.options['form'] - directValue += 1 - directData = params[item.name] - - if directValue > 1 - state_current = { - value: params - } - #App.Config.set((@setting.name, params) - else - state_current = { - value: directData - } - #App.Config.set(@setting.name, directData) - - @setting['state_current'] = state_current - ui = @ - @setting.save( - done: => - ui.formEnable(e) - App.Event.trigger 'notify', { - type: 'success' - msg: App.i18n.translateContent('Update successful!') - timeout: 2000 - } - - # rerender ui || get new collections and session data - App.Setting.preferencesPost(@setting) - - fail: (settings, details) -> - ui.formEnable(e) - App.Event.trigger 'notify', { - type: 'error' - msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!') - timeout: 2000 - } - ) - -class App.SettingsAreaLogo extends App.Controller - elements: - '.logo-preview': 'logoPreview' - - events: - 'submit form': 'submit' - 'change .js-upload': 'onLogoPick' - - constructor: -> - super - @render() - - render: -> - localElement = $(App.view('settings/logo')( - setting: @setting - )) - localElement.find('.js-loginPreview').html( App.view('generic/login_preview')( - logoUrl: @logoUrl() - logoChange: true - )) - @html localElement - - onLogoPick: (event) => - reader = new FileReader() - - reader.onload = (e) => - @logoPreview.attr('src', e.target.result) - - file = event.target.files[0] - - # if no file is given, about in file upload was used - return if !file - - maxSiteInMb = 8 - if file.size && file.size > 1024 * 1024 * maxSiteInMb - App.Event.trigger 'notify', { - type: 'error' - msg: App.i18n.translateContent('File too big, max. %s MB allowed.', maxSiteInMb) - timeout: 2000 - } - @logoPreview.attr('src', '') - return - - reader.readAsDataURL(file) - - submit: (e) => - e.preventDefault() - @formDisable(e) - - # get params - @params = @formParam(e.target) - - # add logo - @params.logo = @logoPreview.attr('src') - - store = (logoResizeDataUrl) => - - # store image - @params.logo_resize = logoResizeDataUrl - @ajax( - id: "setting_image_#{@setting.id}" - type: 'PUT' - url: "#{@apiPath}/settings/image/#{@setting.id}" - data: JSON.stringify(@params) - processData: true - success: (data, status, xhr) => - @formEnable(e) - if data.result is 'ok' - App.Event.trigger 'notify', { - type: 'success' - msg: App.i18n.translateContent('Update successful!') - timeout: 2000 - } - for setting in data.settings - value = App.Setting.get(setting.name) - App.Config.set(name, value) - else - App.Event.trigger 'notify', { - type: 'error' - msg: App.i18n.translateContent(data.message) - timeout: 2000 - } - - fail: => - @formEnable(e) - ) - - # add resized image - App.ImageService.resizeForApp(@params.logo, @logoPreview.width(), @logoPreview.height(), store) diff --git a/app/assets/javascripts/app/controllers/_settings/area_item.coffee b/app/assets/javascripts/app/controllers/_settings/area_item.coffee new file mode 100644 index 000000000..2f54d0c3b --- /dev/null +++ b/app/assets/javascripts/app/controllers/_settings/area_item.coffee @@ -0,0 +1,78 @@ +class App.SettingsAreaItem extends App.Controller + events: + 'submit form': 'update' + + constructor: -> + super + @render() + + render: => + + # defaults + directValue = 0 + for item in @setting.options['form'] + directValue += 1 + if directValue > 1 + for item in @setting.options['form'] + item['default'] = @setting.state_current.value[item.name] + else + item['default'] = @setting.state_current.value + + # form + @configure_attributes = @setting.options['form'] + + # item + @html App.view('settings/item')( + setting: @setting + ) + + new App.ControllerForm( + el: @el.find('.form-item'), + model: { configure_attributes: @configure_attributes, className: '' } + autofocus: false + ) + + update: (e) => + e.preventDefault() + @formDisable(e) + params = @formParam(e.target) + + directValue = 0 + directData = undefined + for item in @setting.options['form'] + directValue += 1 + directData = params[item.name] + + if directValue > 1 + state_current = { + value: params + } + #App.Config.set((@setting.name, params) + else + state_current = { + value: directData + } + #App.Config.set(@setting.name, directData) + + @setting['state_current'] = state_current + ui = @ + @setting.save( + done: => + ui.formEnable(e) + App.Event.trigger 'notify', { + type: 'success' + msg: App.i18n.translateContent('Update successful!') + timeout: 2000 + } + + # rerender ui || get new collections and session data + App.Setting.preferencesPost(@setting) + + fail: (settings, details) -> + ui.formEnable(e) + App.Event.trigger 'notify', { + type: 'error' + msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!') + timeout: 2000 + } + ) diff --git a/app/assets/javascripts/app/controllers/_settings/area_logo.coffee b/app/assets/javascripts/app/controllers/_settings/area_logo.coffee new file mode 100644 index 000000000..7f10c5058 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_settings/area_logo.coffee @@ -0,0 +1,89 @@ +class App.SettingsAreaLogo extends App.Controller + elements: + '.logo-preview': 'logoPreview' + + events: + 'submit form': 'submit' + 'change .js-upload': 'onLogoPick' + + constructor: -> + super + @render() + + render: -> + localElement = $(App.view('settings/logo')( + setting: @setting + )) + localElement.find('.js-loginPreview').html( App.view('generic/login_preview')( + logoUrl: @logoUrl() + logoChange: true + )) + @html localElement + + onLogoPick: (event) => + reader = new FileReader() + + reader.onload = (e) => + @logoPreview.attr('src', e.target.result) + + file = event.target.files[0] + + # if no file is given, about in file upload was used + return if !file + + maxSiteInMb = 8 + if file.size && file.size > 1024 * 1024 * maxSiteInMb + App.Event.trigger 'notify', { + type: 'error' + msg: App.i18n.translateContent('File too big, max. %s MB allowed.', maxSiteInMb) + timeout: 2000 + } + @logoPreview.attr('src', '') + return + + reader.readAsDataURL(file) + + submit: (e) => + e.preventDefault() + @formDisable(e) + + # get params + @params = @formParam(e.target) + + # add logo + @params.logo = @logoPreview.attr('src') + + store = (logoResizeDataUrl) => + + # store image + @params.logo_resize = logoResizeDataUrl + @ajax( + id: "setting_image_#{@setting.id}" + type: 'PUT' + url: "#{@apiPath}/settings/image/#{@setting.id}" + data: JSON.stringify(@params) + processData: true + success: (data, status, xhr) => + @formEnable(e) + if data.result is 'ok' + App.Event.trigger 'notify', { + type: 'success' + msg: App.i18n.translateContent('Update successful!') + timeout: 2000 + } + for setting in data.settings + value = App.Setting.get(setting.name) + App.Config.set(name, value) + else + App.Event.trigger 'notify', { + type: 'error' + msg: App.i18n.translateContent(data.message) + timeout: 2000 + } + + fail: => + @formEnable(e) + ) + + # add resized image + App.ImageService.resizeForApp(@params.logo, @logoPreview.width(), @logoPreview.height(), store) diff --git a/app/assets/javascripts/app/controllers/_settings/area_switch.coffee b/app/assets/javascripts/app/controllers/_settings/area_switch.coffee new file mode 100644 index 000000000..4384273e8 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_settings/area_switch.coffee @@ -0,0 +1,97 @@ +class App.SettingsAreaSwitch extends App.Controller + events: + 'change .js-setting input': 'toggleSetting' + 'submit form': 'update' + + elements: + '.js-setting input': 'uiSetting' + + constructor: -> + super + @render() + + render: => + + # defaults + directValue = 0 + for item in @setting.options['form'] + directValue += 1 + if directValue > 1 + for item in @setting.options['form'] + item['default'] = @setting.state_current.value[item.name] + else + item['default'] = @setting.state_current.value + + # form + @configure_attributes = @setting.options['form'] + + @subSetting = [] + for localSetting in @setting.preferences.sub + @subSetting.push App.Setting.findByAttribute('name', localSetting) + + # item + @html App.view('settings/switch')( + checked: App.Setting.get(@setting.name) + setting: @setting + subSetting: @subSetting + ) + for localSetting in @subSetting + console.log('localSetting', localSetting.state_current) + new App.ControllerForm( + el: @$('.form-item') + params: localSetting.state_current.value + model: { configure_attributes: localSetting.options['form'], className: '' } + autofocus: false + ) + + toggleSetting: => + value = @uiSetting.prop('checked') + App.Setting.set(@setting.name, value) + + update: (e) => + e.preventDefault() + @formDisable(e) + params = @formParam(e.target) + + localSetting = $(e.currentTarget).data('name') + setting = App.Setting.findByAttribute('name', localSetting) + + directValue = 0 + directData = undefined + for item in setting.options['form'] + directValue += 1 + directData = params[item.name] + + if directValue > 1 + state_current = { + value: params + } + #App.Config.set((setting.name, params) + else + state_current = { + value: directData + } + #App.Config.set(setting.name, directData) + + setting['state_current'] = state_current + ui = @ + setting.save( + done: -> + ui.formEnable(e) + App.Event.trigger 'notify', { + type: 'success' + msg: App.i18n.translateContent('Update successful!') + timeout: 2000 + } + + # rerender ui || get new collections and session data + App.Setting.preferencesPost(setting) + + fail: (settings, details) -> + ui.formEnable(e) + App.Event.trigger 'notify', { + type: 'error' + msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!') + timeout: 2000 + } + ) diff --git a/app/assets/javascripts/app/controllers/login.coffee b/app/assets/javascripts/app/controllers/login.coffee index 9d14bc04f..b5c9e0c9f 100644 --- a/app/assets/javascripts/app/controllers/login.coffee +++ b/app/assets/javascripts/app/controllers/login.coffee @@ -23,9 +23,8 @@ class Index extends App.ControllerContent # observe config changes related to login page @bind('config_update_local', (data) => - return if data.name != 'maintenance_mode' && - data.name != 'maintenance_login' && - data.name != 'maintenance_login_message' && + return if !data.name.match(/^maintenance/) && + !data.name.match(/^auth/) && data.name != 'user_lost_password' && data.name != 'user_create_account' && data.name != 'product_name' && @@ -38,33 +37,45 @@ class Index extends App.ControllerContent render: (data = {}) -> auth_provider_all = { facebook: { - url: '/auth/facebook', - name: 'Facebook', - config: 'auth_facebook', - class: 'facebook', + url: '/auth/facebook' + name: 'Facebook' + config: 'auth_facebook' + class: 'facebook' }, twitter: { - url: '/auth/twitter', - name: 'Twitter', - config: 'auth_twitter', - class: 'twitter', + url: '/auth/twitter' + name: 'Twitter' + config: 'auth_twitter' + class: 'twitter' }, linkedin: { - url: '/auth/linkedin', - name: 'LinkedIn', - config: 'auth_linkedin', - class: 'linkedin', + url: '/auth/linkedin' + name: 'LinkedIn' + config: 'auth_linkedin' + class: 'linkedin' + }, + github: { + url: '/auth/github' + name: 'Github' + config: 'auth_github' + class: 'github' + }, + gitlab: { + url: '/auth/gitlab' + name: 'Gitlab' + config: 'auth_gitlab' + class: 'gitlab' }, google_oauth2: { - url: '/auth/google_oauth2', - name: 'Google', - config: 'auth_google_oauth2', - class: 'google', + url: '/auth/google_oauth2' + name: 'Google' + config: 'auth_google_oauth2' + class: 'google' }, } auth_providers = [] for key, provider of auth_provider_all - if @Config.get( provider.config ) is true || @Config.get( provider.config ) is 'true' + if @Config.get(provider.config) is true || @Config.get(provider.config) is 'true' auth_providers.push provider @html App.view('login')( diff --git a/app/assets/javascripts/app/lib/app_post/i18n.coffee b/app/assets/javascripts/app/lib/app_post/i18n.coffee index a3ead5106..f9f6fb251 100644 --- a/app/assets/javascripts/app/lib/app_post/i18n.coffee +++ b/app/assets/javascripts/app/lib/app_post/i18n.coffee @@ -218,7 +218,7 @@ class _i18nSingleton extends Spine.Module if quote translated = App.Utils.htmlEscape(translated) - # apply inline markup + # apply inline markup pre if markup translated = translated .replace(/\|\|(.+?)\|\|/gm, '$1') @@ -241,6 +241,11 @@ class _i18nSingleton extends Spine.Module "🔗" ) + # apply inline markup post + if markup + translated = translated + .replace(/\[(.+?)\]\((.+?)\)/gm, '$1') + @log 'debug', 'translate', string, args, translated # return translated string diff --git a/app/assets/javascripts/app/views/dashboard/first_steps_test_ticket_finish.jst.eco b/app/assets/javascripts/app/views/dashboard/first_steps_test_ticket_finish.jst.eco index 3c10a91a3..34d98d4e9 100644 --- a/app/assets/javascripts/app/views/dashboard/first_steps_test_ticket_finish.jst.eco +++ b/app/assets/javascripts/app/views/dashboard/first_steps_test_ticket_finish.jst.eco @@ -1,2 +1,4 @@ -

<%- @T('A Test Ticket has been created, you can find it in your overview |"%s"|', @overviewName) %>

-

<%- @T('To open and work on it, click on the Ticket |#%s| directly %l or in the overview |"%s"| %l and click on the Ticket.', @ticketNumber, @ticketUrl, @overviewName, @overviewUrl) %>

\ No newline at end of file +

<%- @T('A Test Ticket has been created, you can find it in your overview "%s" %l.', @overviewName, @overviewUrl) %>

+
+ <%- @T('Open Ticket# %s', @ticketNumber) %> +
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/settings/switch.jst.eco b/app/assets/javascripts/app/views/settings/switch.jst.eco new file mode 100644 index 000000000..4a6d573ec --- /dev/null +++ b/app/assets/javascripts/app/views/settings/switch.jst.eco @@ -0,0 +1,19 @@ + +
+

<%- @T.apply(@, [@setting.description].concat(@setting.preferences.description_i18n)) %>

+ <% for localSetting in @subSetting: %> +
+
+
+ +
+
+
\ No newline at end of file diff --git a/db/migrate/20160815000001_update_setting_auth.rb b/db/migrate/20160815000001_update_setting_auth.rb new file mode 100644 index 000000000..ba0dbe6b7 --- /dev/null +++ b/db/migrate/20160815000001_update_setting_auth.rb @@ -0,0 +1,562 @@ + +class UpdateSettingAuth < ActiveRecord::Migration + def up + # return if it's a new setup + return if !Setting.find_by(name: 'system_init_done') + + Role.create_or_update( + id: 1, + name: 'Admin', + note: 'To configure your system.', + preferences: { + not: ['Customer'], + }, + default_at_signup: false, + updated_by_id: 1, + created_by_id: 1 + ) + Role.create_or_update( + id: 2, + name: 'Agent', + note: 'To work on Tickets.', + default_at_signup: false, + preferences: { + not: ['Customer'], + }, + updated_by_id: 1, + created_by_id: 1 + ) + Role.create_or_update( + id: 3, + name: 'Customer', + note: 'People who create Tickets ask for help.', + preferences: { + not: %w(Agent Admin), + }, + default_at_signup: true, + updated_by_id: 1, + created_by_id: 1 + ) + Role.create_or_update( + id: 4, + name: 'Report', + note: 'Access the report area.', + preferences: { + not: ['Customer'], + }, + default_at_signup: false, + created_by_id: 1, + updated_by_id: 1, + ) + + ObjectManager::Attribute.add( + force: true, + object: 'Organization', + name: 'shared', + display: 'Shared organization', + data_type: 'boolean', + data_option: { + null: true, + default: true, + note: 'Customers in the organization can view each other items.', + item_class: 'formGroup--halfSize', + translate: true, + options: { + true: 'yes', + false: 'no', + } + }, + editable: false, + active: true, + screens: { + edit: { + Admin: { + null: false, + }, + }, + view: { + '-all-' => { + shown: true, + }, + }, + }, + to_create: false, + to_migrate: false, + to_delete: false, + position: 1400, + ) + + ObjectManager::Attribute.add( + force: true, + object: 'User', + name: 'role_ids', + display: 'Permissions', + data_type: 'user_permission', + data_option: { + null: false, + item_class: 'checkbox', + }, + editable: false, + active: true, + screens: { + signup: {}, + invite_agent: { + '-all-' => { + null: false, + default: [Role.lookup(name: 'Agent').id], + }, + }, + invite_customer: {}, + edit: { + Admin: { + null: true, + }, + }, + view: { + '-all-' => { + shown: false, + }, + }, + }, + to_create: false, + to_migrate: false, + to_delete: false, + position: 1600, + ) + + Setting.create_if_not_exists( + title: 'Authentication via %s', + name: 'auth_ldap', + area: 'Security::Authentication', + description: 'Enables user authentication via %s.', + preferences: { + title_i18n: ['LDAP'], + description_i18n: ['LDAP'] + }, + state: { + adapter: 'Auth::Ldap', + host: 'localhost', + port: 389, + bind_dn: 'cn=Manager,dc=example,dc=org', + bind_pw: 'example', + uid: 'mail', + base: 'dc=example,dc=org', + always_filter: '', + always_roles: %w(Admin Agent), + always_groups: ['Users'], + sync_params: { + firstname: 'sn', + lastname: 'givenName', + email: 'mail', + login: 'mail', + }, + }, + frontend: false + ) + Setting.create_or_update( + title: 'Authentication via %s', + name: 'auth_twitter', + area: 'Security::ThirdPartyAuthentication', + description: 'Enables user authentication via %s. Register your app first at [%s](%s).', + options: { + form: [ + { + display: '', + null: true, + name: 'auth_twitter', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_twitter_credentials'], + title_i18n: ['Twitter'], + description_i18n: ['Twitter', 'Twitter Developer Site', 'https://dev.twitter.com/apps'] + }, + state: false, + frontend: true + ) + Setting.create_or_update( + title: 'Twitter App Credentials', + name: 'auth_twitter_credentials', + area: 'Security::ThirdPartyAuthentication::Twitter', + description: 'App credentials for Twitter.', + options: { + form: [ + { + display: 'Twitter Key', + null: true, + name: 'key', + tag: 'input', + }, + { + display: 'Twitter Secret', + null: true, + name: 'secret', + tag: 'input', + }, + ], + }, + state: {}, + frontend: false + ) + Setting.create_or_update( + title: 'Authentication via %s', + name: 'auth_facebook', + area: 'Security::ThirdPartyAuthentication', + description: 'Enables user authentication via %s. Register your app first at [%s](%s).', + options: { + form: [ + { + display: '', + null: true, + name: 'auth_facebook', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_facebook_credentials'], + title_i18n: ['Facebook'], + description_i18n: ['Facebook', 'Facebook Developer Site', 'https://developers.facebook.com/apps/'] + }, + state: false, + frontend: true + ) + + Setting.create_or_update( + title: 'Facebook App Credentials', + name: 'auth_facebook_credentials', + area: 'Security::ThirdPartyAuthentication::Facebook', + description: 'App credentials for Facebook.', + options: { + form: [ + { + display: 'App ID', + null: true, + name: 'app_id', + tag: 'input', + }, + { + display: 'App Secret', + null: true, + name: 'app_secret', + tag: 'input', + }, + ], + }, + state: {}, + frontend: false + ) + + Setting.create_or_update( + title: 'Authentication via %s', + name: 'auth_google_oauth2', + area: 'Security::ThirdPartyAuthentication', + description: 'Enables user authentication via %s. Register your app first at [%s](%s).', + options: { + form: [ + { + display: '', + null: true, + name: 'auth_google_oauth2', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_google_oauth2_credentials'], + title_i18n: ['Google'], + description_i18n: ['Google', 'Google API Console Site', 'https://console.developers.google.com/apis/credentials'] + }, + state: false, + frontend: true + ) + Setting.create_or_update( + title: 'Google App Credentials', + name: 'auth_google_oauth2_credentials', + area: 'Security::ThirdPartyAuthentication::Google', + description: 'Enables user authentication via Google.', + options: { + form: [ + { + display: 'Client ID', + null: true, + name: 'client_id', + tag: 'input', + }, + { + display: 'Client Secret', + null: true, + name: 'client_secret', + tag: 'input', + }, + ], + }, + state: {}, + frontend: false + ) + + Setting.create_or_update( + title: 'Authentication via %s', + name: 'auth_linkedin', + area: 'Security::ThirdPartyAuthentication', + description: 'Enables user authentication via %s. Register your app first at [%s](%s).', + options: { + form: [ + { + display: '', + null: true, + name: 'auth_linkedin', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_linkedin_credentials'], + title_i18n: ['LinkedIn'], + description_i18n: ['LinkedIn', 'Linkedin Developer Site', 'https://www.linkedin.com/developer/apps'] + }, + state: false, + frontend: true + ) + Setting.create_or_update( + title: 'LinkedIn App Credentials', + name: 'auth_linkedin_credentials', + area: 'Security::ThirdPartyAuthentication::Linkedin', + description: 'Enables user authentication via LinkedIn.', + options: { + form: [ + { + display: 'App ID', + null: true, + name: 'app_id', + tag: 'input', + }, + { + display: 'App Secret', + null: true, + name: 'app_secret', + tag: 'input', + }, + ], + }, + state: {}, + frontend: false + ) + + Setting.create_or_update( + title: 'Authentication via %s', + name: 'auth_github', + area: 'Security::ThirdPartyAuthentication', + description: 'Enables user authentication via %s. Register your app first at [%s](%s).', + options: { + form: [ + { + display: '', + null: true, + name: 'auth_github', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_github_credentials'], + title_i18n: ['Github'], + description_i18n: ['Github', 'Github OAuth Applications', 'https://github.com/settings/applications'] + }, + state: false, + frontend: true + ) + Setting.create_or_update( + title: 'Github App Credentials', + name: 'auth_github_credentials', + area: 'Security::ThirdPartyAuthentication::Github', + description: 'Enables user authentication via Github.', + options: { + form: [ + { + display: 'App ID', + null: true, + name: 'app_id', + tag: 'input', + }, + { + display: 'App Secret', + null: true, + name: 'app_secret', + tag: 'input', + }, + ], + }, + state: {}, + frontend: false + ) + + Setting.create_or_update( + title: 'Authentication via %s', + name: 'auth_gitlab', + area: 'Security::ThirdPartyAuthentication', + description: 'Enables user authentication via %s. Register your app first at [%s](%s).', + options: { + form: [ + { + display: '', + null: true, + name: 'auth_gitlab', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_gitlab_credentials'], + title_i18n: ['Gitlab'], + description_i18n: ['Gitlab', 'Gitlab Applications', 'https://your-gitlab-host/admin/applications'] + }, + state: false, + frontend: true + ) + Setting.create_or_update( + title: 'Gitlab App Credentials', + name: 'auth_gitlab_credentials', + area: 'Security::ThirdPartyAuthentication::Gitlab', + description: 'Enables user authentication via Gitlab.', + options: { + form: [ + { + display: 'App ID', + null: true, + name: 'app_id', + tag: 'input', + }, + { + display: 'App Secret', + null: true, + name: 'app_secret', + tag: 'input', + }, + { + display: 'Site', + null: true, + name: 'site', + tag: 'input', + placeholder: 'https://gitlab.YOURDOMAIN.com', + }, + ], + }, + state: {}, + frontend: false + ) + + Setting.create_or_update( + title: 'Authentication via %s', + name: 'auth_oauth2', + area: 'Security::ThirdPartyAuthentication', + description: 'Enables user authentication via Generic OAuth2. Register your app first,', + options: { + form: [ + { + display: '', + null: true, + name: 'auth_oauth2', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_oauth2_credentials'], + title_i18n: ['Generic OAuth2'], + }, + state: false, + frontend: true + ) + Setting.create_or_update( + title: 'Generic OAuth2 App Credentials', + name: 'auth_oauth2_credentials', + area: 'Security::ThirdPartyAuthentication::GenericOAuth', + description: 'Enables user authentication via Generic OAuth2.', + options: { + form: [ + { + display: 'Name', + null: true, + name: 'name', + tag: 'input', + placeholder: 'Some Provider Name', + }, + { + display: 'App ID', + null: true, + name: 'app_id', + tag: 'input', + }, + { + display: 'App Secret', + null: true, + name: 'app_secret', + tag: 'input', + }, + { + display: 'Site', + null: true, + name: 'site', + tag: 'input', + placeholder: 'https://gitlab.YOURDOMAIN.com', + }, + { + display: 'authorize_url', + null: true, + name: 'authorize_url', + tag: 'input', + placeholder: '/oauth/authorize', + }, + { + display: 'token_url', + null: true, + name: 'token_url', + tag: 'input', + placeholder: '/oauth/token', + }, + ], + }, + state: {}, + frontend: false + ) + + end +end diff --git a/db/seeds.rb b/db/seeds.rb index 956d8b918..703205852 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -110,7 +110,7 @@ Setting.create_if_not_exists( }, ], }, - preferences: { prio: 3 }, + preferences: { prio: 3, controller: 'SettingsAreaLogo' }, state: 'logo.svg', frontend: true ) @@ -432,10 +432,14 @@ Setting.create_if_not_exists( frontend: true ) Setting.create_if_not_exists( - title: 'Authentication via LDAP', + title: 'Authentication via %s', name: 'auth_ldap', area: 'Security::Authentication', - description: 'Enables user authentication via LDAP.', + description: 'Enables user authentication via %s.', + preferences: { + title_i18n: ['LDAP'], + description_i18n: ['LDAP'] + }, state: { adapter: 'Auth::Ldap', host: 'localhost', @@ -457,10 +461,10 @@ Setting.create_if_not_exists( frontend: false ) Setting.create_if_not_exists( - title: 'Authentication via Twitter', + title: 'Authentication via %s', name: 'auth_twitter', area: 'Security::ThirdPartyAuthentication', - description: "@T('Enables user authentication via twitter. Register your app first at [Twitter Developer Site](https://dev.twitter.com/apps)')", + description: 'Enables user authentication via %s. Register your app first at [%s](%s).', options: { form: [ { @@ -475,13 +479,19 @@ Setting.create_if_not_exists( }, ], }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_twitter_credentials'], + title_i18n: ['Twitter'], + description_i18n: ['Twitter', 'Twitter Developer Site', 'https://dev.twitter.com/apps'] + }, state: false, frontend: true ) Setting.create_if_not_exists( title: 'Twitter App Credentials', name: 'auth_twitter_credentials', - area: 'Security::ThirdPartyAuthentication', + area: 'Security::ThirdPartyAuthentication::Twitter', description: 'App credentials for Twitter.', options: { form: [ @@ -503,10 +513,10 @@ Setting.create_if_not_exists( frontend: false ) Setting.create_if_not_exists( - title: 'Authentication via Facebook', + title: 'Authentication via %s', name: 'auth_facebook', area: 'Security::ThirdPartyAuthentication', - description: "@T('Enables user authentication via Facebook. Register your app first at [Facebook Developer Site](https://developers.facebook.com/apps/)')", + description: 'Enables user authentication via %s. Register your app first at [%s](%s).', options: { form: [ { @@ -521,6 +531,12 @@ Setting.create_if_not_exists( }, ], }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_facebook_credentials'], + title_i18n: ['Facebook'], + description_i18n: ['Facebook', 'Facebook Developer Site', 'https://developers.facebook.com/apps/'] + }, state: false, frontend: true ) @@ -528,7 +544,7 @@ Setting.create_if_not_exists( Setting.create_if_not_exists( title: 'Facebook App Credentials', name: 'auth_facebook_credentials', - area: 'Security::ThirdPartyAuthentication', + area: 'Security::ThirdPartyAuthentication::Facebook', description: 'App credentials for Facebook.', options: { form: [ @@ -551,10 +567,10 @@ Setting.create_if_not_exists( ) Setting.create_if_not_exists( - title: 'Authentication via Google', + title: 'Authentication via %s', name: 'auth_google_oauth2', area: 'Security::ThirdPartyAuthentication', - description: 'Enables user authentication via Google. Register your app first at [Google API Console Site](https://console.developers.google.com/apis/credentials)', + description: 'Enables user authentication via %s. Register your app first at [%s](%s).', options: { form: [ { @@ -569,13 +585,19 @@ Setting.create_if_not_exists( }, ], }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_google_oauth2_credentials'], + title_i18n: ['Google'], + description_i18n: ['Google', 'Google API Console Site', 'https://console.developers.google.com/apis/credentials'] + }, state: false, frontend: true ) Setting.create_if_not_exists( title: 'Google App Credentials', name: 'auth_google_oauth2_credentials', - area: 'Security::ThirdPartyAuthentication', + area: 'Security::ThirdPartyAuthentication::Google', description: 'Enables user authentication via Google.', options: { form: [ @@ -598,10 +620,10 @@ Setting.create_if_not_exists( ) Setting.create_if_not_exists( - title: 'Authentication via LinkedIn', + title: 'Authentication via %s', name: 'auth_linkedin', area: 'Security::ThirdPartyAuthentication', - description: 'Enables user authentication via LinkedIn. Register your app first at [Linkedin Developer Site](https://www.linkedin.com/developer/apps)', + description: 'Enables user authentication via %s. Register your app first at [%s](%s).', options: { form: [ { @@ -616,13 +638,19 @@ Setting.create_if_not_exists( }, ], }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_linkedin_credentials'], + title_i18n: ['LinkedIn'], + description_i18n: ['LinkedIn', 'Linkedin Developer Site', 'https://www.linkedin.com/developer/apps'] + }, state: false, frontend: true ) Setting.create_if_not_exists( title: 'LinkedIn App Credentials', name: 'auth_linkedin_credentials', - area: 'Security::ThirdPartyAuthentication', + area: 'Security::ThirdPartyAuthentication::Linkedin', description: 'Enables user authentication via LinkedIn.', options: { form: [ @@ -644,6 +672,199 @@ Setting.create_if_not_exists( frontend: false ) +Setting.create_if_not_exists( + title: 'Authentication via %s', + name: 'auth_github', + area: 'Security::ThirdPartyAuthentication', + description: 'Enables user authentication via %s. Register your app first at [%s](%s).', + options: { + form: [ + { + display: '', + null: true, + name: 'auth_github', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_github_credentials'], + title_i18n: ['Github'], + description_i18n: ['Github', 'Github OAuth Applications', 'https://github.com/settings/applications'] + }, + state: false, + frontend: true +) +Setting.create_if_not_exists( + title: 'Github App Credentials', + name: 'auth_github_credentials', + area: 'Security::ThirdPartyAuthentication::Github', + description: 'Enables user authentication via Github.', + options: { + form: [ + { + display: 'App ID', + null: true, + name: 'app_id', + tag: 'input', + }, + { + display: 'App Secret', + null: true, + name: 'app_secret', + tag: 'input', + }, + ], + }, + state: {}, + frontend: false +) + +Setting.create_if_not_exists( + title: 'Authentication via %s', + name: 'auth_gitlab', + area: 'Security::ThirdPartyAuthentication', + description: 'Enables user authentication via %s. Register your app first at [%s](%s).', + options: { + form: [ + { + display: '', + null: true, + name: 'auth_gitlab', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_gitlab_credentials'], + title_i18n: ['Gitlab'], + description_i18n: ['Gitlab', 'Gitlab Applications', 'https://your-gitlab-host/admin/applications'] + }, + state: false, + frontend: true +) +Setting.create_if_not_exists( + title: 'Gitlab App Credentials', + name: 'auth_gitlab_credentials', + area: 'Security::ThirdPartyAuthentication::Gitlab', + description: 'Enables user authentication via Gitlab.', + options: { + form: [ + { + display: 'App ID', + null: true, + name: 'app_id', + tag: 'input', + }, + { + display: 'App Secret', + null: true, + name: 'app_secret', + tag: 'input', + }, + { + display: 'Site', + null: true, + name: 'site', + tag: 'input', + placeholder: 'https://gitlab.YOURDOMAIN.com', + }, + ], + }, + state: {}, + frontend: false +) + +Setting.create_if_not_exists( + title: 'Authentication via %s', + name: 'auth_oauth2', + area: 'Security::ThirdPartyAuthentication', + description: 'Enables user authentication via Generic OAuth2. Register your app first,', + options: { + form: [ + { + display: '', + null: true, + name: 'auth_oauth2', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_oauth2_credentials'], + title_i18n: ['Generic OAuth2'], + }, + state: false, + frontend: true +) +Setting.create_if_not_exists( + title: 'Generic OAuth2 App Credentials', + name: 'auth_oauth2_credentials', + area: 'Security::ThirdPartyAuthentication::GenericOAuth', + description: 'Enables user authentication via Generic OAuth2.', + options: { + form: [ + { + display: 'Name', + null: true, + name: 'name', + tag: 'input', + placeholder: 'Some Provider Name', + }, + { + display: 'App ID', + null: true, + name: 'app_id', + tag: 'input', + }, + { + display: 'App Secret', + null: true, + name: 'app_secret', + tag: 'input', + }, + { + display: 'Site', + null: true, + name: 'site', + tag: 'input', + placeholder: 'https://gitlab.YOURDOMAIN.com', + }, + { + display: 'authorize_url', + null: true, + name: 'authorize_url', + tag: 'input', + placeholder: '/oauth/authorize', + }, + { + display: 'token_url', + null: true, + name: 'token_url', + tag: 'input', + placeholder: '/oauth/token', + }, + ], + }, + state: {}, + frontend: false +) + Setting.create_if_not_exists( title: 'Minimal size', name: 'password_min_size', diff --git a/public/assets/tests/core.js b/public/assets/tests/core.js index 08f90dca0..0dd16a439 100644 --- a/public/assets/tests/core.js +++ b/public/assets/tests/core.js @@ -298,6 +298,15 @@ test( "i18n", function() { translated = App.i18n.translateContent('//*äöüß'); equal( translated, '<test&now>//*äöüß', 'de - //*äöüß' ); + translated = App.i18n.translateContent('some link [to what ever](http://lalala)'); + equal( translated, 'some link to what ever', 'de-de - link' ); + + translated = App.i18n.translateContent('some link [to what ever](%s)', 'http://lalala'); + equal( translated, 'some link to what ever', 'de-de - link' ); + + translated = App.i18n.translateContent('Enables user authentication via %s. Register your app first at [%s](%s).', 'XXX', 'YYY', 'http://lalala'); + equal( translated, 'Aktivieren der Benutzeranmeldung über XXX. Registriere Deine Anwendung zuerst über YYY.', 'en-us - link' ); + var time_local = new Date(); var offset = time_local.getTimezoneOffset(); var timestamp = App.i18n.translateTimestamp('2012-11-06T21:07:24Z', offset); @@ -347,6 +356,15 @@ test( "i18n", function() { translated = App.i18n.translateContent(''); equal( translated, '<test&now>', 'en-us - ' ); + translated = App.i18n.translateContent('some link [to what ever](http://lalala)'); + equal( translated, 'some link to what ever', 'en-us - link' ); + + translated = App.i18n.translateContent('some link [to what ever](%s)', 'http://lalala'); + equal( translated, 'some link to what ever', 'en-us - link' ); + + translated = App.i18n.translateContent('Enables user authentication via %s. Register your app first at [%s](%s).', 'XXX', 'YYY', 'http://lalala'); + equal( translated, 'Enables user authentication via XXX. Register your app first at YYY.', 'en-us - link' ); + timestamp = App.i18n.translateTimestamp('2012-11-06T21:07:24Z', offset); equal( timestamp, '11/06/2012 21:07', 'en - timestamp translated correctly' ); diff --git a/test/browser/setting_test.rb b/test/browser/setting_test.rb index f30fa6f32..b38748d36 100644 --- a/test/browser/setting_test.rb +++ b/test/browser/setting_test.rb @@ -12,123 +12,132 @@ class SettingTest < TestCase tasks_close_all() # make sure, that we have english frontend - click( css: 'a[href="#current_user"]' ) - click( css: 'a[href="#profile"]' ) - click( css: 'a[href="#profile/language"]' ) + click(css: 'a[href="#current_user"]') + click(css: 'a[href="#profile"]') + click(css: 'a[href="#profile/language"]') select( css: '.language_item [name="locale"]', value: 'English (United States)', ) - click( css: '.content button[type="submit"]' ) + click(css: '.content button[type="submit"]') sleep 2 # change settings - click( css: 'a[href="#manage"]' ) - click( css: 'a[href="#settings/security"]' ) - click( css: 'a[href="#third_party_auth"]' ) + click(css: 'a[href="#manage"]') + click(css: 'a[href="#settings/security"]') + click(css: 'a[href="#third_party_auth"]') sleep 2 + switch( + css: '#content .js-setting[data-name="auth_facebook"]', + type: 'off', + ) + + browser2 = browser_instance + location( + browser: browser2, + url: browser_url, + ) + watch_for( + browser: browser2, + css: 'body', + value: 'login', + ) + match_not( + browser: browser2, + css: 'body', + value: 'facebook', + ) # set yes - select( - css: '#auth_facebook select[name="auth_facebook"]', - value: 'yes', - ) - match( - css: '#auth_facebook select[name="auth_facebook"]', - value: 'yes', - ) - click( css: '#auth_facebook button[type=submit]' ) - watch_for( - css: '#notify', - value: 'update successful', - ) - sleep 4 - match( - css: '#auth_facebook select[name="auth_facebook"]', - value: 'yes', - ) - match_not( - css: '#auth_facebook select[name="auth_facebook"]', - value: 'no', - ) - - # set no - select( - css: '#auth_facebook select[name="auth_facebook"]', - value: 'no', - ) - click( css: '#auth_facebook button[type=submit]' ) - watch_for( - css: '#notify', - value: 'update successful', - ) - sleep 4 - match( - css: '#auth_facebook select[name="auth_facebook"]', - value: 'no', - ) - match_not( - css: '#auth_facebook select[name="auth_facebook"]', - value: 'yes', + switch( + css: '#content .js-setting[data-name="auth_facebook"]', + type: 'on', ) # set key and secret set( - css: '#auth_facebook_credentials input[name=app_id]', + css: '[data-name="auth_facebook_credentials"] input[name=app_id]', value: 'id_test1234äöüß', ) set( - css: '#auth_facebook_credentials input[name=app_secret]', + css: '[data-name="auth_facebook_credentials"] input[name=app_secret]', value: 'secret_test1234äöüß', ) - click( css: '#auth_facebook_credentials button[type=submit]' ) + click( css: '[data-name="auth_facebook_credentials"] button[type=submit]') watch_for( css: '#notify', value: 'update successful', ) sleep 4 match( - css: '#auth_facebook_credentials input[name=app_id]', + css: '[data-name="auth_facebook_credentials"] input[name=app_id]', value: 'id_test1234äöüß', ) match( - css: '#auth_facebook_credentials input[name=app_secret]', + css: '[data-name="auth_facebook_credentials"] input[name=app_secret]', value: 'secret_test1234äöüß', ) + # verify login page + sleep 2 + watch_for( + browser: browser2, + css: 'body', + value: 'facebook', + ) + # set key and secret again set( - css: '#auth_facebook_credentials input[name=app_id]', + css: '[data-name="auth_facebook_credentials"] input[name=app_id]', value: '---', ) set( - css: '#auth_facebook_credentials input[name=app_secret]', + css: '[data-name="auth_facebook_credentials"] input[name=app_secret]', value: '---', ) - click( css: '#auth_facebook_credentials button[type=submit]' ) + click(css: '[data-name="auth_facebook_credentials"] button[type=submit]') watch_for( css: '#notify', value: 'update successful', ) sleep 4 match( - css: '#auth_facebook_credentials input[name=app_id]', + css: '[data-name="auth_facebook_credentials"] input[name=app_id]', value: '---', ) match( - css: '#auth_facebook_credentials input[name=app_secret]', + css: '[data-name="auth_facebook_credentials"] input[name=app_secret]', value: '---', ) reload() + click(css: 'a[href="#settings/security"]') + click(css: 'a[href="#third_party_auth"]') watch_for( - css: '#auth_facebook_credentials input[name=app_id]', + css: '[data-name="auth_facebook_credentials"] input[name=app_id]', value: '---', ) watch_for( - css: '#auth_facebook_credentials input[name=app_secret]', + css: '[data-name="auth_facebook_credentials"] input[name=app_secret]', value: '---', ) + sleep 2 + switch( + css: '#content .js-setting[data-name="auth_facebook"]', + type: 'off', + ) + + sleep 2 + watch_for( + browser: browser2, + css: 'body', + value: 'login', + ) + match_not( + browser: browser2, + css: 'body', + value: 'facebook', + ) end end