diff --git a/Gemfile b/Gemfile index 87338c24f..ee7827cee 100644 --- a/Gemfile +++ b/Gemfile @@ -105,3 +105,6 @@ gem 'prawn' gem 'prawn-table' gem 'puma' + +# ical export +gem 'icalendar' diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 97b5066b7..bf9d7b363 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,14 +4,15 @@ class ApplicationController < ActionController::Base # http_basic_authenticate_with :name => "test", :password => "ttt" helper_method :current_user, - :authentication_check, - :config_frontend, - :is_role, - :model_create_render, - :model_update_render, - :model_restory_render, - :mode_show_rendeder, - :model_index_render + :authentication_check, + :authentication_check_action_token, + :config_frontend, + :is_role, + :model_create_render, + :model_update_render, + :model_restory_render, + :mode_show_rendeder, + :model_index_render skip_before_filter :verify_authenticity_token before_filter :set_user, :session_update @@ -193,6 +194,24 @@ class ApplicationController < ActionController::Base true end + def authentication_check_action_token(action) + + user = Token.check( + :action => action, + :name => params[:action_token], + ) + + if !user + puts params.inspect + response_access_deny + return + end + + current_user_set( user ) + + true + end + def is_role( role_name ) return false if !current_user return true if current_user.is_role( role_name ) diff --git a/app/controllers/ical_tickets_controller.rb b/app/controllers/ical_tickets_controller.rb new file mode 100644 index 000000000..bca5f24f5 --- /dev/null +++ b/app/controllers/ical_tickets_controller.rb @@ -0,0 +1,195 @@ +# Copyright (C) 2012-2015 Zammad Foundation, http://zammad-foundation.org/ + +require 'icalendar' + +class IcalTicketsController < ApplicationController + before_filter { authentication_check_action_token 'iCal' } + + # @path [GET] /ical/tickets_all/:action_token + # + # @summary Returns an iCal file with all tickets (open, new, pending, esclation) as events. + # + # @parameter action_token(required) [String] The action_token identifying the requested User privileged for 'iCal' action. + # + # @response_message 200 [String] iCal file ready to import in calendar applications. + # @response_message 401 Permission denied. + def all + + new_open_events_data = new_open_events_data_get + pending_events_data = pending_events_data_get + escalation_events_data = escalation_events_data_get + + events_data = new_open_events_data + pending_events_data + escalation_events_data + + events_data_to_ical( events_data ) + end + + # @path [GET] /ical/tickets_new_open/:action_token + # + # @summary Returns an iCal file with all new and open tickets as events. + # + # @parameter action_token(required) [String] The action_token identifying the requested User privileged for 'iCal' action. + # + # @response_message 200 [String] iCal file ready to import in calendar applications. + # @response_message 401 Permission denied. + def new_open + + events_data = new_open_events_data_get + + events_data_to_ical( events_data ) + end + + # @path [GET] /ical/tickets_pending/:action_token + # + # @summary Returns an iCal file with all pending tickets as events. + # + # @parameter action_token(required) [String] The action_token identifying the requested User privileged for 'iCal' action. + # + # @response_message 200 [String] iCal file ready to import in calendar applications. + # @response_message 401 Permission denied. + def pending + events_data = pending_events_data_get + + events_data_to_ical( events_data ) + end + + # @path [GET] /ical/ticket_escalation/:action_token + # + # @summary Returns an iCal file with all escalation times for tickets as events. + # + # @parameter action_token(required) [String] The action_token identifying the requested User privileged for 'iCal' action. + # + # @response_message 200 [String] iCal file ready to import in calendar applications. + # @response_message 401 Permission denied. + def escalation + events_data = escalation_events_data_get + + events_data_to_ical( events_data ) + end + + private + + def new_open_events_data_get + + condition = { + 'tickets.owner_id' => current_user.id, + 'tickets.state_id' => Ticket::State.where( + :state_type_id => Ticket::StateType.where( + :name => [ + 'new', + 'open', + ], + ), + ), + } + + tickets = Ticket.search( + :current_user => current_user, + :condition => condition, + ) + + events_data = [] + tickets.each do |ticket| + + event_data = {} + + event_data[:dtstart] = Icalendar::Values::Date.new( Date.today ) + event_data[:dtend] = Icalendar::Values::Date.new( Date.today ) + event_data[:summary] = "#{ ticket.state.name } ticket: '#{ ticket.title }'" + event_data[:description] = "T##{ ticket.number }" + + events_data.push event_data + end + + events_data + end + + def pending_events_data_get + + condition = { + 'tickets.owner_id' => current_user.id, + 'tickets.state_id' => Ticket::State.where( + :state_type_id => Ticket::StateType.where( + :name => [ + 'pending reminder', + 'pending action', + ], + ), + ), + } + + tickets = Ticket.search( + :current_user => current_user, + :condition => condition, + ) + + events_data = [] + tickets.each do |ticket| + + event_data = {} + + event_data[:dtstart] = Icalendar::Values::DateTime.new( ticket.pending_time ) + event_data[:dtend] = Icalendar::Values::DateTime.new( ticket.pending_time ) + event_data[:summary] = "#{ ticket.state.name } ticket: '#{ ticket.title }'" + event_data[:description] = "T##{ ticket.number }" + + events_data.push event_data + end + + events_data + end + + + def escalation_events_data_get + + condition = [ + 'tickets.escalation_time IS NOT NULL', + 'tickets.owner_id = ?', current_user.id + ] + + tickets = Ticket.search( + :current_user => current_user, + :condition => condition, + ) + + events_data = [] + tickets.each do |ticket| + + event_data = {} + + event_data[:dtstart] = Icalendar::Values::DateTime.new( ticket.escalation_time ) + event_data[:dtend] = Icalendar::Values::DateTime.new( ticket.escalation_time ) + event_data[:summary] = "ticket escalation: '#{ ticket.title }'" + event_data[:description] = "T##{ ticket.number }" + + events_data.push event_data + end + + events_data + end + + def events_data_to_ical(events_data) + + cal = Icalendar::Calendar.new + + events_data.each do |event_data| + + cal.event do |e| + e.dtstart = event_data[:dtstart] + e.dtend = event_data[:dtend] + e.summary = event_data[:summary] + e.description = event_data[:description] + e.ip_class = "PRIVATE" + end + + end + + send_data( + cal.to_ical, + :filename => 'zammad.ical', + :type => 'text/plain', + :disposition => 'inline' + ) + end + +end diff --git a/app/models/token.rb b/app/models/token.rb index 698b96a4a..a5c1c338a 100644 --- a/app/models/token.rb +++ b/app/models/token.rb @@ -12,7 +12,8 @@ class Token < ActiveRecord::Base return if !token # check if token is still valid - if token.created_at < 1.day.ago + if !token.persistent && + token.created_at < 1.day.ago # delete token token.delete diff --git a/config/routes/ical_tickets.rb b/config/routes/ical_tickets.rb new file mode 100644 index 000000000..b5cd0a9f0 --- /dev/null +++ b/config/routes/ical_tickets.rb @@ -0,0 +1,9 @@ +Zammad::Application.routes.draw do + api_path = Rails.configuration.api_path + + # ical ticket + match api_path + '/ical/tickets/:action_token', :to => 'ical_tickets#all', :via => :get + match api_path + '/ical/tickets_new_open/:action_token', :to => 'ical_tickets#new_open', :via => :get + match api_path + '/ical/tickets_pending/:action_token', :to => 'ical_tickets#pending', :via => :get + match api_path + '/ical/tickets_escalation/:action_token', :to => 'ical_tickets#escalation', :via => :get +end \ No newline at end of file diff --git a/db/migrate/20150220216145_token_persistent.rb b/db/migrate/20150220216145_token_persistent.rb new file mode 100644 index 000000000..6f3277096 --- /dev/null +++ b/db/migrate/20150220216145_token_persistent.rb @@ -0,0 +1,8 @@ +class TokenPersistent < ActiveRecord::Migration + def up + add_column :tokens, :persistent, :boolean + end + + def down + end +end diff --git a/test/unit/token_test.rb b/test/unit/token_test.rb new file mode 100644 index 000000000..bd7108dfb --- /dev/null +++ b/test/unit/token_test.rb @@ -0,0 +1,109 @@ +# encoding: utf-8 +require 'test_helper' + +class TokenTest < ActiveSupport::TestCase + test 'token' do + + tests = [ + + # test 1 + { + :test_name => 'invalid token', + :action => 'PasswordReset', + :name => '1NV4L1D', + :result => nil, + }, + + # test 2 + { + :test_name => 'fresh token', + :create => { + :user_id => 2, + :action => 'PasswordReset', + }, + :action => 'PasswordReset', + :result => true, + :verify => { + :firstname => 'Nicole', + :lastname => 'Braun', + :email => 'nicole.braun@zammad.org', + } + }, + + # test 3 + { + :test_name => 'two days but not persistent', + :create => { + :user_id => 2, + :action => 'PasswordReset', + :created_at => 2.day.ago, + }, + :action => 'PasswordReset', + :result => nil, + }, + + { + :test_name => 'two days but persistent', + :create => { + :user_id => 2, + :action => 'iCal', + :created_at => 2.day.ago, + :persistent => true, + }, + :action => 'iCal', + :result => true, + :verify => { + :firstname => 'Nicole', + :lastname => 'Braun', + :email => 'nicole.braun@zammad.org', + } + }, + ] + + tests.each { |test| + + if test[:create] + + #puts test[:test_name] + ': creating token '+ test[:create].inspect + + token = Token.create( + :action => test[:create][:action], + :user_id => test[:create][:user_id], + :created_at => test[:create][:created_at].to_s, + :persistent => test[:create][:persistent] + ) + + #puts test[:test_name] + ': created token ' + token.inspect + + test[:name] = token.name + end + + user = Token.check( + :action => test[:action], + :name => test[:name] + ) + + if test[:result] == true + if !user + assert( false, test[:test_name] + ': token verification failed' ) + else + test[:verify].each {|key, value| + assert_equal( user[key], value, 'verify' ) + } + end + else + assert_equal( test[:result], user, test[:test_name] + ': failed or not existing' ) + end + + if test[:name] + #puts test[:test_name] + ': deleting token '+ test[:name] + + token = Token.where( :name => test[:name] ).first + + if token + token.destroy + end + end + } + end +end \ No newline at end of file