From faafe0cc60798da9fb19eaf0d1db710cd1c47a7e Mon Sep 17 00:00:00 2001 From: Thorsten Eckel Date: Tue, 1 Nov 2016 17:16:04 +0100 Subject: [PATCH 1/4] Working on OAuth2 support with doorkeeper gem. --- Gemfile | 1 + Gemfile.lock | 3 + .../javascripts/app/models/application.coffee | 8 +- app/assets/javascripts/app/views/api.jst.eco | 5 +- app/controllers/application_controller.rb | 9 +- app/controllers/applications_controller.rb | 64 +++++++++ config/initializers/doorkeeper.rb | 112 ++++++++++++++++ config/locales/doorkeeper.en.yml | 124 ++++++++++++++++++ config/routes/doorkeeper.rb | 13 ++ ...20161101131409_create_doorkeeper_tables.rb | 68 ++++++++++ 10 files changed, 394 insertions(+), 13 deletions(-) create mode 100644 app/controllers/applications_controller.rb create mode 100644 config/initializers/doorkeeper.rb create mode 100644 config/locales/doorkeeper.en.yml create mode 100644 config/routes/doorkeeper.rb create mode 100644 db/migrate/20161101131409_create_doorkeeper_tables.rb diff --git a/Gemfile b/Gemfile index 901595f59..e9bfa83c7 100644 --- a/Gemfile +++ b/Gemfile @@ -28,6 +28,7 @@ end gem 'autoprefixer-rails' +gem 'doorkeeper' gem 'oauth2' gem 'omniauth' diff --git a/Gemfile.lock b/Gemfile.lock index 7c3f059c6..06d4ccbb8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,6 +82,8 @@ GEM docile (1.1.5) domain_name (0.5.20160826) unf (>= 0.0.5, < 1.0.0) + doorkeeper (4.2.0) + railties (>= 4.2) eco (1.0.0) coffee-script eco-source @@ -350,6 +352,7 @@ DEPENDENCIES daemons delayed_job_active_record diffy + doorkeeper eco em-websocket email_verifier diff --git a/app/assets/javascripts/app/models/application.coffee b/app/assets/javascripts/app/models/application.coffee index 85fd37030..4bad86d8b 100644 --- a/app/assets/javascripts/app/models/application.coffee +++ b/app/assets/javascripts/app/models/application.coffee @@ -1,17 +1,17 @@ class App.Application extends App.Model - @configure 'Application', 'name', 'scopes', 'redirect_uri' + @configure 'Application', 'name', 'redirect_uri', 'uid', 'secret' @extend Spine.Model.Ajax @url: @apiPath + '/applications' @configure_attributes = [ { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false }, { name: 'redirect_uri', display: 'Redirect URI', tag: 'textarea', limit: 250, null: false, note: 'Use one line per URI' }, - { name: 'scopes', display: 'Scopes', tag: 'input', note: 'Scopes define the access for' }, - { name: 'clients', display: 'Clients', tag: 'input', readonly: 1 }, + { name: 'uid', display: 'Application ID', tag: 'input', type: 'text', null: true, readonly: 1 }, + { name: 'secret', display: 'Application secret', tag: 'input', type: 'text', null: true }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, ] @configure_overview = [ - 'name', 'scopes', 'clients' + 'name', 'uid' ] @configure_delete = true diff --git a/app/assets/javascripts/app/views/api.jst.eco b/app/assets/javascripts/app/views/api.jst.eco index ddffb1fd9..86f412824 100644 --- a/app/assets/javascripts/app/views/api.jst.eco +++ b/app/assets/javascripts/app/views/api.jst.eco @@ -59,7 +59,7 @@ OAuth URLs are: - @@ -69,9 +69,6 @@ OAuth URLs are: -
<%- @T('Title') %> + <%- @T('Action') %> <%- @T('URL') %>
<%- @T('Getting an Access Token') %> <%= @C('http_type') %>://<%= @C('fqdn') %>/oauth/token -
<%- @T('Revoking Access') %> - <%= @C('http_type') %>://<%= @C('fqdn') %>/oauth/applications
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b732d50aa..3c84b465c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -301,7 +301,6 @@ class ApplicationController < ActionController::Base return authentication_check_prerequesits(user, 'token_auth', auth_param) if user end -=begin # check oauth2 token based authentication token = Doorkeeper::OAuth::Token.from_bearer_authorization(request) if token @@ -314,14 +313,14 @@ class ApplicationController < ActionController::Base raise Exceptions::NotAuthorized, 'OAuth2 token is expired!' end - if access_token.scopes.empty? - raise Exceptions::NotAuthorized, 'OAuth2 scope missing for token!' - end + # if access_token.scopes.empty? + # raise Exceptions::NotAuthorized, 'OAuth2 scope missing for token!' + # end user = User.find(access_token.resource_owner_id) return authentication_check_prerequesits(user, 'token_auth', auth_param) if user end -=end + false end diff --git a/app/controllers/applications_controller.rb b/app/controllers/applications_controller.rb new file mode 100644 index 000000000..00da58b5e --- /dev/null +++ b/app/controllers/applications_controller.rb @@ -0,0 +1,64 @@ +# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ + +class ApplicationsController < ApplicationController + before_action { authentication_check(permission: 'admin.api') } + + def index + all = Doorkeeper::Application.all + if params[:full] + assets = {} + item_ids = [] + all.each { |item| + item_ids.push item.id + if !assets[:Application] + assets[:Application] = {} + end + assets[:Application][item.id] = item.attributes + } + render json: { + record_ids: item_ids, + assets: assets, + }, status: :ok + return + end + + render json: all, status: :ok + end + + def show + application = Doorkeeper::Application.find(params[:id]) + render json: application, status: :ok + end + + def create + application = Doorkeeper::Application.new(clean_params) + application.save! + render json: application, status: :ok + end + + def update + application = Doorkeeper::Application.find(params[:id]) + application.update_attributes!(clean_params) + render json: application, status: :ok + end + + def destroy + application = Doorkeeper::Application.find(params[:id]) + application.destroy! + render json: {}, status: :ok + end + + private + + def clean_params + params_data = params.permit! #.to_h + params_data.delete('application') + params_data.delete('action') + params_data.delete('controller') + params_data.delete('id') + params_data.delete('uid') + params_data.delete('secret') + params_data.delete('created_at') + params_data + end +end diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb new file mode 100644 index 000000000..4eeb378e4 --- /dev/null +++ b/config/initializers/doorkeeper.rb @@ -0,0 +1,112 @@ +Doorkeeper.configure do + # Change the ORM that doorkeeper will use (needs plugins) + orm :active_record + + # This block will be called to check whether the resource owner is authenticated or not. + resource_owner_authenticator do + # fail "Please configure doorkeeper resource_owner_authenticator block located in #{__FILE__}" + # Put your resource owner authentication logic here. + # Example implementation: + User.find_by_id(session[:user_id]) || redirect_to(new_user_session_url) + end + + # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below. + # admin_authenticator do + # # Put your admin authentication logic here. + # # Example implementation: + # Admin.find_by_id(session[:admin_id]) || redirect_to(new_admin_session_url) + # end + + # Authorization Code expiration time (default 10 minutes). + # authorization_code_expires_in 10.minutes + + # Access token expiration time (default 2 hours). + # If you want to disable expiration, set this to nil. + # access_token_expires_in 2.hours + + # Assign a custom TTL for implicit grants. + # custom_access_token_expires_in do |oauth_client| + # oauth_client.application.additional_settings.implicit_oauth_expiration + # end + + # Use a custom class for generating the access token. + # https://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator + # access_token_generator '::Doorkeeper::JWT' + + # The controller Doorkeeper::ApplicationController inherits from. + # Defaults to ActionController::Base. + # https://github.com/doorkeeper-gem/doorkeeper#custom-base-controller + # base_controller 'ApplicationController' + + # Reuse access token for the same resource owner within an application (disabled by default) + # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383 + # reuse_access_token + + # Issue access tokens with refresh token (disabled by default) + # use_refresh_token + + # Provide support for an owner to be assigned to each registered application (disabled by default) + # Optional parameter confirmation: true (default false) if you want to enforce ownership of + # a registered application + # Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support + # enable_application_owner confirmation: false + + # Define access token scopes for your provider + # For more information go to + # https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes + # default_scopes :public + # optional_scopes :write, :update + + # Change the way client credentials are retrieved from the request object. + # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then + # falls back to the `:client_id` and `:client_secret` params from the `params` object. + # Check out the wiki for more information on customization + # client_credentials :from_basic, :from_params + + # Change the way access token is authenticated from the request object. + # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then + # falls back to the `:access_token` or `:bearer_token` params from the `params` object. + # Check out the wiki for more information on customization + # access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param + + # Change the native redirect uri for client apps + # When clients register with the following redirect uri, they won't be redirected to any server and the authorization code will be displayed within the provider + # The value can be any string. Use nil to disable this feature. When disabled, clients must provide a valid URL + # (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi) + # + # native_redirect_uri 'urn:ietf:wg:oauth:2.0:oob' + + # Forces the usage of the HTTPS protocol in non-native redirect uris (enabled + # by default in non-development environments). OAuth2 delegates security in + # communication to the HTTPS protocol so it is wise to keep this enabled. + # + # force_ssl_in_redirect_uri !Rails.env.development? + + # Specify what grant flows are enabled in array of Strings. The valid + # strings and the flows they enable are: + # + # "authorization_code" => Authorization Code Grant Flow + # "implicit" => Implicit Grant Flow + # "password" => Resource Owner Password Credentials Grant Flow + # "client_credentials" => Client Credentials Grant Flow + # + # If not specified, Doorkeeper enables authorization_code and + # client_credentials. + # + # implicit and password grant flows have risks that you should understand + # before enabling: + # http://tools.ietf.org/html/rfc6819#section-4.4.2 + # http://tools.ietf.org/html/rfc6819#section-4.4.3 + # + # grant_flows %w(authorization_code client_credentials) + + # Under some circumstances you might want to have applications auto-approved, + # so that the user skips the authorization step. + # For example if dealing with a trusted application. + # skip_authorization do |resource_owner, client| + # client.superapp? or resource_owner.admin? + # end + + # WWW-Authenticate Realm (default "Doorkeeper"). + # realm "Doorkeeper" +end diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml new file mode 100644 index 000000000..e67b778be --- /dev/null +++ b/config/locales/doorkeeper.en.yml @@ -0,0 +1,124 @@ +en: + activerecord: + attributes: + doorkeeper/application: + name: 'Name' + redirect_uri: 'Redirect URI' + errors: + models: + doorkeeper/application: + attributes: + redirect_uri: + fragment_present: 'cannot contain a fragment.' + invalid_uri: 'must be a valid URI.' + relative_uri: 'must be an absolute URI.' + secured_uri: 'must be an HTTPS/SSL URI.' + + doorkeeper: + applications: + confirmations: + destroy: 'Are you sure?' + buttons: + edit: 'Edit' + destroy: 'Destroy' + submit: 'Submit' + cancel: 'Cancel' + authorize: 'Authorize' + form: + error: 'Whoops! Check your form for possible errors' + help: + redirect_uri: 'Use one line per URI' + native_redirect_uri: 'Use %{native_redirect_uri} for local tests' + scopes: 'Separate scopes with spaces. Leave blank to use the default scopes.' + edit: + title: 'Edit application' + index: + title: 'Your applications' + new: 'New Application' + name: 'Name' + callback_url: 'Callback URL' + new: + title: 'New Application' + show: + title: 'Application: %{name}' + application_id: 'Application Id' + secret: 'Secret' + scopes: 'Scopes' + callback_urls: 'Callback urls' + actions: 'Actions' + + authorizations: + buttons: + authorize: 'Authorize' + deny: 'Deny' + error: + title: 'An error has occurred' + new: + title: 'Authorization required' + prompt: 'Authorize %{client_name} to use your account?' + able_to: 'This application will be able to' + show: + title: 'Authorization code' + + authorized_applications: + confirmations: + revoke: 'Are you sure?' + buttons: + revoke: 'Revoke' + index: + title: 'Your authorized applications' + application: 'Application' + created_at: 'Created At' + date_format: '%Y-%m-%d %H:%M:%S' + + errors: + messages: + # Common error messages + invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.' + invalid_redirect_uri: 'The redirect uri included is not valid.' + unauthorized_client: 'The client is not authorized to perform this request using this method.' + access_denied: 'The resource owner or authorization server denied the request.' + invalid_scope: 'The requested scope is invalid, unknown, or malformed.' + server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.' + temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.' + + #configuration error messages + credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.' + resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.' + + # Access grant errors + unsupported_response_type: 'The authorization server does not support this response type.' + + # Access token errors + invalid_client: 'Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.' + invalid_grant: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.' + unsupported_grant_type: 'The authorization grant type is not supported by the authorization server.' + + # Password Access token errors + invalid_resource_owner: 'The provided resource owner credentials are not valid, or resource owner cannot be found' + + invalid_token: + revoked: "The access token was revoked" + expired: "The access token expired" + unknown: "The access token is invalid" + + flash: + applications: + create: + notice: 'Application created.' + destroy: + notice: 'Application deleted.' + update: + notice: 'Application updated.' + authorized_applications: + destroy: + notice: 'Application revoked.' + + layouts: + admin: + nav: + oauth2_provider: 'OAuth2 Provider' + applications: 'Applications' + home: 'Home' + application: + title: 'OAuth authorization required' diff --git a/config/routes/doorkeeper.rb b/config/routes/doorkeeper.rb new file mode 100644 index 000000000..9acd9e820 --- /dev/null +++ b/config/routes/doorkeeper.rb @@ -0,0 +1,13 @@ +Zammad::Application.routes.draw do + api_path = Rails.configuration.api_path + + match api_path + '/applications', to: 'applications#index', via: :get + match api_path + '/applications/:id', to: 'applications#show', via: :get + match api_path + '/applications', to: 'applications#create', via: :post + match api_path + '/applications/:id', to: 'applications#update', via: :put + + # oauth2 provider routes + use_doorkeeper do + skip_controllers :applications, :authorized_applications + end +end diff --git a/db/migrate/20161101131409_create_doorkeeper_tables.rb b/db/migrate/20161101131409_create_doorkeeper_tables.rb new file mode 100644 index 000000000..da7c84353 --- /dev/null +++ b/db/migrate/20161101131409_create_doorkeeper_tables.rb @@ -0,0 +1,68 @@ +class CreateDoorkeeperTables < ActiveRecord::Migration + def change + create_table :oauth_applications do |t| + t.string :name, null: false + t.string :uid, null: false + t.string :secret, null: false + t.text :redirect_uri, null: false + t.string :scopes, null: false, default: '' + t.timestamps null: false + end + + add_index :oauth_applications, :uid, unique: true + + create_table :oauth_access_grants do |t| + t.integer :resource_owner_id, null: false + t.references :application, null: false + t.string :token, null: false + t.integer :expires_in, null: false + t.text :redirect_uri, null: false + t.datetime :created_at, null: false + t.datetime :revoked_at + t.string :scopes + end + + add_index :oauth_access_grants, :token, unique: true + add_foreign_key( + :oauth_access_grants, + :oauth_applications, + column: :application_id + ) + + create_table :oauth_access_tokens do |t| + t.integer :resource_owner_id + t.references :application + + # If you use a custom token generator you may need to change this column + # from string to text, so that it accepts tokens larger than 255 + # characters. More info on custom token generators in: + # https://github.com/doorkeeper-gem/doorkeeper/tree/v3.0.0.rc1#custom-access-token-generator + # + # t.text :token, null: false + t.string :token, null: false + + t.string :refresh_token + t.integer :expires_in + t.datetime :revoked_at + t.datetime :created_at, null: false + t.string :scopes + + # If there is a previous_refresh_token column, + # refresh tokens will be revoked after a related access token is used. + # If there is no previous_refresh_token column, + # previous tokens are revoked as soon as a new access token is created. + # Comment out this line if you'd rather have refresh tokens + # instantly revoked. + t.string :previous_refresh_token, null: false, default: '' + end + + add_index :oauth_access_tokens, :token, unique: true + add_index :oauth_access_tokens, :resource_owner_id + add_index :oauth_access_tokens, :refresh_token, unique: true + add_foreign_key( + :oauth_access_tokens, + :oauth_applications, + column: :application_id + ) + end +end From aa29a75841fbe7abab16fc4f6b4c4b89d02cd528 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sun, 13 Nov 2016 19:33:12 +0100 Subject: [PATCH 2/4] Fixed issue#301 - Using #{article.body} in trigger gives html. --- app/models/ticket.rb | 7 +- app/models/ticket/article.rb | 43 +++ app/views/mailer/password_change/de.html.erb | 8 +- app/views/mailer/password_change/en.html.erb | 8 +- app/views/mailer/password_reset/de.html.erb | 10 +- app/views/mailer/password_reset/en.html.erb | 10 +- app/views/mailer/signup/de.html.erb | 10 +- app/views/mailer/signup/en.html.erb | 10 +- app/views/mailer/test_ticket/de.html.erb | 4 +- app/views/mailer/test_ticket/en.html.erb | 4 +- app/views/mailer/ticket_create/de.html.erb | 18 +- app/views/mailer/ticket_create/en.html.erb | 18 +- .../mailer/ticket_escalation/de.html.erb | 12 +- .../mailer/ticket_escalation/en.html.erb | 12 +- .../ticket_escalation_warning/de.html.erb | 12 +- .../ticket_escalation_warning/en.html.erb | 12 +- .../ticket_reminder_reached/de.html.erb | 12 +- .../ticket_reminder_reached/en.html.erb | 12 +- app/views/mailer/ticket_update/de.html.erb | 14 +- app/views/mailer/ticket_update/en.html.erb | 14 +- app/views/mailer/user_device_new/de.html.erb | 16 +- app/views/mailer/user_device_new/en.html.erb | 16 +- .../user_device_new_location/de.html.erb | 16 +- .../user_device_new_location/en.html.erb | 16 +- app/views/mailer/user_invite/de.html.erb | 12 +- app/views/mailer/user_invite/en.html.erb | 12 +- app/views/slack/ticket_create/en.md.erb | 12 +- app/views/slack/ticket_escalation/en.md.erb | 8 +- .../slack/ticket_escalation_warning/en.md.erb | 8 +- .../slack/ticket_reminder_reached/en.md.erb | 8 +- app/views/slack/ticket_update/en.md.erb | 6 +- lib/notification_factory/mailer.rb | 16 +- lib/notification_factory/renderer.rb | 54 ++-- lib/notification_factory/slack.rb | 6 +- lib/notification_factory/template.rb | 38 ++- .../notification_factory_renderer_test.rb | 286 ++++++++++++------ ...otification_factory_slack_template_test.rb | 4 +- .../notification_factory_template_test.rb | 51 +++- test/unit/ticket_trigger_test.rb | 76 ++++- 39 files changed, 592 insertions(+), 319 deletions(-) diff --git a/app/models/ticket.rb b/app/models/ticket.rb index e76413c5f..220935ae2 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -738,22 +738,21 @@ perform changes on ticket } # get subject - value['subject'].gsub!(/\#\{config\.(.+?)\}/, '<%= c "\\1", false %>') - value['subject'].gsub!(/\#\{(.+?)\}/, '<%= d "\\1", false %>') subject = NotificationFactory::Mailer.template( templateInline: value['subject'], locale: 'en-en', objects: objects, + quote: false, ) subject = subject_build(subject) - value['body'].gsub!(/\#\{config\.(.+?)\}/, '<%= c "\\1", true %>') - value['body'].gsub!(/\#\{(.+?)\}/, '<%= d "\\1", true %>') body = NotificationFactory::Mailer.template( templateInline: value['body'], locale: 'en-en', objects: objects, + quote: true, ) + Ticket::Article.create( ticket_id: id, to: recipient_string, diff --git a/app/models/ticket/article.rb b/app/models/ticket/article.rb index 240c0ca96..a152563e5 100644 --- a/app/models/ticket/article.rb +++ b/app/models/ticket/article.rb @@ -121,6 +121,49 @@ returns Ticket::Article.where('ticket_id = ? AND sender_id NOT IN (?)', ticket_id, sender.id).order('created_at DESC').first end +=begin + +get body as html + + article = Ticket::Article.find(123) + article.body_as_html + +=end + + def body_as_html + return '' if !body + return body if content_type && content_type =~ %r{text/html}i + body.text2html + end + +=begin + +get body as text + + article = Ticket::Article.find(123) + article.body_as_text + +=end + + def body_as_text + return '' if !body + return body if !content_type || content_type.empty? || content_type =~ %r{text/plain}i + body.html2text + end + +=begin + +get body as text with quote sign "> " at the beginning of each line + + article = Ticket::Article.find(123) + article.body_as_text + +=end + + def body_as_text_with_quote + body_as_text.word_wrap.message_quote + end + private # strip not wanted chars diff --git a/app/views/mailer/password_change/de.html.erb b/app/views/mailer/password_change/de.html.erb index 6a433bc4e..24cf7e9b8 100644 --- a/app/views/mailer/password_change/de.html.erb +++ b/app/views/mailer/password_change/de.html.erb @@ -1,9 +1,9 @@ -Dein <%= c 'product_name' %> Passwort wurde geändert +Dein #{config.product_name} Passwort wurde geändert -
Hallo <%= d 'user.firstname' %>,
+
Hallo #{user.firstname},

-
das Passwort für Dein <%= c 'product_name' %> Account <%= d 'user.login' %> wurde kürzlich geändert.
+
das Passwort für Dein #{config.product_name} Account #{user.login} wurde kürzlich geändert.

Diese Aktivität ist Dir nicht bekannt? In diesen Fall kontaktiere Deinen System-Administrator.

-
Dein <%= c 'product_name' %> Team
+
Dein #{config.product_name} Team
diff --git a/app/views/mailer/password_change/en.html.erb b/app/views/mailer/password_change/en.html.erb index 81eb723b5..80236ad12 100644 --- a/app/views/mailer/password_change/en.html.erb +++ b/app/views/mailer/password_change/en.html.erb @@ -1,9 +1,9 @@ -Your <%= c 'product_name' %> password has been changed +Your #{product_name} password has been changed -

Hi <%= d 'user.firstname' %>,

+

Hi #{user.firstname},


-

The password for your <%= c 'product_name' %> account <%= d 'user.login' %> has been changed recently.

+

The password for your #{product_name} account #{user.login} has been changed recently.


This activity is not known to you? If not, contact your system administrator.


-

Your <%= c 'product_name' %> Team

+

Your #{product_name} Team

diff --git a/app/views/mailer/password_reset/de.html.erb b/app/views/mailer/password_reset/de.html.erb index 13d2f4f7b..fd03cff03 100644 --- a/app/views/mailer/password_reset/de.html.erb +++ b/app/views/mailer/password_reset/de.html.erb @@ -1,15 +1,15 @@ -Zurücksetzen Deines <%= c 'product_name' %> Passworts +Zurücksetzen Deines #{config.product_name} Passworts -
Hallo <%= d 'user.firstname' %>,
+
Hallo #{user.firstname},

-
wir haben eine Anfrage zum Zurücksetzen des Passworts für <%= c 'product_name' %> Account <%= d 'user.login' %> erhalten.
+
wir haben eine Anfrage zum Zurücksetzen des Passworts für #{config.product_name} Account #{user.login} erhalten.

Wenn Sie Ihr Passwort zurückzusetzen wollen, klicken Sie auf den unten stehenden Link (oder kopieren Sie die URL in den Browser einfügen):

-
<%= c 'http_type' %>://<%= c 'fqdn' %>/#password_reset_verify/<%= d 'token.name' %>
+
#{config.http_type}://#{config.fqdn}/#password_reset_verify/#{token.name}

Dieser Link führt Sie zu einer Seite, auf der Sie Ihr Passwort ändern können.

Wenn Sie Ihr Passwort nicht zurücksetzen wollen, ignorieren Sie diese Meldung. Das Passwort bleibt unverändert.

-
Dein <%= c 'product_name' %> Team
+
Dein #{config.product_name} Team
diff --git a/app/views/mailer/password_reset/en.html.erb b/app/views/mailer/password_reset/en.html.erb index c673eead8..3a993b785 100644 --- a/app/views/mailer/password_reset/en.html.erb +++ b/app/views/mailer/password_reset/en.html.erb @@ -1,15 +1,15 @@ -Reset your <%= c 'product_name' %> password +Reset your #{config.product_name} password -
Hi <%= d 'user.firstname' %>,
+
Hi #{user.firstname},

-
We received a request to reset the password for your <%= c 'product_name' %> account <%= d 'user.login' %>.
+
We received a request to reset the password for your #{config.product_name} account #{user.login}.

If you want to reset your password, click on the link below (or copy and paste the URL into your browser):

-
<%= c 'http_type' %>://<%= c 'fqdn' %>/#password_reset_verify/<%= d 'token.name' %>
+
#{config.http_type}://#{config.fqdn}/#password_reset_verify/#{token.name}

This link takes you to a page where you can change your password.

If you don't want to reset your password, please ignore this message. Your password will not be reseted.

-
Your <%= c 'product_name' %> Team
+
Your #{config.product_name} Team
diff --git a/app/views/mailer/signup/de.html.erb b/app/views/mailer/signup/de.html.erb index 9c76b8dca..d5a5ecf6c 100644 --- a/app/views/mailer/signup/de.html.erb +++ b/app/views/mailer/signup/de.html.erb @@ -1,9 +1,9 @@ -Bestätigung des <%= c 'product_name' %> Accounts, <%= d 'user.firstname' %> <%= d 'user.lastname' %> +Bestätigung des #{config.product_name} Accounts, #{user.firstname} #{user.lastname} -
Hallo <%= d 'user.firstname' %>,
+
Hallo #{user.firstname},

-
bestätige Deine E-Mail-Adresse um Deine Registrierung bei <%= c 'product_name' %> abzuschließen. Es ist einfach - klick einfach auf den Link unten.
+
bestätige Deine E-Mail-Adresse um Deine Registrierung bei #{config.product_name} abzuschließen. Es ist einfach - klick einfach auf den Link unten.

-
<%= c 'http_type' %>://<%= c 'fqdn' %>/#email_verify/<%= d 'token.name' %>
+
#{config.http_type}://#{config.fqdn}/#email_verify/#{token.name}

-
Dein <%= c 'product_name' %> Team
+
Dein #{config.product_name} Team
diff --git a/app/views/mailer/signup/en.html.erb b/app/views/mailer/signup/en.html.erb index 168f1d067..f03b586a0 100644 --- a/app/views/mailer/signup/en.html.erb +++ b/app/views/mailer/signup/en.html.erb @@ -1,9 +1,9 @@ -Confirm your <%= c 'product_name' %> account, <%= d 'user.firstname' %> <%= d 'user.lastname' %> +Confirm your #{config.product_name} account, #{user.firstname} #{user.lastname} -
Hi <%= d 'user.firstname' %>,
+
Hi #{user.firstname},

-
Confirm your email address to complete your <%= c 'product_name' %> account. It's easy, just click the link below.
+
Confirm your email address to complete your #{config.product_name} account. It's easy, just click the link below.

-
<%= c 'http_type' %>://<%= c 'fqdn' %>/#email_verify/<%= d 'token.name' %>
+
#{config.http_type}://#{config.fqdn}/#email_verify/#{token.name}

-
Your <%= c 'product_name' %> Team
+
Your #{config.product_name} Team
diff --git a/app/views/mailer/test_ticket/de.html.erb b/app/views/mailer/test_ticket/de.html.erb index cf4fe7d43..4e36a4b39 100644 --- a/app/views/mailer/test_ticket/de.html.erb +++ b/app/views/mailer/test_ticket/de.html.erb @@ -1,9 +1,9 @@ Test Ticket! -
Hallo <%= d 'agent.firstname' %>,
+
Hallo #{agent.firstname},

dies ist ein Test Ticket. Ich bin ein Kunde und benötige Hilfe! :)

-
<%= d 'customer.fullname' %>
+
#{customer.fullname}

Das Zammad Projekt
diff --git a/app/views/mailer/test_ticket/en.html.erb b/app/views/mailer/test_ticket/en.html.erb index fd1d00929..557c902eb 100644 --- a/app/views/mailer/test_ticket/en.html.erb +++ b/app/views/mailer/test_ticket/en.html.erb @@ -1,9 +1,9 @@ Test Ticket! -
Dear <%= d 'agent.firstname' %>,
+
Dear #{agent.firstname},

This is a test ticket. I'm a customer and I need some help! :)

-
<%= d 'customer.fullname' %>
+
#{customer.fullname}

The Zammad Project
diff --git a/app/views/mailer/ticket_create/de.html.erb b/app/views/mailer/ticket_create/de.html.erb index b1db98e71..bc666a485 100644 --- a/app/views/mailer/ticket_create/de.html.erb +++ b/app/views/mailer/ticket_create/de.html.erb @@ -1,24 +1,24 @@ -Neues Ticket (<%= d 'ticket.title' %>) +Neues Ticket (#{ticket.title}) -
Hallo <%= d 'recipient.firstname' %>,
+
Hallo #{recipient.firstname},

-
es wurde ein neues Ticket (<%= d 'ticket.title' %>) von "<%= d 'current_user.longname' %>" erstellt.
+
es wurde ein neues Ticket (#{ticket.title}) von "#{current_user.longname}" erstellt.

-<%= t 'Group' %>: <%= d 'ticket.group.name' %>
-<%= t 'Owner' %>: <%= d 'ticket.owner.fullname' %>
-<%= t 'State' %>: <%= t d 'ticket.state.name' %>
+#{t('Group')}: #{ticket.group.name}
+#{t('Owner')}: #{ticket.owner.fullname}
+#{t('State')}: #{t(ticket.state.name)}

<% if @objects[:article] %>
- <%= t 'Information' %>: + #{t('Information')}:
- <%= a_html 'article' %> + #{article.body_as_html}
<% end %>
- <%= t 'View this in Zammad' %> + #{t('View this in Zammad')}
diff --git a/app/views/mailer/ticket_create/en.html.erb b/app/views/mailer/ticket_create/en.html.erb index 408fe77b7..009784232 100644 --- a/app/views/mailer/ticket_create/en.html.erb +++ b/app/views/mailer/ticket_create/en.html.erb @@ -1,24 +1,24 @@ -New Ticket (<%= d 'ticket.title' %>) +New Ticket (#{ticket.title}) -
Hi <%= d 'recipient.firstname' %>,
+
Hi #{recipient.firstname},

-
A new ticket (<%= d 'ticket.title' %>) has been created by "<%= d 'current_user.longname' %>".
+
A new ticket (#{ticket.title}) has been created by "#{current_user.longname}".

-<%= t 'Group' %>: <%= d 'ticket.group.name' %>
-<%= t 'Owner' %>: <%= d 'ticket.owner.fullname' %>
-<%= t 'State' %>: <%= t d 'ticket.state.name' %>
+#{t('Group')}: #{ticket.group.name}
+#{t('Owner')}: #{ticket.owner.fullname}
+#{t('State')}: #{t(ticket.state.name)}

<% if @objects[:article] %>
- <%= t 'Information' %>: + #{t('Information')}:
- <%= a_html 'article' %> + #{article.body_as_html}
<% end %>
- <%= t 'View this in Zammad' %> + #{t('View this in Zammad')}
diff --git a/app/views/mailer/ticket_escalation/de.html.erb b/app/views/mailer/ticket_escalation/de.html.erb index 86ba3da5d..ea71cd93d 100644 --- a/app/views/mailer/ticket_escalation/de.html.erb +++ b/app/views/mailer/ticket_escalation/de.html.erb @@ -1,18 +1,18 @@ -Ticket ist eskaliert (<%= d 'ticket.title' %>) +Ticket ist eskaliert (#{ticket.title}) -
Hallo <%= d 'recipient.firstname' %>,
+
Hallo #{recipient.firstname},

-
Ticket (<%= d 'ticket.title' %>) von "<%= d 'ticket.customer.longname' %>" ist seit "<%= d 'ticket.escalation_at' %>" eskaliert!
+
Ticket (#{ticket.title}) von "#{ticket.customer.longname}" ist seit "#{ticket.escalation_at}" eskaliert!

<% if @objects[:article] %>
- <%= t 'Information' %>: + #{t('Information')}:
- <%= a_html 'article' %> + #{article.body_as_html}
<% end %>
- <%= t 'View this in Zammad' %> + #{t('View this in Zammad')}
diff --git a/app/views/mailer/ticket_escalation/en.html.erb b/app/views/mailer/ticket_escalation/en.html.erb index 078a55262..bb779fba0 100644 --- a/app/views/mailer/ticket_escalation/en.html.erb +++ b/app/views/mailer/ticket_escalation/en.html.erb @@ -1,18 +1,18 @@ -Ticket is escalated (<%= d 'ticket.title' %>) +Ticket is escalated (#{ticket.title}) -
Hi <%= d 'recipient.firstname' %>,
+
Hi #{recipient.firstname},

-
A ticket (<%= d 'ticket.title' %>) from "<%= d 'ticket.customer.longname' %>" is escalated since "<%= d 'ticket.escalation_at' %>"!
+
A ticket (#{ticket.title}) from "#{ticket.customer.longname}" is escalated since "#{ticket.escalation_at}"!

<% if @objects[:article] %>
- <%= t 'Information' %>: + #{t('Information')}:
- <%= a_html 'article' %> + #{article.body_as_html}
<% end %>
- <%= t 'View this in Zammad' %> + #{t('View this in Zammad')}
diff --git a/app/views/mailer/ticket_escalation_warning/de.html.erb b/app/views/mailer/ticket_escalation_warning/de.html.erb index d78d8fe48..6a20eb8a7 100644 --- a/app/views/mailer/ticket_escalation_warning/de.html.erb +++ b/app/views/mailer/ticket_escalation_warning/de.html.erb @@ -1,18 +1,18 @@ -Ticket wird eskalieren (<%= d 'ticket.title' %>) +Ticket wird eskalieren (#{ticket.title}) -
Hallo <%= d 'recipient.firstname' %>,
+
Hallo #{recipient.firstname},

-
Ticket (<%= d 'ticket.title' %>) von "<%= d 'ticket.customer.longname' %>" wird um "<%= d 'ticket.escalation_at' %>" eskalieren!
+
Ticket (#{ticket.title}) von "#{ticket.customer.longname}" wird um "#{ticket.escalation_at}" eskalieren!

<% if @objects[:article] %>
- <%= t 'Information' %>: + #{t('Information')}:
- <%= a_html 'article' %> + #{article.body_as_html}
<% end %>
- <%= t 'View this in Zammad' %> + #{t('View this in Zammad')}
diff --git a/app/views/mailer/ticket_escalation_warning/en.html.erb b/app/views/mailer/ticket_escalation_warning/en.html.erb index 0952efc91..3750ebe39 100644 --- a/app/views/mailer/ticket_escalation_warning/en.html.erb +++ b/app/views/mailer/ticket_escalation_warning/en.html.erb @@ -1,18 +1,18 @@ -Ticket will escalate (<%= d 'ticket.title' %>) +Ticket will escalate (#{ticket.title}) -
Hi <%= d 'recipient.firstname' %>,
+
Hi #{recipient.firstname},

-
A ticket (<%= d 'ticket.title' %>) from "<%= d 'ticket.customer.longname' %>" will escalate at "<%= d 'ticket.escalation_at' %>"!
+
A ticket (#{ticket.title}) from "#{ticket.customer.longname}" will escalate at "#{ticket.escalation_at}"!

<% if @objects[:article] %>
- <%= t 'Information' %>: + #{t('Information')}:
- <%= a_html 'article' %> + #{article.body_as_html}
<% end %>
- <%= t 'View this in Zammad' %> + #{t('View this in Zammad')}
diff --git a/app/views/mailer/ticket_reminder_reached/de.html.erb b/app/views/mailer/ticket_reminder_reached/de.html.erb index 275660959..1e4e1785f 100644 --- a/app/views/mailer/ticket_reminder_reached/de.html.erb +++ b/app/views/mailer/ticket_reminder_reached/de.html.erb @@ -1,18 +1,18 @@ -Warten auf Erinnerung erreicht! (<%= d 'ticket.title' %>) +Warten auf Erinnerung erreicht! (#{ticket.title}) -
Hallo <%= d 'recipient.firstname' %>,
+
Hallo #{recipient.firstname},

-
dieses Ticket benötigt Deine Aufmerksamkeit, warten auf Erinnerung für (<%= d 'ticket.title' %>) mit dem Kunden "<%= d 'ticket.customer.longname' %>" ist erreicht.
+
dieses Ticket benötigt Deine Aufmerksamkeit, warten auf Erinnerung für (#{ticket.title}) mit dem Kunden "#{ticket.customer.longname}" ist erreicht.

<% if @objects[:article] %>
- <%= t 'Information' %>: + #{t('Information')}:
- <%= a_html 'article' %> + #{article.body_as_html}
<% end %>
- <%= t 'View this in Zammad' %> + #{t('View this in Zammad')}
diff --git a/app/views/mailer/ticket_reminder_reached/en.html.erb b/app/views/mailer/ticket_reminder_reached/en.html.erb index 65f7b5440..5998d8830 100644 --- a/app/views/mailer/ticket_reminder_reached/en.html.erb +++ b/app/views/mailer/ticket_reminder_reached/en.html.erb @@ -1,18 +1,18 @@ -Reminder reached (<%= d 'ticket.title' %>) +Reminder reached (#{ticket.title}) -
Hi <%= d 'recipient.firstname' %>,
+
Hi #{recipient.firstname},

-
A ticket needs attention, reminder reached for (<%= d 'ticket.title' %>) with customer "<%= d 'ticket.customer.longname' %>".
+
A ticket needs attention, reminder reached for (#{ticket.title}) with customer "#{ticket.customer.longname}".

<% if @objects[:article] %>
- <%= t 'Information' %>: + #{t('Information')}:
- <%= a_html 'article' %> + #{article.body_as_html}
<% end %>
- <%= t 'View this in Zammad' %> + #{t('View this in Zammad')}
diff --git a/app/views/mailer/ticket_update/de.html.erb b/app/views/mailer/ticket_update/de.html.erb index 6930d2c01..bc2afff05 100644 --- a/app/views/mailer/ticket_update/de.html.erb +++ b/app/views/mailer/ticket_update/de.html.erb @@ -1,14 +1,14 @@ -Ticket aktualisiert (<%= d 'ticket.title' %>) +Ticket aktualisiert (#{ticket.title}) -
Hi <%= d 'recipient.firstname' %>,
+
Hi #{recipient.firstname},

-Ticket (<%= d 'ticket.title' %>) wurde von "<%= d 'current_user.longname' %>" aktualisiert. +Ticket (#{ticket.title}) wurde von "#{current_user.longname}" aktualisiert.

<% if @objects[:changes] && !@objects[:changes].empty? %>
- <%= t 'Changes' %>:
+ #{t('Changes')}:
<% @objects[:changes].each do |key, value| %> <%= t key %>: <%= h value[0] %> -> <%= h value[1] %>
<% end %> @@ -17,13 +17,13 @@ Ticket (<%= d 'ticket.title' %>) wurde von "<%= d 'current_user.longname' %><
<% if @objects[:article] %>
- <%= t 'Information' %>: + #{t('Information')}:
- <%= a_html 'article' %> + #{article.body_as_html}
<% end %>
- <%= t 'View this in Zammad' %> + #{t('View this in Zammad')}
diff --git a/app/views/mailer/ticket_update/en.html.erb b/app/views/mailer/ticket_update/en.html.erb index 220874bcc..c0dd35ec2 100644 --- a/app/views/mailer/ticket_update/en.html.erb +++ b/app/views/mailer/ticket_update/en.html.erb @@ -1,14 +1,14 @@ -Updated Ticket (<%= d 'ticket.title' %>) +Updated Ticket (#{ticket.title}) -
Hi <%= d 'recipient.firstname' %>,
+
Hi #{recipient.firstname},

-Ticket (<%= d 'ticket.title' %>) has been updated by "<%= d 'current_user.longname' %>". +Ticket (#{ticket.title}) has been updated by "#{current_user.longname}".

<% if @objects[:changes] && !@objects[:changes].empty? %>
- <%= t 'Changes' %>:
+ #{t('Changes')}:
<% @objects[:changes].each do |key, value| %> <%= t key %>: <%= h value[0] %> -> <%= h value[1] %>
<% end %> @@ -17,13 +17,13 @@ Ticket (<%= d 'ticket.title' %>) has been updated by "<%= d 'current_user.lon
<% if @objects[:article] %>
- <%= t 'Information' %>: + #{t('Information')}:
- <%= a_html 'article' %> + #{article.body_as_html}
<% end %>
diff --git a/app/views/mailer/user_device_new/de.html.erb b/app/views/mailer/user_device_new/de.html.erb index 889cd7b9a..a4b283ae9 100644 --- a/app/views/mailer/user_device_new/de.html.erb +++ b/app/views/mailer/user_device_new/de.html.erb @@ -1,19 +1,19 @@ -<%= c 'product_name' %>-Anmeldung erfasst von einem neuen Gerät +#{config.product_name}-Anmeldung erfasst von einem neuen Gerät -
Hallo <%= d 'user.firstname' %>,
+
Hallo #{user.firstname},

-
es sieht aus, als ob Du Dich mit einem neuen Gerät um "<%= d 'user_device.created_at' %>" angemeldet hast:
+
es sieht aus, als ob Du Dich mit einem neuen Gerät um "#{user_device.created_at}" angemeldet hast:

-Dein Gerät: <%= d 'user_device.name' %>
-Deine Lokation (relativ): <%= d 'user_device.location' %>
-Deine IP: <%= d 'user_device.ip' %>
+Dein Gerät: #{user_device.name}
+Deine Lokation (relativ): #{user_device.location}
+Deine IP: #{user_device.ip}

Das Gerät wurde in die Liste der bekannten Geräte hinzugefügt, diese Liste kannst Du hier einsehen:

-
<%= c 'http_type' %>://<%= c 'fqdn' %>/#profile/devices
+
#{config.http_type}://#{config.fqdn}/#profile/devices

Wenn dies nicht Du warst, entferne das Gerät aus der Liste, ändere Dein Account-Passwort und kontaktieren Deinen Administrator. Jemand könnte unberechtigten Zugriff auf Dein Konto bekommen haben.

-
Dein <%= c 'product_name' %> Team
+
Dein #{config.product_name} Team
diff --git a/app/views/mailer/user_device_new/en.html.erb b/app/views/mailer/user_device_new/en.html.erb index 71b792281..70fc2bbd7 100644 --- a/app/views/mailer/user_device_new/en.html.erb +++ b/app/views/mailer/user_device_new/en.html.erb @@ -1,19 +1,19 @@ -<%= c 'product_name' %> signin detected from a new device +#{config.product_name} signin detected from a new device -
Hi <%= d 'user.firstname' %>,
+
Hi #{user.firstname},

-
It looks like you signed into your account using a new device on "<%= d 'user_device.created_at' %>":
+
It looks like you signed into your account using a new device on "#{user_device.created_at}":

-Your device: <%= d 'user_device.name' %>
-Your location (relative): <%= d 'user_device.location' %>
-Your IP: <%= d 'user_device.ip' %>
+Your device: #{user_device.name}
+Your location (relative): #{user_device.location}
+Your IP: #{user_device.ip}

Your device has been added to your list of known devices, which you can view here:

-
<%= c 'http_type' %>://<%= c 'fqdn' %>/#profile/devices
+
#{config.http_type}://#{config.fqdn}/#profile/devices

If this wasn't you, remove the device, changing your account password, and contacting your administrator. Somebody might have gained unauthorized access to your account.

-
Your <%= c 'product_name' %> Team
+
Your #{config.product_name} Team
diff --git a/app/views/mailer/user_device_new_location/de.html.erb b/app/views/mailer/user_device_new_location/de.html.erb index 31b7fd816..d59269bcf 100644 --- a/app/views/mailer/user_device_new_location/de.html.erb +++ b/app/views/mailer/user_device_new_location/de.html.erb @@ -1,19 +1,19 @@ -<%= c 'product_name' %>-Anmeldung von einem anderen Land erfasst +#{config.product_name}-Anmeldung von einem anderen Land erfasst -
Hallo <%= d 'user.firstname' %>,
+
Hallo #{user.firstname},

-
es sieht aus, als ob Du Dich um "<%= d 'user_device.created_at' %>" von einem bekannten Gerät aus einem anderen Land angemeldet hast:
+
es sieht aus, als ob Du Dich um "#{user_device.created_at}" von einem bekannten Gerät aus einem anderen Land angemeldet hast:

-Dein Gerät: <%= d 'user_device.name' %>
-Deine Lokation (relativ): <%= d 'user_device.location' %>
-Deine IP: <%= d 'user_device.ip' %>
+Dein Gerät: #{user_device.name}
+Deine Lokation (relativ): #{user_device.location}
+Deine IP: #{user_device.ip}

Das neue Land wurde in die Liste der bekannten Geräte hinzugefügt, diese Liste kannst Du hier einsehen:

-
<%= c 'http_type' %>://<%= c 'fqdn' %>/#profile/devices
+
#{config.http_type}://#{config.fqdn}/#profile/devices

Wenn dies nicht Du warst, entferne die neue Lokation aus der Liste, ändere Dein Account-Passwort und kontaktieren Deinen Administrator. Jemand könnte unberechtigten Zugriff auf Dein Konto bekommen haben.

-
Dein <%= c 'product_name' %> Team
+
Dein #{config.product_name} Team
diff --git a/app/views/mailer/user_device_new_location/en.html.erb b/app/views/mailer/user_device_new_location/en.html.erb index 2cdfb5a6f..6b9119591 100644 --- a/app/views/mailer/user_device_new_location/en.html.erb +++ b/app/views/mailer/user_device_new_location/en.html.erb @@ -1,19 +1,19 @@ -<%= c 'product_name' %> signin detected from a new country +#{config.product_name} signin detected from a new country -
Hi <%= d 'user.firstname' %>,
+
Hi #{user.firstname},

-
It looks like you used your account with an known device but from a new country on "<%= d 'user_device.created_at' %>":
+
It looks like you used your account with an known device but from a new country on "#{user_device.created_at}":

-Your device: <%= d 'user_device.name' %>
-Your location (relative): <%= d 'user_device.location' %>
-Your IP: <%= d 'user_device.ip' %>
+Your device: #{user_device.name}
+Your location (relative): #{user_device.location}
+Your IP: #{user_device.ip}

The country has been added to your list of known devices, which you can view here:

-
<%= c 'http_type' %>://<%= c 'fqdn' %>/#profile/devices
+
#{config.http_type}://#{config.fqdn}/#profile/devices

If this wasn't you, remove the device, changing your account password, and contacting your administrator. Somebody might have gained unauthorized access to your account.

-
Your <%= c 'product_name' %> Team
+
Your #{config.product_name} Team
diff --git a/app/views/mailer/user_invite/de.html.erb b/app/views/mailer/user_invite/de.html.erb index b83a06dd8..48504b298 100644 --- a/app/views/mailer/user_invite/de.html.erb +++ b/app/views/mailer/user_invite/de.html.erb @@ -1,13 +1,13 @@ -Einladung zu <%= c 'product_name' %> über <%= c 'fqdn' %> +Einladung zu #{config.product_name} über #{config.fqdn} -
Hallo <%= d 'user.firstname' %>,
+
Hallo #{user.firstname},

-
Ich (<%= d 'current_user.firstname' %> <%= d 'current_user.lastname' %>) möchte Dich zu <%= c 'product_name' %> einladen - unsere Kundensupport / Ticket System Platform.
+
Ich (#{current_user.firstname} #{current_user.lastname}) möchte Dich zu #{config.product_name} einladen - unsere Kundensupport / Ticket System Platform.

-
Um sich anzumelden kann hier das Password gesetzt werden.
+
Um sich anzumelden kann hier das Password gesetzt werden.

Enjoy,

-
<%= d 'current_user.firstname' %> <%= d 'current_user.lastname' %>
+
#{current_user.firstname} #{current_user.lastname}

-
Dein <%= c 'product_name' %> Team
+
Dein #{config.product_name} Team
diff --git a/app/views/mailer/user_invite/en.html.erb b/app/views/mailer/user_invite/en.html.erb index 0dea44860..52e3b39db 100644 --- a/app/views/mailer/user_invite/en.html.erb +++ b/app/views/mailer/user_invite/en.html.erb @@ -1,13 +1,13 @@ -Invitation to <%= c 'product_name' %> at <%= c 'fqdn' %> +Invitation to #{config.product_name} at #{config.fqdn} -
Hi <%= d 'user.firstname' %>,
+
Hi #{user.firstname},

-
I (<%= d 'current_user.firstname' %> <%= d 'current_user.lastname' %>) invite you to <%= c 'product_name' %> - our customer support / ticket system platform.
+
I (#{current_user.firstname} #{current_user.lastname}) invite you to #{config.product_name} - our customer support / ticket system platform.

-
Click here and set your password.
+
Click here and set your password.

Enjoy,

-
<%= d 'current_user.firstname' %> <%= d 'current_user.lastname' %>
+
#{current_user.firstname} #{current_user.lastname}

-
Your <%= c 'product_name' %> Team
+
Your #{config.product_name} Team
diff --git a/app/views/slack/ticket_create/en.md.erb b/app/views/slack/ticket_create/en.md.erb index 0d8268e39..e35162286 100644 --- a/app/views/slack/ticket_create/en.md.erb +++ b/app/views/slack/ticket_create/en.md.erb @@ -1,9 +1,9 @@ -# <%= d 'ticket.title' %> -_<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticket#<%= d 'ticket.number' %>>: Created by <%= d 'current_user.longname' %> at <%= d 'ticket.updated_at' %>_ -* <%= t 'Group' %>: <%= d 'ticket.group.name' %> -* <%= t 'Owner' %>: <%= d 'ticket.owner.fullname' %> -* <%= t 'State' %>: <%= t d 'ticket.state.name' %> +# #{ticket.title} +_<#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}|Ticket##{ticket.number}>: Created by #{current_user.longname} at #{ticket.updated_at}_ +* #{t('Group')}: #{ticket.group.name} +* #{t('Owner')}: #{ticket.owner.fullname} +* #{t('State')}: #{t(ticket.state.name)} <% if @objects[:article] %> -<%= a_text 'article' %> +#{article.body_as_text} <% end %> diff --git a/app/views/slack/ticket_escalation/en.md.erb b/app/views/slack/ticket_escalation/en.md.erb index e9d232544..9cd928ab3 100644 --- a/app/views/slack/ticket_escalation/en.md.erb +++ b/app/views/slack/ticket_escalation/en.md.erb @@ -1,7 +1,7 @@ -# <%= d 'ticket.title' %> -_<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticket#<%= d 'ticket.number' %>>: Escalated at <%= d 'ticket.escalation_at' %>_ -A ticket (<%= d 'ticket.title' %>) from "<%= d 'ticket.customer.longname' %>" is escalated since "<%= d 'ticket.escalation_at' %>"! +# #{ticket.title} +_<#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}|Ticket##{ticket.number}>: Escalated at #{ticket.escalation_at}_ +A ticket (#{ticket.title}) from "#{ticket.customer.longname}" is escalated since "#{ticket.escalation_at}"! <% if @objects[:article] %> -<%= a_text 'article' %> +#{article.body_as_text} <% end %> diff --git a/app/views/slack/ticket_escalation_warning/en.md.erb b/app/views/slack/ticket_escalation_warning/en.md.erb index 15068b0fb..ae976b512 100644 --- a/app/views/slack/ticket_escalation_warning/en.md.erb +++ b/app/views/slack/ticket_escalation_warning/en.md.erb @@ -1,7 +1,7 @@ -# <%= d 'ticket.title' %> -_<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticket#<%= d 'ticket.number' %>>: Will escalate at <%= d 'ticket.escalation_at' %>_ -A ticket (<%= d 'ticket.title' %>) from "<%= d 'ticket.customer.longname' %>" will escalate at "<%= d 'ticket.escalation_at' %>"! +# #{ticket.title} +_<#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}|Ticket##{ticket.number}>: Will escalate at #{ticket.escalation_at}_ +A ticket (#{ticket.title}) from "#{ticket.customer.longname}" will escalate at "#{ticket.escalation_at}"! <% if @objects[:article] %> -<%= a_text 'article' %> +#{article.body_as_text} <% end %> diff --git a/app/views/slack/ticket_reminder_reached/en.md.erb b/app/views/slack/ticket_reminder_reached/en.md.erb index 8ae47fc71..7156b6464 100644 --- a/app/views/slack/ticket_reminder_reached/en.md.erb +++ b/app/views/slack/ticket_reminder_reached/en.md.erb @@ -1,7 +1,7 @@ -# <%= d 'ticket.title' %> -_<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticket#<%= d 'ticket.number' %>>: Reminder reached!_ -A ticket needs attention, reminder reached for (<%= d 'ticket.title' %>) with customer "*<%= d 'ticket.customer.longname' %>*". +# #{ticket.title} +_<#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}|Ticket##{ticket.number}>: Reminder reached!_ +A ticket needs attention, reminder reached for (#{ticket.title}) with customer "*#{ticket.customer.longname}*". <% if @objects[:article] %> -<%= a_text 'article' %> +#{article.body_as_text} <% end %> diff --git a/app/views/slack/ticket_update/en.md.erb b/app/views/slack/ticket_update/en.md.erb index fe1a80e29..e8a9f86de 100644 --- a/app/views/slack/ticket_update/en.md.erb +++ b/app/views/slack/ticket_update/en.md.erb @@ -1,5 +1,5 @@ -# <%= d 'ticket.title' %> -_<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticket#<%= d 'ticket.number' %>>: Updated by <%= d 'current_user.longname' %> at <%= d 'ticket.updated_at' %>_ +# #{ticket.title} +_<#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}|Ticket##{ticket.number}>: Updated by #{current_user.longname} at #{ticket.updated_at}_ <% if @objects[:changes] && !@objects[:changes].empty? %> <% @objects[:changes].each do |key, value| %> * <%= t key %>: <%= h value[0] %> -> <%= h value[1] %> @@ -7,5 +7,5 @@ _<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticke <% end %> <% if @objects[:article] %> -<%= a_text 'article' %> +#{article.body_as_text} <% end %> diff --git a/lib/notification_factory/mailer.rb b/lib/notification_factory/mailer.rb index ca70330c4..79895fb84 100644 --- a/lib/notification_factory/mailer.rb +++ b/lib/notification_factory/mailer.rb @@ -6,7 +6,7 @@ get notification settings for user and notification type result = NotificationFactory::Mailer.notification_settings(user, ticket, type) - type: create | update | reminder_reached | pending + type: create | update | reminder_reached | escalation (escalation_warning) returns @@ -21,6 +21,15 @@ returns =end def self.notification_settings(user, ticket, type) + + # map types if needed + map = { + 'escalation_warning' => 'escalation' + } + if map[type] + type = map[type] + end + return if !user.preferences return if !user.preferences['notification_config'] matrix = user.preferences['notification_config']['matrix'] @@ -190,11 +199,12 @@ retunes ) result = NotificationFactory::Mailer.template( - templateInline: "Invitation to <%= c 'product_name' %> at <%= c 'fqdn' %>", + templateInline: "Invitation to \#{config.product_name} at \#{config.fqdn}", locale: 'en-us', objects: { recipient: User.find(2), }, + quote: true, # html quoting ) only raw subject/body @@ -221,7 +231,7 @@ returns def self.template(data) if data[:templateInline] - return NotificationFactory::Renderer.new(data[:objects], data[:locale], data[:templateInline], false).render + return NotificationFactory::Renderer.new(data[:objects], data[:locale], data[:templateInline], data[:quote]).render end template = NotificationFactory.template_read( diff --git a/lib/notification_factory/renderer.rb b/lib/notification_factory/renderer.rb index 86e60814f..e8d4a8697 100644 --- a/lib/notification_factory/renderer.rb +++ b/lib/notification_factory/renderer.rb @@ -9,7 +9,7 @@ examples how to use ticket: Ticket.first, }, 'de-de', - 'some template <%= d "ticket.title", false %> <%= c "fqdn", false %>', + 'some template #{ticket.title} {config.fqdn}', false ).render @@ -18,7 +18,7 @@ examples how to use ticket: Ticket.first, }, 'de-de', - 'some template <%= d "ticket.title", true %> <%= c "fqdn", true %>', + 'some template #{ticket.title} #{config.fqdn}', ).render =end @@ -26,7 +26,7 @@ examples how to use def initialize(objects, locale, template, escape = true) @objects = objects @locale = locale || 'en-us' - @template = NotificationFactory::Template.new(template) + @template = NotificationFactory::Template.new(template, escape) @escape = escape end @@ -41,6 +41,25 @@ examples how to use # do validaton, ignore some methodes return "\#{#{key} / not allowed}" if !data_key_valid?(key) + # aliases + map = { + 'article.body' => 'article.body_as_text_with_quote.text2html', + } + if map[key] + key = map[key] + end + + # escape in html mode + if escape + no_escape = { + 'article.body_as_html' => true, + 'article.body_as_text_with_quote.text2html' => true, + } + if no_escape[key] + escape = false + end + end + value = nil object_methods = key.split('.') object_name = object_methods.shift @@ -76,7 +95,11 @@ examples how to use value = "\#{#{object_name}.#{object_methods_s} / no such method}" break end - object_refs = object_refs.send(method.to_sym) + begin + object_refs = object_refs.send(method.to_sym) + rescue => e + object_refs = "\#{#{object_name}.#{object_methods_s} / e.message}" + end } placeholder = if !value object_refs @@ -100,27 +123,6 @@ examples how to use escaping(translation, escape) end - # a_html - article body in html - # a_html(article) - def a_html(article) - content_type = d "#{article}.content_type", false - if content_type =~ /html/ - return d "#{article}.body", false - end - d("#{article}.body", false).text2html - end - - # a_text - article body in text - # a_text(article) - def a_text(article) - content_type = d "#{article}.content_type", false - body = d "#{article}.body", false - if content_type =~ /html/ - body = body.html2text - end - (body.strip + "\n").gsub(/^(.*?)$/, '> \\1') - end - # h - htmlEscape # h('fqdn', htmlEscape) def h(key) @@ -137,7 +139,7 @@ examples how to use end def data_key_valid?(key) - return false if key =~ /`|\.(|\s*)(save|destroy|delete|remove|drop|update|create|new|all|where|find)/i + return false if key =~ /`|\.(|\s*)(save|destroy|delete|remove|drop|update|create|new|all|where|find)/i && key !~ /(update|create)d_at/i true end diff --git a/lib/notification_factory/slack.rb b/lib/notification_factory/slack.rb index c0102e460..da8dc3b6c 100644 --- a/lib/notification_factory/slack.rb +++ b/lib/notification_factory/slack.rb @@ -33,8 +33,8 @@ returns type: 'slack', ) - message_subject = NotificationFactory::Renderer.new(data[:objects], data[:locale], template[:subject]).render - message_body = NotificationFactory::Renderer.new(data[:objects], data[:locale], template[:body]).render + message_subject = NotificationFactory::Renderer.new(data[:objects], data[:locale], template[:subject], false).render + message_body = NotificationFactory::Renderer.new(data[:objects], data[:locale], template[:body], false).render if !data[:raw] application_template = NotificationFactory.application_template_read( @@ -43,7 +43,7 @@ returns ) data[:objects][:message] = message_body data[:objects][:standalone] = data[:standalone] - message_body = NotificationFactory::Renderer.new(data[:objects], data[:locale], application_template).render + message_body = NotificationFactory::Renderer.new(data[:objects], data[:locale], application_template, false).render end { subject: message_subject.strip!, diff --git a/lib/notification_factory/template.rb b/lib/notification_factory/template.rb index 7da6e8efe..76dbe7c53 100644 --- a/lib/notification_factory/template.rb +++ b/lib/notification_factory/template.rb @@ -5,13 +5,15 @@ class NotificationFactory::Template examples how to use cleaned_template = NotificationFactory::Template.new( - 'some template <%= d "ticket.title", false %> <%= c "fqdn", false %>', + 'some template #{ticket.title} #{config.fqdn}', + true, ).to_s =end - def initialize(template) + def initialize(template, escape) @template = template + @escape = escape end def to_s @@ -23,7 +25,35 @@ examples how to use def strip_html # some browsers start adding HTML tags # fixes https://github.com/zammad/zammad/issues/385 - @template.gsub!(%r{#\{\s*<[^>]+>([^<]+)]+>\s*\}}, '\1') - @template.gsub!(/#\{\s*<[^>]+>([^<]+)\s*\}/, '\1') + @template.gsub!(/\#\{\s*t\((.+?)\)\s*\}/m) do + content = $1 + if content =~ /^'(.+?)'$/ + "<%= t \"#{strip_content($1)}\", #{@escape} %>" + else + "<%= t d\"#{strip_variable(content)}\", #{@escape} %>" + end + end + @template.gsub!(/\#\{\s*config\.(.+?)\s*\}/m) do + "<%= c \"#{strip_variable($1)}\", #{@escape} %>" + end + @template.gsub!(/\#\{(.*?)\}/m) do + "<%= d \"#{strip_variable($1)}\", #{@escape} %>" + end end + + def strip_content(string) + return string if !string + string.gsub!(/\t|\r|\n/, '') + string.gsub!(/"/, '\"') + string + end + + def strip_variable(string) + return string if !string + string.gsub!(/\t|\r|\n|"|'|§|;/, '') + string.gsub!(/\s*/, '') + string.gsub!(/<.+?>/, '') + string + end + end diff --git a/test/unit/notification_factory_renderer_test.rb b/test/unit/notification_factory_renderer_test.rb index a6e5bb0e7..d091afeec 100644 --- a/test/unit/notification_factory_renderer_test.rb +++ b/test/unit/notification_factory_renderer_test.rb @@ -3,29 +3,40 @@ require 'test_helper' class NotificationFactoryRendererTest < ActiveSupport::TestCase - # TODO: should be mocked somehow - Translation.load('de-de') - # RSpec incoming! def described_class NotificationFactory::Renderer end - Group = Struct.new(:name) - State = Struct.new(:name) - User = Struct.new(:firstname, :lastname, :longname, :fullname) - Ticket = Struct.new(:id, :title, :group, :owner, :state) - - group = Group.new('Users') - state = State.new('new') - owner = User.new('Notificationxxx', 'Agent1yyy', 'Notificationxxx Agent1yyy', 'Notificationxxx Agent1yyy (Zammad)') - current_user = User.new('CurrentUserxxx', 'Agent2yyy', 'CurrentUserxxx Agent2yyy', 'CurrentUserxxx Agent2yyy (Zammad)') - recipient = User.new('Recipientxxx', 'Customer1yyy', 'Recipientxxx Customer1yyy', 'Recipientxxx Customer1yyy (Zammad)') - ticket = Ticket.new(1, 'Welcome to Zammad!', group, owner, state) + group = Group.new(name: 'Users') + owner = User.new(firstname: 'Notificationxxx', lastname: 'Agent1yyy') + current_user = User.new(firstname: 'CurrentUserxxx', lastname: 'Agent2yyy') + recipient = User.new(firstname: 'Recipientxxx', lastname: 'Customer1yyy') + state = Ticket::State.new(name: 'new') + ticket = Ticket.new( + id: 1, + title: 'Welcome to Zammad!', + group: group, + owner: owner, + state: state, + created_at: Time.zone.parse('2016-11-12 12:00:00 UTC'), + updated_at: Time.zone.parse('2016-11-12 14:00:00 UTC'), + ) + article_html1 = Ticket::Article.new( + body: 'test hello
some new line', + content_type: 'text/html', + ) + article_plain1 = Ticket::Article.new( + body: "test hello\nsome new line", + content_type: 'text/plain', + ) + article_plain2 = Ticket::Article.new( + body: "test hello\nsome new line", + ) test 'replace object attribute' do - template = "<%= d 'ticket.title' %>" + template = "\#{ticket.title}" result = described_class.new( { ticket: ticket, @@ -35,7 +46,27 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML(ticket.title), result) - template = "<%= d 'ticket. title' %>" + template = "\#{ticket.created_at}" + result = described_class.new( + { + ticket: ticket, + }, + 'en-us', + template, + ).render + assert_equal(ticket.created_at.to_s, result) + + template = "\#{ticket.updated_at}" + result = described_class.new( + { + ticket: ticket, + }, + 'en-us', + template, + ).render + assert_equal(ticket.updated_at.to_s, result) + + template = "\#{ticket. title}" result = described_class.new( { ticket: ticket, @@ -45,7 +76,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML(ticket.title), result) - template = "<%= d 'ticket.\n title' %>" + template = "\#{ticket.\n title}" result = described_class.new( { ticket: ticket, @@ -55,7 +86,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML(ticket.title), result) - template = "<%= d 'ticket.\t title' %>" + template = "\#{ticket.\t title}" result = described_class.new( { ticket: ticket, @@ -65,7 +96,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML(ticket.title), result) - template = "<%= d 'ticket.\t\n title\t' %>" + template = "\#{ticket.\t\n title\t}" result = described_class.new( { ticket: ticket, @@ -75,13 +106,50 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML(ticket.title), result) + template = "\#{ticket.\" title\t}" + result = described_class.new( + { + ticket: ticket, + }, + 'en-us', + template, + ).render + assert_equal(CGI.escapeHTML(ticket.title), result) + + template = "some test
\#{article.body}" + result = described_class.new( + { + article: article_html1, + }, + 'en-us', + template, + ).render + assert_equal('some test
> test hello
> some new line
', result) + + result = described_class.new( + { + article: article_plain1, + }, + 'en-us', + template, + ).render + assert_equal('some test
> test <b>hello</b>
> some new line
', result) + + result = described_class.new( + { + article: article_plain2, + }, + 'en-us', + template, + ).render + assert_equal('some test
> test <b>hello</b>
> some new line
', result) + end test 'config' do - setting = 'fqdn' - template = "<%= c '#{setting}' %>" - + setting = 'fqdn' + template = "\#{config.#{setting}}" result = described_class.new( { ticket: ticket, @@ -89,13 +157,11 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase 'en-us', template, ).render - assert_equal(Setting.get(setting), result) setting1 = 'fqdn' setting2 = 'product_name' - template = "some <%= c '#{setting1}' %> and <%= c '#{setting2}' %>" - + template = "some \#{config.#{setting1}} and \#{config.#{setting2}}" result = described_class.new( { ticket: ticket, @@ -103,14 +169,25 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase 'en-us', template, ).render + assert_equal("some #{Setting.get(setting1)} and #{Setting.get(setting2)}", result) + setting1 = 'fqdn' + setting2 = 'product_name' + template = "some \#{ config.#{setting1}} and \#{\tconfig.#{setting2}}" + result = described_class.new( + { + ticket: ticket, + }, + 'en-us', + template, + ).render assert_equal("some #{Setting.get(setting1)} and #{Setting.get(setting2)}", result) end test 'translation' do - template = "<%= t 'new' %>" - + #template = "<%= t 'new' %>" + template = "\#{t('new')}" result = described_class.new( { ticket: ticket, @@ -118,11 +195,9 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase 'de-de', template, ).render - assert_equal('neu', result) - template = "some text <%= t 'new' %> and <%= t 'open' %>" - + template = "some text \#{t('new')} and \#{t('open')}" result = described_class.new( { ticket: ticket, @@ -130,14 +205,33 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase 'de-de', template, ).render + assert_equal('some text neu and offen', result) + template = "some text \#{t('new') } and \#{ t('open')}" + result = described_class.new( + { + ticket: ticket, + }, + 'de-de', + template, + ).render + assert_equal('some text neu and offen', result) + + template = "some text \#{\nt('new') } and \#{ t('open')\t}" + result = described_class.new( + { + ticket: ticket, + }, + 'de-de', + template, + ).render assert_equal('some text neu and offen', result) end test 'chained function calls' do - template = "<%= t d 'ticket.state.name' %>" + template = "\#{t(ticket.state.name)}" result = described_class.new( { @@ -152,7 +246,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase test 'not existing object and attribute' do - template = "<%= d '' %>" + template = "\#{}" result = described_class.new( { ticket: ticket, @@ -162,7 +256,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{no such object}'), result) - template = "<%= d 'notexsiting.notexsiting' %>" + template = "\#{notexsiting.notexsiting}" result = described_class.new( { ticket: ticket, @@ -172,7 +266,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{notexsiting / no such object}'), result) - template = "<%= d 'ticket.notexsiting' %>" + template = "\#{ticket.notexsiting}" result = described_class.new( { ticket: ticket, @@ -182,7 +276,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket.notexsiting / no such method}'), result) - template = "<%= d 'ticket.' %>" + template = "\#{ticket.}" result = described_class.new( { ticket: ticket, @@ -192,7 +286,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket. / no such method}'), result) - template = "<%= d 'ticket.title.notexsiting' %>" + template = "\#{ticket.title.notexsiting}" result = described_class.new( { ticket: ticket, @@ -202,7 +296,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket.title.notexsiting / no such method}'), result) - template = "<%= d 'ticket.notexsiting.notexsiting' %>" + template = "\#{ticket.notexsiting.notexsiting}" result = described_class.new( { ticket: ticket, @@ -212,7 +306,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket.notexsiting / no such method}'), result) - template = "<%= d 'notexsiting' %>" + template = "\#{notexsiting}" result = described_class.new( { ticket: ticket, @@ -222,7 +316,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{notexsiting / no such object}'), result) - template = "<%= d 'notexsiting.' %>" + template = "\#{notexsiting.}" result = described_class.new( { ticket: ticket, @@ -232,7 +326,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{notexsiting / no such object}'), result) - template = "<%= d 'string' %>" + template = "\#{string}" result = described_class.new( { string: 'some string', @@ -242,7 +336,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('some string'), result) - template = "<%= d 'fixum' %>" + template = "\#{fixum}" result = described_class.new( { fixum: 123, @@ -252,7 +346,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('123'), result) - template = "<%= d 'float' %>" + template = "\#{float}" result = described_class.new( { float: 123.99, @@ -266,7 +360,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase test 'data key validation' do - template = "<%= d 'ticket.title `echo 1`' %>" + template = "\#{ticket.title `echo 1`}" result = described_class.new( { ticket: ticket, @@ -274,9 +368,9 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase 'en-us', template, ).render - assert_equal(CGI.escapeHTML('#{ticket.title `echo 1` / not allowed}'), result) + assert_equal(CGI.escapeHTML('#{ticket.title`echo1` / not allowed}'), result) - template = "<%= d 'ticket.destroy' %>" + template = "\#{ticket.destroy}" result = described_class.new( { ticket: ticket, @@ -286,7 +380,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket.destroy / not allowed}'), result) - template = "<%= d 'ticket.save' %>" + template = "\#{ticket.save}" result = described_class.new( { ticket: ticket, @@ -296,7 +390,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket.save / not allowed}'), result) - template = "<%= d 'ticket.update' %>" + template = "\#{ticket.update}" result = described_class.new( { ticket: ticket, @@ -306,37 +400,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket.update / not allowed}'), result) - template = "<%= d 'ticket.delete' %>" - result = described_class.new( - { - ticket: ticket, - }, - 'en-us', - template, - ).render - assert_equal(CGI.escapeHTML('#{ticket.delete / not allowed}'), result) - - template = "<%= d 'ticket.remove' %>" - result = described_class.new( - { - ticket: ticket, - }, - 'en-us', - template, - ).render - assert_equal(CGI.escapeHTML('#{ticket.remove / not allowed}'), result) - - template = "<%= d 'ticket.drop' %>" - result = described_class.new( - { - ticket: ticket, - }, - 'en-us', - template, - ).render - assert_equal(CGI.escapeHTML('#{ticket.drop / not allowed}'), result) - - template = "<%= d 'ticket.create' %>" + template = "\#{ticket.create}" result = described_class.new( { ticket: ticket, @@ -346,7 +410,47 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket.create / not allowed}'), result) - template = "<%= d 'ticket.new' %>" + template = "\#{ticket.delete}" + result = described_class.new( + { + ticket: ticket, + }, + 'en-us', + template, + ).render + assert_equal(CGI.escapeHTML('#{ticket.delete / not allowed}'), result) + + template = "\#{ticket.remove}" + result = described_class.new( + { + ticket: ticket, + }, + 'en-us', + template, + ).render + assert_equal(CGI.escapeHTML('#{ticket.remove / not allowed}'), result) + + template = "\#{ticket.drop}" + result = described_class.new( + { + ticket: ticket, + }, + 'en-us', + template, + ).render + assert_equal(CGI.escapeHTML('#{ticket.drop / not allowed}'), result) + + template = "\#{ticket.create}" + result = described_class.new( + { + ticket: ticket, + }, + 'en-us', + template, + ).render + assert_equal(CGI.escapeHTML('#{ticket.create / not allowed}'), result) + + template = "\#{ticket.new}" result = described_class.new( { ticket: ticket, @@ -356,7 +460,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket.new / not allowed}'), result) - template = "<%= d 'ticket.update_att' %>" + template = "\#{ticket.update_att}" result = described_class.new( { ticket: ticket, @@ -366,7 +470,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket.update_att / not allowed}'), result) - template = "<%= d 'ticket.all' %>" + template = "\#{ticket.all}" result = described_class.new( { ticket: ticket, @@ -376,7 +480,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket.all / not allowed}'), result) - template = "<%= d 'ticket.find' %>" + template = "\#{ticket.find}" result = described_class.new( { ticket: ticket, @@ -386,7 +490,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket.find / not allowed}'), result) - template = "<%= d 'ticket.where' %>" + template = "\#{ticket.where}" result = described_class.new( { ticket: ticket, @@ -396,7 +500,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase ).render assert_equal(CGI.escapeHTML('#{ticket.where / not allowed}'), result) - template = "<%= d 'ticket. destroy' %>" + template = "\#{ticket. destroy}" result = described_class.new( { ticket: ticket, @@ -404,9 +508,9 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase 'en-us', template, ).render - assert_equal(CGI.escapeHTML('#{ticket. destroy / not allowed}'), result) + assert_equal(CGI.escapeHTML('#{ticket.destroy / not allowed}'), result) - template = "<%= d 'ticket.\n destroy' %>" + template = "\#{ticket.\n destroy}" result = described_class.new( { ticket: ticket, @@ -414,9 +518,9 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase 'en-us', template, ).render - assert_equal(CGI.escapeHTML("\#{ticket.\n destroy / not allowed}"), result) + assert_equal(CGI.escapeHTML("\#{ticket.destroy / not allowed}"), result) - template = "<%= d 'ticket.\t destroy' %>" + template = "\#{ticket.\t destroy}" result = described_class.new( { ticket: ticket, @@ -424,9 +528,9 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase 'en-us', template, ).render - assert_equal(CGI.escapeHTML("\#{ticket.\t destroy / not allowed}"), result) + assert_equal(CGI.escapeHTML("\#{ticket.destroy / not allowed}"), result) - template = "<%= d 'ticket.\r destroy' %>" + template = "\#{ticket.\r destroy}" result = described_class.new( { ticket: ticket, @@ -434,7 +538,7 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase 'en-us', template, ).render - assert_equal(CGI.escapeHTML("\#{ticket.\r destroy / not allowed}"), result) + assert_equal(CGI.escapeHTML("\#{ticket.destroy / not allowed}"), result) end diff --git a/test/unit/notification_factory_slack_template_test.rb b/test/unit/notification_factory_slack_template_test.rb index d746c4b42..cb3b349f0 100644 --- a/test/unit/notification_factory_slack_template_test.rb +++ b/test/unit/notification_factory_slack_template_test.rb @@ -78,7 +78,7 @@ class NotificationFactorySlackTemplateTest < ActiveSupport::TestCase ) assert_match('# Welcome to Zammad!', result[:subject]) - assert_match('User<b>xxx</b>', result[:body]) + assert_match('Userxxx', result[:body]) assert_match('Created by', result[:body]) assert_match('test123', result[:body]) assert_no_match('Dein', result[:body]) @@ -113,7 +113,7 @@ class NotificationFactorySlackTemplateTest < ActiveSupport::TestCase }, ) assert_match('# Welcome to Zammad!', result[:subject]) - assert_match('User<b>xxx</b>', result[:body]) + assert_match('Userxxx', result[:body]) assert_match('state: aaa -> bbb', result[:body]) assert_match('group: xxx -> yyy', result[:body]) assert_no_match('Dein', result[:body]) diff --git a/test/unit/notification_factory_template_test.rb b/test/unit/notification_factory_template_test.rb index c8ad841f7..baee7c7ea 100644 --- a/test/unit/notification_factory_template_test.rb +++ b/test/unit/notification_factory_template_test.rb @@ -11,48 +11,69 @@ class NotificationFactoryTemplateTest < ActiveSupport::TestCase test 'regular browser html' do # ensures https://github.com/zammad/zammad/issues/385 - template_before = '<%= d "#{ticket.id}" %>' - template_after = '<%= d "ticket.id" %>' + template_before = '#{ticket.id}' + template_after = '<%= d "ticket.id", true %>' - result = described_class.new(template_before).to_s + result = described_class.new(template_before, true).to_s + assert_equal(template_after, result) + + template_before = '#{config.fqdn}' + template_after = '<%= d "config.fqdn", true %>' + + result = described_class.new(template_before, true).to_s assert_equal(template_after, result) end test 'spaced browser html' do # ensures https://github.com/zammad/zammad/issues/385 - template_before = '<%= d "#{ ticket.id } " %>' - template_after = '<%= d "ticket.id " %>' + template_before = '#{ ticket.id }' + template_after = '<%= d "ticket.id", true %>' - result = described_class.new(template_before).to_s + result = described_class.new(template_before, true).to_s assert_equal(template_after, result) end test 'broken browser html' do # ensures https://github.com/zammad/zammad/issues/385 - template_before = '<%= d "#{ticket.id }" %>' - template_after = '<%= d "ticket.id " %>' + template_before = '#{ticket.id }' + template_after = '<%= d "ticket.id", true %>' - result = described_class.new(template_before).to_s + result = described_class.new(template_before, true).to_s assert_equal(template_after, result) end test 'empty tag' do - template_before = '<%= d "#{}" %>' - template_after = '<%= d "#{}" %>' + template_before = '#{}' + template_after = '<%= d "", true %>' - result = described_class.new(template_before).to_s + result = described_class.new(template_before, true).to_s assert_equal(template_after, result) end test 'empty tag with space' do - template_before = '<%= d "#{ }" %>' - template_after = '<%= d "#{ }" %>' + template_before = '#{ }' + template_after = '<%= d "", false %>' - result = described_class.new(template_before).to_s + result = described_class.new(template_before, false).to_s + assert_equal(template_after, result) + end + + test 'translation' do + + template_before = "\#{t('some text')}" + template_after = '<%= t "some text", false %>' + + result = described_class.new(template_before, false).to_s + assert_equal(template_after, result) + + template_before = "\#{t('some \"text\"')}" + template_after = '<%= t "some \"text\"", false %>' + + result = described_class.new(template_before, false).to_s assert_equal(template_after, result) end diff --git a/test/unit/ticket_trigger_test.rb b/test/unit/ticket_trigger_test.rb index 9b9adf90b..5d86c2659 100644 --- a/test/unit/ticket_trigger_test.rb +++ b/test/unit/ticket_trigger_test.rb @@ -13,7 +13,7 @@ class TicketTriggerTest < ActiveSupport::TestCase }, perform: { 'notification.email' => { - 'body' => 'some text
#{ticket.customer.lastname}
#{ticket.title}', + 'body' => 'some text
#{ticket.customer.lastname}
#{ticket.title}
#{article.body}', 'recipient' => 'ticket_customer', 'subject' => 'Thanks for your inquiry (#{ticket.title})!', }, @@ -60,12 +60,25 @@ class TicketTriggerTest < ActiveSupport::TestCase created_by_id: 1, ) assert(ticket1, 'ticket1 created') + Ticket::Article.create( + ticket_id: ticket1.id, + from: 'some_sender@example.com', + to: 'some_recipient@example.com', + subject: 'some subject', + message_id: 'some@id', + body: "some message note\nnew line", + internal: false, + sender: Ticket::Article::Sender.find_by(name: 'Agent'), + type: Ticket::Article::Type.find_by(name: 'note'), + updated_by_id: 1, + created_by_id: 1, + ) assert_equal('some title äöüß', ticket1.title, 'ticket1.title verify') assert_equal('Users', ticket1.group.name, 'ticket1.group verify') assert_equal('new', ticket1.state.name, 'ticket1.state verify') assert_equal('2 normal', ticket1.priority.name, 'ticket1.priority verify') - assert_equal(0, ticket1.articles.count, 'ticket1.articles verify') + assert_equal(1, ticket1.articles.count, 'ticket1.articles verify') assert_equal([], Tag.tag_list(object: 'Ticket', o_id: ticket1.id)) Observer::Transaction.commit @@ -75,13 +88,14 @@ class TicketTriggerTest < ActiveSupport::TestCase assert_equal('Users', ticket1.group.name, 'ticket1.group verify') assert_equal('new', ticket1.state.name, 'ticket1.state verify') assert_equal('3 high', ticket1.priority.name, 'ticket1.priority verify') - assert_equal(1, ticket1.articles.count, 'ticket1.articles verify') + assert_equal(2, ticket1.articles.count, 'ticket1.articles verify') assert_equal(%w(aa kk), Tag.tag_list(object: 'Ticket', o_id: ticket1.id)) article1 = ticket1.articles.last assert_match('Zammad ', article1.from) assert_match('nicole.braun@zammad.org', article1.to) assert_match('Thanks for your inquiry (some title äöüß)!', article1.subject) assert_match('Braun
some <b>title</b>', article1.body) + assert_match('> some message <b>note</b>
> new line', article1.body) assert_equal('text/html', article1.content_type) ticket1.priority = Ticket::Priority.lookup(name: '2 normal') @@ -93,7 +107,7 @@ class TicketTriggerTest < ActiveSupport::TestCase assert_equal('Users', ticket1.group.name, 'ticket1.group verify') assert_equal('new', ticket1.state.name, 'ticket1.state verify') assert_equal('2 normal', ticket1.priority.name, 'ticket1.priority verify') - assert_equal(1, ticket1.articles.count, 'ticket1.articles verify') + assert_equal(2, ticket1.articles.count, 'ticket1.articles verify') assert_equal(%w(aa kk), Tag.tag_list(object: 'Ticket', o_id: ticket1.id)) ticket1.state = Ticket::State.lookup(name: 'open') @@ -105,7 +119,7 @@ class TicketTriggerTest < ActiveSupport::TestCase assert_equal('Users', ticket1.group.name, 'ticket1.group verify') assert_equal('open', ticket1.state.name, 'ticket1.state verify') assert_equal('2 normal', ticket1.priority.name, 'ticket1.priority verify') - assert_equal(1, ticket1.articles.count, 'ticket1.articles verify') + assert_equal(2, ticket1.articles.count, 'ticket1.articles verify') assert_equal(%w(aa kk), Tag.tag_list(object: 'Ticket', o_id: ticket1.id)) ticket1.state = Ticket::State.lookup(name: 'new') @@ -118,7 +132,7 @@ class TicketTriggerTest < ActiveSupport::TestCase assert_equal('Users', ticket1.group.name, 'ticket1.group verify') assert_equal('new', ticket1.state.name, 'ticket1.state verify') assert_equal('3 high', ticket1.priority.name, 'ticket1.priority verify') - assert_equal(2, ticket1.articles.count, 'ticket1.articles verify') + assert_equal(3, ticket1.articles.count, 'ticket1.articles verify') assert_equal(%w(aa kk), Tag.tag_list(object: 'Ticket', o_id: ticket1.id)) article1 = ticket1.articles.last assert_match('Zammad ', article1.from) @@ -155,6 +169,56 @@ class TicketTriggerTest < ActiveSupport::TestCase assert_equal(0, ticket2.articles.count, 'ticket2.articles verify') assert_equal([], Tag.tag_list(object: 'Ticket', o_id: ticket2.id)) + ticket3 = Ticket.create( + title: "some title\n äöüß3", + group: Group.lookup(name: 'Users'), + customer: User.lookup(email: 'nicole.braun@zammad.org'), + state: Ticket::State.lookup(name: 'new'), + priority: Ticket::Priority.lookup(name: '2 normal'), + updated_by_id: 1, + created_by_id: 1, + ) + assert(ticket3, 'ticket3 created') + Ticket::Article.create( + ticket_id: ticket3.id, + from: 'some_sender@example.com', + to: 'some_recipient@example.com', + subject: 'some subject', + message_id: 'some@id', + content_type: 'text/html', + body: 'some message note
new line', + internal: false, + sender: Ticket::Article::Sender.find_by(name: 'Agent'), + type: Ticket::Article::Type.find_by(name: 'note'), + updated_by_id: 1, + created_by_id: 1, + ) + + assert_equal('some title äöüß3', ticket3.title, 'ticket3.title verify') + assert_equal('Users', ticket3.group.name, 'ticket3.group verify') + assert_equal('new', ticket3.state.name, 'ticket3.state verify') + assert_equal('2 normal', ticket3.priority.name, 'ticket3.priority verify') + assert_equal(1, ticket3.articles.count, 'ticket3.articles verify') + assert_equal([], Tag.tag_list(object: 'Ticket', o_id: ticket3.id)) + + Observer::Transaction.commit + + ticket3 = Ticket.lookup(id: ticket3.id) + assert_equal('some title äöüß3', ticket3.title, 'ticket3.title verify') + assert_equal('Users', ticket3.group.name, 'ticket3.group verify') + assert_equal('new', ticket3.state.name, 'ticket3.state verify') + assert_equal('3 high', ticket3.priority.name, 'ticket3.priority verify') + assert_equal(2, ticket3.articles.count, 'ticket3.articles verify') + assert_equal(%w(aa kk), Tag.tag_list(object: 'Ticket', o_id: ticket3.id)) + article3 = ticket3.articles.last + assert_match('Zammad ', article3.from) + assert_match('nicole.braun@zammad.org', article3.to) + assert_match('Thanks for your inquiry (some title äöüß3)!', article3.subject) + assert_match('Braun
some <b>title</b>', article3.body) + assert_match('> some message note
> new line', article3.body) + assert_no_match('> some message <b>note</b>
> new line', article3.body) + assert_equal('text/html', article3.content_type) + Trigger.destroy_all end From 48e084df3d4a6f18d5db228b8514b45ddacebdb9 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sun, 13 Nov 2016 22:16:17 +0100 Subject: [PATCH 3/4] Improved oauth admin area. --- .../_application_controller_table.coffee | 9 +- .../javascripts/app/controllers/api.coffee | 102 ++++++++++++++++-- .../javascripts/app/models/application.coffee | 9 +- app/assets/javascripts/app/views/api.jst.eco | 1 + app/controllers/application_controller.rb | 4 + app/controllers/applications_controller.rb | 12 ++- .../routes/{doorkeeper.rb => applications.rb} | 1 + 7 files changed, 119 insertions(+), 19 deletions(-) rename config/routes/{doorkeeper.rb => applications.rb} (86%) diff --git a/app/assets/javascripts/app/controllers/_application_controller_table.coffee b/app/assets/javascripts/app/controllers/_application_controller_table.coffee index b1e9cbee8..41bd1b3f8 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_table.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_table.coffee @@ -76,14 +76,15 @@ class App.ControllerTable extends App.Controller e.preventDefault() console.log('checkboxClick', e.target) - callbackHeader = (header) -> - console.log('current header is', header) + callbackHeader = (headers) -> + console.log('current header is', headers) # add new header item attribute = name: 'some name' display: 'Some Name' - header.push attribute - console.log('new header is', header) + headers.push attribute + console.log('new header is', headers) + headers callbackAttributes = (value, object, attribute, header, refObject) -> console.log('data of item col', value, object, attribute, header, refObject) diff --git a/app/assets/javascripts/app/controllers/api.coffee b/app/assets/javascripts/app/controllers/api.coffee index 50cd78ce1..bcc0ab6fc 100644 --- a/app/assets/javascripts/app/controllers/api.coffee +++ b/app/assets/javascripts/app/controllers/api.coffee @@ -34,20 +34,47 @@ class Index extends App.ControllerSubContent App.Setting.unsubscribe(@subscribeApplicationId) table = => + + callbackHeader = (headers) -> + attribute = + name: 'view' + display: 'View' + headers.splice(3, 0, attribute) + attribute = + name: 'token' + display: 'Token' + headers.splice(4, 0, attribute) + headers + + callbackViewAttributes = (value, object, attribute, header, refObject) -> + value = 'X' + value + + callbackTokenAttributes = (value, object, attribute, header, refObject) -> + value = 'X' + value + new App.ControllerTable( - el: @$('.js-appList') - model: App.Application - table_id: 'applications' - objects: App.Application.all() + el: @$('.js-appList') + model: App.Application + table_id: 'applications' + objects: App.Application.all() bindRow: events: 'click': @appEdit + bindCol: + view: + events: + 'click': @appView + token: + events: + 'click': @appToken + callbackHeader: [callbackHeader] + callbackAttributes: + view: [callbackViewAttributes] + token: [callbackTokenAttributes] ) table() - #App.Application.fetchFull( - # table - # clear: true - #) @subscribeApplicationId = App.Application.subscribe(table, initFetch: true, clear: true) @@ -82,6 +109,18 @@ class Index extends App.ControllerSubContent value = @PasswordAccess.prop('checked') App.Setting.set('api_password_access', value) + appToken: (id, e) -> + e.preventDefault() + new ViewAppTokenModal( + app: App.Application.find(id) + ) + + appView: (id, e) -> + e.preventDefault() + new ViewAppModal( + app: App.Application.find(id) + ) + appNew: (e) -> e.preventDefault() new App.ControllerGenericNew( @@ -107,4 +146,51 @@ class Index extends App.ControllerSubContent container: @el.closest('.content') ) +class ViewAppModal extends App.ControllerModal + headPrefix: 'App' + buttonSubmit: false + buttonCancel: true + shown: true + small: true + events: + 'click .js-select': 'selectAll' + + constructor: (params) -> + @head = params.app.name + super + + content: -> + "AppID: +
+ Secret: " + +class ViewAppTokenModal extends App.ControllerModal + headPrefix: 'Generate Token' + buttonSubmit: 'Generate Token' + buttonCancel: true + shown: true + small: true + events: + 'click .js-select': 'selectAll' + + constructor: (params) -> + @head = params.app.name + super + + content: -> + "#{App.i18n.translateContent('Generate Access Token for |%s|', App.Session.get().displayNameLong())}" + + onSubmit: => + @ajax( + id: 'application_token' + type: 'POST' + url: "#{@apiPath}/applications/token" + processData: true + data: JSON.stringify(id: @app.id) + success: (data, status, xhr) => + @contentInline = "#{App.i18n.translateContent('New Access Token is')}: " + @update() + @$('.js-submit').remove() + ) + App.Config.set('API', { prio: 1200, name: 'API', parent: '#system', target: '#system/api', controller: Index, permission: ['admin.api'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/models/application.coffee b/app/assets/javascripts/app/models/application.coffee index 4bad86d8b..e31faa35e 100644 --- a/app/assets/javascripts/app/models/application.coffee +++ b/app/assets/javascripts/app/models/application.coffee @@ -1,17 +1,16 @@ class App.Application extends App.Model - @configure 'Application', 'name', 'redirect_uri', 'uid', 'secret' + @configure 'Application', 'name', 'redirect_uri' @extend Spine.Model.Ajax @url: @apiPath + '/applications' @configure_attributes = [ { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false }, - { name: 'redirect_uri', display: 'Redirect URI', tag: 'textarea', limit: 250, null: false, note: 'Use one line per URI' }, - { name: 'uid', display: 'Application ID', tag: 'input', type: 'text', null: true, readonly: 1 }, - { name: 'secret', display: 'Application secret', tag: 'input', type: 'text', null: true }, + { name: 'redirect_uri', display: 'Callback URL', tag: 'textarea', limit: 250, null: false, note: 'Use one line per URI' }, + { name: 'clients', display: 'Clients', tag: 'input', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, ] @configure_overview = [ - 'name', 'uid' + 'name', 'redirect_uri', 'clients' ] @configure_delete = true diff --git a/app/assets/javascripts/app/views/api.jst.eco b/app/assets/javascripts/app/views/api.jst.eco index 86f412824..de29d157c 100644 --- a/app/assets/javascripts/app/views/api.jst.eco +++ b/app/assets/javascripts/app/views/api.jst.eco @@ -51,6 +51,7 @@ curl -u <%= @S('email') %>:some_password <%= @C('http_type') %>://<%= @C('fqdn') +

diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3c84b465c..d6c2a8451 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -308,6 +308,10 @@ class ApplicationController < ActionController::Base logger.debug "oauth2 token auth check '#{token}'" access_token = Doorkeeper::AccessToken.by_token(token) + if !access_token + raise Exceptions::NotAuthorized, 'Invalid token!' + end + # check expire if access_token.expires_in && (access_token.created_at + access_token.expires_in) < Time.zone.now raise Exceptions::NotAuthorized, 'OAuth2 token is expired!' diff --git a/app/controllers/applications_controller.rb b/app/controllers/applications_controller.rb index 00da58b5e..1b44c54c2 100644 --- a/app/controllers/applications_controller.rb +++ b/app/controllers/applications_controller.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ class ApplicationsController < ApplicationController before_action { authentication_check(permission: 'admin.api') } @@ -13,7 +13,9 @@ class ApplicationsController < ApplicationController if !assets[:Application] assets[:Application] = {} end - assets[:Application][item.id] = item.attributes + application = item.attributes + application[:clients] = Doorkeeper::AccessToken.where(application_id: item.id).count + assets[:Application][item.id] = application } render json: { record_ids: item_ids, @@ -25,6 +27,11 @@ class ApplicationsController < ApplicationController render json: all, status: :ok end + def token + access_token = Doorkeeper::AccessToken.create!(application_id: params[:id], resource_owner_id: current_user.id) + render json: { token: access_token.token }, status: :ok + end + def show application = Doorkeeper::Application.find(params[:id]) render json: application, status: :ok @@ -59,6 +66,7 @@ class ApplicationsController < ApplicationController params_data.delete('uid') params_data.delete('secret') params_data.delete('created_at') + params_data.delete('updated_at') params_data end end diff --git a/config/routes/doorkeeper.rb b/config/routes/applications.rb similarity index 86% rename from config/routes/doorkeeper.rb rename to config/routes/applications.rb index 9acd9e820..752e170ea 100644 --- a/config/routes/doorkeeper.rb +++ b/config/routes/applications.rb @@ -5,6 +5,7 @@ Zammad::Application.routes.draw do match api_path + '/applications/:id', to: 'applications#show', via: :get match api_path + '/applications', to: 'applications#create', via: :post match api_path + '/applications/:id', to: 'applications#update', via: :put + match api_path + '/applications/token', to: 'applications#token', via: :post # oauth2 provider routes use_doorkeeper do From bf3411daca315031c69c6c1bce1b8e51e3d3ee80 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sun, 13 Nov 2016 23:59:39 +0100 Subject: [PATCH 4/4] Implemented issue#356 - Automatically join users to organizations based on the email address. --- app/models/organization.rb | 12 +++ app/models/user.rb | 16 +++- db/migrate/20120101000001_create_base.rb | 3 + ...01_organization_domain_based_assignment.rb | 85 +++++++++++++++++++ db/seeds.rb | 69 +++++++++++++++ ...ganization_domain_based_assignment_test.rb | 78 +++++++++++++++++ 6 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20161112000001_organization_domain_based_assignment.rb create mode 100644 test/unit/organization_domain_based_assignment_test.rb diff --git a/app/models/organization.rb b/app/models/organization.rb index fcebf68c6..ad984b287 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -13,6 +13,9 @@ class Organization < ApplicationModel has_many :members, class_name: 'User' validates :name, presence: true + before_create :domain_cleanup + before_update :domain_cleanup + activity_stream_support permission: 'admin.role' history_support search_index_support @@ -21,6 +24,15 @@ class Organization < ApplicationModel private + def domain_cleanup + return if !domain + return if domain.empty? + domain.gsub!(/@/, '') + domain.gsub!(/\s*/, '') + domain.strip! + domain.downcase! + end + def cache_delete super diff --git a/app/models/user.rb b/app/models/user.rb index a4652568f..6d3714fc2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -33,7 +33,7 @@ class User < ApplicationModel include User::SearchIndex before_validation :check_name, :check_email, :check_login, :check_password - before_create :check_preferences_default, :validate_roles + before_create :check_preferences_default, :validate_roles, :domain_based_assignment before_update :check_preferences_default, :validate_roles after_create :avatar_for_email_check after_update :avatar_for_email_check @@ -856,6 +856,20 @@ returns } end + def domain_based_assignment + return if !email + return if organization_id + begin + domain = Mail::Address.new(email).domain + return if !domain + organization = Organization.find_by(domain: domain.downcase, domain_assignment: true) + return if !organization + self.organization_id = organization.id + rescue + return + end + end + def avatar_for_email_check return if !email return if email.empty? diff --git a/db/migrate/20120101000001_create_base.rb b/db/migrate/20120101000001_create_base.rb index ecbe5c8d4..9acd58459 100644 --- a/db/migrate/20120101000001_create_base.rb +++ b/db/migrate/20120101000001_create_base.rb @@ -125,6 +125,8 @@ class CreateBase < ActiveRecord::Migration create_table :organizations do |t| t.string :name, limit: 100, null: false t.boolean :shared, null: false, default: true + t.string :domain, limit: 250, null: true, default: '' + t.boolean :domain_assignment, null: false, default: false t.boolean :active, null: false, default: true t.string :note, limit: 250, null: true, default: '' t.integer :updated_by_id, null: false @@ -132,6 +134,7 @@ class CreateBase < ActiveRecord::Migration t.timestamps limit: 3, null: false end add_index :organizations, [:name], unique: true + add_index :organizations, [:domain] create_table :roles_users, id: false do |t| t.integer :user_id diff --git a/db/migrate/20161112000001_organization_domain_based_assignment.rb b/db/migrate/20161112000001_organization_domain_based_assignment.rb new file mode 100644 index 000000000..9d1763379 --- /dev/null +++ b/db/migrate/20161112000001_organization_domain_based_assignment.rb @@ -0,0 +1,85 @@ +class OrganizationDomainBasedAssignment < ActiveRecord::Migration + def up + # return if it's a new setup + return if !Setting.find_by(name: 'system_init_done') + + add_column :organizations, :domain, :string, limit: 250, null: true, default: '' + add_column :organizations, :domain_assignment, :boolean, null: false, default: false + add_index :organizations, [:domain] + + ObjectManager::Attribute.add( + force: true, + object: 'Organization', + name: 'domain_assignment', + display: 'Domain based assignment', + data_type: 'boolean', + data_option: { + null: true, + default: false, + note: 'Assign Users based on users domain.', + item_class: 'formGroup--halfSize', + options: { + true: 'yes', + false: 'no', + }, + translate: true, + }, + editable: false, + active: true, + screens: { + edit: { + Admin: { + null: false, + }, + }, + view: { + '-all-' => { + shown: true, + }, + }, + }, + to_create: false, + to_migrate: false, + to_delete: false, + position: 1410, + updated_by_id: 1, + created_by_id: 1, + ) + + ObjectManager::Attribute.add( + force: true, + object: 'Organization', + name: 'domain', + display: 'Domain', + data_type: 'input', + data_option: { + type: 'text', + maxlength: 150, + null: true, + item_class: 'formGroup--halfSize', + }, + editable: false, + active: true, + screens: { + edit: { + '-all-' => { + null: true, + }, + }, + view: { + '-all-' => { + shown: true, + }, + }, + }, + to_create: false, + to_migrate: false, + to_delete: false, + position: 1420, + updated_by_id: 1, + created_by_id: 1, + ) + + Cache.clear + end +end diff --git a/db/seeds.rb b/db/seeds.rb index 2ca89c295..52b61445c 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -4660,6 +4660,75 @@ ObjectManager::Attribute.add( position: 1400, ) +ObjectManager::Attribute.add( + force: true, + object: 'Organization', + name: 'domain_assignment', + display: 'Domain based assignment', + data_type: 'boolean', + data_option: { + null: true, + default: false, + note: 'Assign Users based on users domain.', + item_class: 'formGroup--halfSize', + options: { + true: 'yes', + false: 'no', + }, + translate: true, + }, + editable: false, + active: true, + screens: { + edit: { + Admin: { + null: false, + }, + }, + view: { + '-all-' => { + shown: true, + }, + }, + }, + to_create: false, + to_migrate: false, + to_delete: false, + position: 1410, +) + +ObjectManager::Attribute.add( + force: true, + object: 'Organization', + name: 'domain', + display: 'Domain', + data_type: 'input', + data_option: { + type: 'text', + maxlength: 150, + null: true, + item_class: 'formGroup--halfSize', + }, + editable: false, + active: true, + screens: { + edit: { + '-all-' => { + null: true, + }, + }, + view: { + '-all-' => { + shown: true, + }, + }, + }, + to_create: false, + to_migrate: false, + to_delete: false, + position: 1420, +) + ObjectManager::Attribute.add( force: true, object: 'Organization', diff --git a/test/unit/organization_domain_based_assignment_test.rb b/test/unit/organization_domain_based_assignment_test.rb new file mode 100644 index 000000000..df730b706 --- /dev/null +++ b/test/unit/organization_domain_based_assignment_test.rb @@ -0,0 +1,78 @@ +# encoding: utf-8 +require 'test_helper' + +class OrganizationDomainBasedAssignmentTest < ActiveSupport::TestCase + test 'organization based assignment' do + + organization1 = Organization.create_if_not_exists( + name: 'organization based assignment 1', + domain: '@examPle1.com ', + domain_assignment: true, + updated_by_id: 1, + created_by_id: 1, + ) + organization2 = Organization.create_if_not_exists( + name: 'organization based assignment 2', + domain: 'example2.com', + domain_assignment: false, + updated_by_id: 1, + created_by_id: 1, + ) + + roles = Role.where(name: 'Customer') + customer1 = User.create_or_update( + login: 'organization-based_assignment-customer1@example1.com', + firstname: 'Domain', + lastname: 'Agent1', + email: 'organization-based_assignment-customer1@example1.com', + password: 'customerpw', + active: true, + roles: roles, + updated_by_id: 1, + created_by_id: 1, + ) + assert_equal(organization1.id, customer1.organization_id) + + customer2 = User.create_or_update( + login: 'organization-based_assignment-customer2@example1.com', + firstname: 'Domain', + lastname: 'Agent2', + email: 'organization-based_assignment-customer2@example1.com', + password: 'customerpw', + active: true, + organization_id: organization2.id, + roles: roles, + updated_by_id: 1, + created_by_id: 1, + ) + assert_equal(organization2.id, customer2.organization_id) + + customer3 = User.create_or_update( + login: 'organization-based_assignment-customer3@example2.com', + firstname: 'Domain', + lastname: 'Agent2', + email: 'organization-based_assignment-customer3@example2.com', + password: 'customerpw', + active: true, + roles: roles, + updated_by_id: 1, + created_by_id: 1, + ) + assert_equal(nil, customer3.organization_id) + + customer4 = User.create_or_update( + login: 'organization-based_assignment-customer4', + firstname: 'Domain', + lastname: 'Agent2', + email: '@', + password: 'customerpw', + active: true, + roles: roles, + updated_by_id: 1, + created_by_id: 1, + ) + assert_equal(nil, customer4.organization_id) + + end + +end