summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock7
-rw-r--r--app/controllers/application_controller.rb10
-rw-r--r--app/controllers/controller_extension/flash.rb43
-rw-r--r--app/controllers/controller_extension/token_authentication.rb4
-rw-r--r--app/controllers/errors_controller.rb10
-rw-r--r--app/controllers/v1/certs_controller.rb8
-rw-r--r--app/controllers/v1/smtp_certs_controller.rb37
-rw-r--r--app/helpers/application_helper.rb3
-rw-r--r--app/helpers/core_helper.rb7
-rw-r--r--app/helpers/link_helper.rb49
-rw-r--r--app/models/account.rb8
-rw-r--r--app/models/client_certificate.rb8
-rw-r--r--app/models/email.rb5
-rw-r--r--app/models/identity.rb66
-rw-r--r--app/models/local_email.rb10
-rw-r--r--app/models/pgp_key.rb3
-rw-r--r--app/models/token.rb13
-rw-r--r--app/models/user.rb16
-rw-r--r--app/views/common/_action_buttons.html.haml12
-rw-r--r--app/views/common/_download_button.html.haml4
-rw-r--r--app/views/common/_home_page_buttons.html.haml4
-rw-r--r--app/views/errors/not_found.html.haml7
-rw-r--r--app/views/errors/server_error.html.haml7
-rw-r--r--app/views/layouts/_footer.html.haml2
-rw-r--r--app/views/layouts/_header.html.haml4
-rw-r--r--app/views/layouts/_messages.html.haml2
-rw-r--r--app/views/layouts/_navigation.html.haml2
-rw-r--r--app/views/pages/pricing.html.haml2
-rw-r--r--app/views/pages/terms-of-service.en.md4
-rw-r--r--app/views/users/_destroy_account.html.haml6
-rw-r--r--app/views/users/_overview.html.haml24
-rw-r--r--app/views/users/new.html.haml2
-rw-r--r--app/views/users/show.html.haml37
-rw-r--r--config/application.rb3
-rw-r--r--config/initializers/add_controller_methods.rb1
-rw-r--r--config/initializers/i18n.rb4
-rw-r--r--config/locales/en.yml46
-rw-r--r--config/locales/errors.en.yml11
-rw-r--r--config/locales/flash.en.yml10
-rw-r--r--config/locales/footer.en.yml7
-rw-r--r--config/locales/generic.en.yml25
-rw-r--r--config/locales/home.en.yml8
-rw-r--r--config/locales/simple_form.en.yml4
-rw-r--r--config/locales/users.en.yml20
-rw-r--r--config/routes.rb10
-rw-r--r--engines/billing/app/views/credit_card_info/edit.html.haml3
-rw-r--r--engines/billing/app/views/customer/_customer_data.html.haml2
-rw-r--r--engines/billing/app/views/customer/confirm.html.haml2
-rw-r--r--engines/billing/app/views/customer/edit.html.haml4
-rw-r--r--engines/billing/app/views/customer/show.html.haml6
-rw-r--r--engines/billing/app/views/subscriptions/destroy.html.haml2
-rw-r--r--engines/billing/app/views/subscriptions/index.html.haml2
-rw-r--r--engines/billing/app/views/subscriptions/show.html.haml3
-rw-r--r--engines/support/app/controllers/tickets_controller.rb103
-rw-r--r--engines/support/app/helpers/tickets_helper.rb14
-rw-r--r--engines/support/app/models/ticket.rb2
-rw-r--r--engines/support/app/models/ticket_comment.rb5
-rw-r--r--engines/support/app/views/tickets/_comment.html.haml5
-rw-r--r--engines/support/app/views/tickets/_comments.html.haml8
-rw-r--r--engines/support/app/views/tickets/_edit_form.html.haml32
-rw-r--r--engines/support/app/views/tickets/_new_comment_form.html.haml6
-rw-r--r--engines/support/app/views/tickets/_tabs.html.haml18
-rw-r--r--engines/support/app/views/tickets/edit.html.haml6
-rw-r--r--engines/support/app/views/tickets/index.html.haml10
-rw-r--r--engines/support/app/views/tickets/new.html.haml2
-rw-r--r--engines/support/app/views/tickets/show.html.haml9
-rw-r--r--engines/support/config/locales/en.yml125
-rw-r--r--engines/support/config/routes.rb14
-rw-r--r--engines/support/test/factories.rb12
-rw-r--r--engines/support/test/functional/ticket_comments_test.rb101
-rw-r--r--engines/support/test/functional/tickets_controller_test.rb204
-rw-r--r--engines/support/test/functional/tickets_list_test.rb122
-rw-r--r--engines/support/test/integration/create_ticket_test.rb14
-rw-r--r--lib/extensions/couchrest.rb4
-rw-r--r--lib/leap_web/version.rb2
-rw-r--r--public/404.html26
-rw-r--r--public/500.html25
-rw-r--r--test/functional/test_helpers_test.rb2
-rw-r--r--test/functional/v1/certs_controller_test.rb20
-rw-r--r--test/functional/v1/sessions_controller_test.rb2
-rw-r--r--test/functional/v1/smtp_certs_controller_test.rb36
-rw-r--r--test/integration/api/cert_test.rb30
-rw-r--r--test/integration/api/smtp_cert_test.rb52
-rw-r--r--test/integration/api/token_test.rb15
-rw-r--r--test/integration/browser/account_test.rb16
-rw-r--r--test/integration/browser/session_test.rb2
-rw-r--r--test/support/api_integration_test.rb26
-rw-r--r--test/support/assert_responses.rb30
-rw-r--r--test/support/auth_test_helper.rb9
-rw-r--r--test/support/browser_integration_test.rb1
-rw-r--r--test/test_helper.rb2
-rw-r--r--test/unit/identity_test.rb21
-rw-r--r--test/unit/token_test.rb23
-rw-r--r--test/unit/user_test.rb7
95 files changed, 1186 insertions, 577 deletions
diff --git a/Gemfile b/Gemfile
index 816cc4a..ae11e0e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -60,6 +60,7 @@ end
group :test, :development do
gem 'thin'
+ gem 'i18n-missing_translations'
end
group :assets do
diff --git a/Gemfile.lock b/Gemfile.lock
index 53e0d0b..d189022 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -8,13 +8,13 @@ GIT
PATH
remote: engines/billing
specs:
- leap_web_billing (0.5.1)
+ leap_web_billing (0.5.2)
braintree
PATH
remote: engines/support
specs:
- leap_web_help (0.5.1)
+ leap_web_help (0.5.2)
GEM
remote: https://rubygems.org/
@@ -122,6 +122,8 @@ GEM
hike (1.2.3)
http_accept_language (2.0.0)
i18n (0.6.9)
+ i18n-missing_translations (0.0.2)
+ i18n (~> 0.6.0)
journey (1.0.4)
jquery-rails (3.0.4)
railties (>= 3.0, < 5.0)
@@ -254,6 +256,7 @@ DEPENDENCIES
haml (~> 3.1.7)
haml-rails (~> 0.3.4)
http_accept_language
+ i18n-missing_translations
jquery-rails
json
kaminari (= 0.13.0)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 35d6cb4..a4560e2 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -23,16 +23,6 @@ class ApplicationController < ActionController::Base
json: {error: "The server failed to process your request. We'll look into it."}
end
- #
- # Allows us to pass through bold text to flash messages. See format_flash() for where this is reversed.
- #
- # TODO: move to core
- #
- def bold(str)
- "[b]#{str}[/b]"
- end
- helper_method :bold
-
##
## LOCALE
##
diff --git a/app/controllers/controller_extension/flash.rb b/app/controllers/controller_extension/flash.rb
new file mode 100644
index 0000000..1642141
--- /dev/null
+++ b/app/controllers/controller_extension/flash.rb
@@ -0,0 +1,43 @@
+module ControllerExtension::Flash
+ extend ActiveSupport::Concern
+
+ protected
+
+ def flash_for(resource, options = {})
+ return unless resource.changed?
+ add_flash_message_for resource
+ add_flash_errors_for resource if options[:with_errors]
+ end
+
+ def add_flash_message_for(resource)
+ message = flash_message_for(resource)
+ type = flash_type_for(resource)
+ if message.present?
+ flash[type] = message
+ end
+ end
+
+ def flash_message_for(resource)
+ I18n.t flash_i18n_key(resource),
+ scope: :flash,
+ cascade: true,
+ resource: resource.class.model_name.human
+ end
+
+ def flash_i18n_key(resource)
+ namespace = [action_name]
+ namespace += controller_path.split('/')
+ namespace << flash_type_for(resource)
+ namespace.join(".")
+ end
+
+ def flash_type_for(resource)
+ resource.valid? ? :success : :error
+ end
+
+ def add_flash_errors_for(resource)
+ return if resource.valid?
+ flash[:error] += "<br>"
+ flash[:error] += resource.errors.full_messages.join(". <br>")
+ end
+end
diff --git a/app/controllers/controller_extension/token_authentication.rb b/app/controllers/controller_extension/token_authentication.rb
index 6e0a6ce..b0ed624 100644
--- a/app/controllers/controller_extension/token_authentication.rb
+++ b/app/controllers/controller_extension/token_authentication.rb
@@ -2,8 +2,8 @@ module ControllerExtension::TokenAuthentication
extend ActiveSupport::Concern
def token
- @token ||= authenticate_with_http_token do |token_id, options|
- Token.find(token_id)
+ @token ||= authenticate_with_http_token do |token, options|
+ Token.find_by_token(token)
end
end
diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb
new file mode 100644
index 0000000..6c659e6
--- /dev/null
+++ b/app/controllers/errors_controller.rb
@@ -0,0 +1,10 @@
+# We render http errors ourselves so we can customize them
+class ErrorsController < ApplicationController
+ # 404
+ def not_found
+ end
+
+ # 500
+ def server_error
+ end
+end
diff --git a/app/controllers/v1/certs_controller.rb b/app/controllers/v1/certs_controller.rb
index 73409ef..b6d1d0b 100644
--- a/app/controllers/v1/certs_controller.rb
+++ b/app/controllers/v1/certs_controller.rb
@@ -3,7 +3,15 @@ class V1::CertsController < ApplicationController
before_filter :require_login, :unless => :anonymous_certs_allowed?
# GET /cert
+ # deprecated - we actually create a new cert and that can
+ # be reflected in the action. GET /cert will eventually go
+ # away and be replaced by POST /cert
def show
+ create
+ end
+
+ # POST /cert
+ def create
@cert = ClientCertificate.new(:prefix => service_level.cert_prefix)
render text: @cert.to_s, content_type: 'text/plain'
end
diff --git a/app/controllers/v1/smtp_certs_controller.rb b/app/controllers/v1/smtp_certs_controller.rb
new file mode 100644
index 0000000..377a49c
--- /dev/null
+++ b/app/controllers/v1/smtp_certs_controller.rb
@@ -0,0 +1,37 @@
+class V1::SmtpCertsController < ApplicationController
+
+ before_filter :require_login
+ before_filter :require_email_account
+ before_filter :fetch_identity
+
+ # POST /1/smtp_cert
+ def create
+ @cert = ClientCertificate.new prefix: current_user.email_address
+ @identity.register_cert(@cert)
+ @identity.save
+ render text: @cert.to_s, content_type: 'text/plain'
+ end
+
+ protected
+
+ #
+ # Filters
+ #
+
+ def require_email_account
+ access_denied unless service_level.provides? 'email'
+ end
+
+ def fetch_identity
+ @identity = current_user.identity
+ end
+
+ #
+ # Helper methods
+ #
+
+ def service_level
+ current_user.effective_service_level
+ end
+
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 90e649a..6de5e1b 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -40,8 +40,9 @@ module ApplicationHelper
end
end
+ # fairly strict sanitation for flash messages
def format_flash(msg)
- html_escape(msg).gsub('[b]', '<b>').gsub('[/b]', '</b>').html_safe
+ sanitize(msg, tags: %w(em strong b br), attributes: [])
end
end
diff --git a/app/helpers/core_helper.rb b/app/helpers/core_helper.rb
index 4126906..46e8fa4 100644
--- a/app/helpers/core_helper.rb
+++ b/app/helpers/core_helper.rb
@@ -10,4 +10,11 @@ module CoreHelper
render 'common/home_page_buttons'
end
+ #
+ # returns true if the configured service levels contain a level with a price attached
+ #
+ def paid_service_level?
+ APP_CONFIG[:service_levels].present? && APP_CONFIG[:service_levels].detect{|k,v| v['rate'].present?}
+ end
+
end
diff --git a/app/helpers/link_helper.rb b/app/helpers/link_helper.rb
new file mode 100644
index 0000000..55e392b
--- /dev/null
+++ b/app/helpers/link_helper.rb
@@ -0,0 +1,49 @@
+module LinkHelper
+
+ #
+ # markup for bootstrap button
+ #
+ # takes same arguments as link_to and adds a 'btn' class.
+ # In addition:
+ # * the name will be translated if it is a symbol
+ # * html_options[:type] will be converted into a btn-type class
+ #
+ # example:
+ # btn :home, home_path, type: [:large, :primary]
+ #
+ def btn(*args, &block)
+ html_options = extract_html_options!(args, &block)
+ type = Array(html_options.delete(:type))
+ type.map! {|t| "btn-#{t}"}
+ html_options[:class] = concat_classes(html_options[:class], 'btn', type)
+ args[0] = t(args[0]) if args[0].is_a?(Symbol)
+ link_to *args, html_options, &block
+ end
+
+ def destroy_btn(*args, &block)
+ html_options = extract_html_options!(args, &block)
+ confirmation = t "#{controller_symbol}.confirm.destroy.are_you_sure",
+ cascade: true
+ html_options.merge! method: :delete, confirm: confirmation
+ btn *args, html_options, &block
+ end
+
+ #
+ # concat_classes will combine classes in a fairly flexible way.
+ # it can handle nil, arrays, space separated strings
+ # it returns a space separated string of classes.
+ def concat_classes(*classes)
+ classes.compact!
+ classes.map {|c| c.respond_to?(:split) ? c.split(' ') : c }
+ classes.flatten!
+ classes.join ' '
+ end
+
+ def extract_html_options!(args)
+ if args.count > 2 or args.count > 1 && block_given?
+ args.extract_options!
+ else
+ {}
+ end
+ end
+end
diff --git a/app/models/account.rb b/app/models/account.rb
index cf998e4..32ed445 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -16,9 +16,13 @@ class Account
# Returns the user record so it can be used in views.
def self.create(attrs)
- @user = User.create(attrs).tap do |user|
- Identity.create_for user
+ @user = User.create(attrs)
+ if @user.persisted?
+ identity = @user.identity
+ identity.user_id = @user.id
+ identity.save
end
+ return @user
end
def update(attrs)
diff --git a/app/models/client_certificate.rb b/app/models/client_certificate.rb
index 76b07a2..63de9e1 100644
--- a/app/models/client_certificate.rb
+++ b/app/models/client_certificate.rb
@@ -43,8 +43,16 @@ class ClientCertificate
self.key.to_pem + self.cert.to_pem
end
+ def fingerprint
+ OpenSSL::Digest::SHA1.hexdigest(openssl_cert.to_der).scan(/../).join(':')
+ end
+
private
+ def openssl_cert
+ cert.openssl_body
+ end
+
def self.root_ca
@root_ca ||= begin
crt = File.read(APP_CONFIG[:client_ca_cert])
diff --git a/app/models/email.rb b/app/models/email.rb
index a9a503f..4090275 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -7,6 +7,11 @@ class Email < String
:message => "needs to be a valid email address"
}
+ # Make sure we can call Email.new(nil) and get an invalid email address
+ def initialize(s)
+ super(s.to_s)
+ end
+
def to_partial_path
"emails/email"
end
diff --git a/app/models/identity.rb b/app/models/identity.rb
index ad8c01e..2f6241c 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -8,9 +8,12 @@ class Identity < CouchRest::Model::Base
property :address, LocalEmail
property :destination, Email
property :keys, HashWithIndifferentAccess
+ property :cert_fingerprints, Hash
- validate :unique_forward
- validate :alias_available
+ validates :address, presence: true
+ validate :address_available
+ validates :destination, presence: true, if: :enabled?
+ validates :destination, uniqueness: {scope: :address}
validate :address_local_email
validate :destination_email
@@ -49,7 +52,8 @@ class Identity < CouchRest::Model::Base
def self.find_for(user, attributes = {})
attributes.reverse_merge! attributes_from_user(user)
- find_by_address_and_destination [attributes[:address], attributes[:destination]]
+ id = find_by_address_and_destination attributes.values_at(:address, :destination)
+ return id if id && id.user == user
end
def self.build_for(user, attributes = {})
@@ -66,7 +70,9 @@ class Identity < CouchRest::Model::Base
def self.disable_all_for(user)
Identity.by_user_id.key(user.id).each do |identity|
identity.disable
- identity.save
+ # if the identity is not unique anymore because the destination
+ # was reset to nil we destroy it.
+ identity.save || identity.destroy
end
end
@@ -90,7 +96,11 @@ class Identity < CouchRest::Model::Base
end
def enabled?
- self.destination && self.user_id
+ self.user_id
+ end
+
+ def disabled?
+ !enabled?
end
def disable
@@ -107,36 +117,50 @@ class Identity < CouchRest::Model::Base
write_attribute('keys', keys.merge(type => key.to_s))
end
+ def cert_fingerprints
+ read_attribute('cert_fingerprints') || Hash.new
+ end
+
+ def register_cert(cert)
+ today = DateTime.now.to_date.to_s
+ write_attribute 'cert_fingerprints',
+ cert_fingerprints.merge(cert.fingerprint => today)
+ end
+
# for LoginFormatValidation
def login
- self.address.handle
+ address.handle if address.present?
end
protected
- def unique_forward
- same = Identity.find_by_address_and_destination([address, destination])
- if same && same != self
- errors.add :base, "This alias already exists"
+ def address_available
+ blocking_identities = Identity.by_address.key(address).all
+ blocking_identities.delete self
+ if self.user
+ blocking_identities.reject! { |other| other.user == self.user }
end
- end
-
- def alias_available
- same = Identity.find_by_address(address)
- if same && same.user != self.user
- errors.add :base, "This email has already been taken"
+ if blocking_identities.any?
+ errors.add :address, :taken
end
end
def address_local_email
- return if address.valid? #this ensures it is LocalEmail
- self.errors.add(:address, address.errors.messages[:email].first) #assumes only one error
+ # caught by presence validation
+ return if address.blank?
+ return if address.valid?
+ address.errors.each do |attribute, error|
+ self.errors.add(:address, error)
+ end
end
def destination_email
- return if destination.nil? # this identity is disabled
- return if destination.valid? # this ensures it is Email
- self.errors.add(:destination, destination.errors.messages[:email].first) #assumes only one error #TODO
+ # caught by presence validation or this identity is disabled
+ return if destination.blank?
+ return if destination.valid?
+ destination.errors.each do |attribute, error|
+ self.errors.add(:destination, error)
+ end
end
end
diff --git a/app/models/local_email.rb b/app/models/local_email.rb
index 2b4c65e..ded7baf 100644
--- a/app/models/local_email.rb
+++ b/app/models/local_email.rb
@@ -58,11 +58,9 @@ class LocalEmail < Email
end
def handle_in_passwd?
- begin
- !!Etc.getpwnam(handle)
- rescue ArgumentError
- # handle was not found
- return false
- end
+ Etc.getpwnam(handle).present?
+ rescue ArgumentError
+ # handle was not found
+ return false
end
end
diff --git a/app/models/pgp_key.rb b/app/models/pgp_key.rb
index 66f8660..3384f4c 100644
--- a/app/models/pgp_key.rb
+++ b/app/models/pgp_key.rb
@@ -25,9 +25,10 @@ class PgpKey
# allow comparison with plain keyblock strings.
def ==(other)
+ return false if (self.present? != other.present?)
self.equal?(other) or
# relax the comparison on line ends.
- self.to_s.tr_s("\n\r", '') == other.tr_s("\r\n", '')
+ self.to_s.tr_s("\n\r", '') == other.tr_s("\n\r", '')
end
protected
diff --git a/app/models/token.rb b/app/models/token.rb
index e759ee3..ff2ad12 100644
--- a/app/models/token.rb
+++ b/app/models/token.rb
@@ -1,3 +1,5 @@
+require 'digest/sha2'
+
class Token < CouchRest::Model::Base
use_database :tokens
@@ -11,10 +13,16 @@ class Token < CouchRest::Model::Base
validates :user_id, presence: true
+ attr_accessor :token
+
design do
view :by_last_seen_at
end
+ def self.find_by_token(token)
+ self.find Digest::SHA512.hexdigest(token)
+ end
+
def self.expires_after
APP_CONFIG[:auth] && APP_CONFIG[:auth][:token_expires_after]
end
@@ -31,7 +39,7 @@ class Token < CouchRest::Model::Base
end
def to_s
- id
+ token
end
def authenticate
@@ -65,7 +73,8 @@ class Token < CouchRest::Model::Base
def initialize(*args)
super
if new_record?
- self.id = SecureRandom.urlsafe_base64(32).gsub(/^_*/, '')
+ self.token = SecureRandom.urlsafe_base64(32).gsub(/^_*/, '')
+ self.id = Digest::SHA512.hexdigest(self.token)
self.last_seen_at = Time.now
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 6678de6..f8b9ddc 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -24,7 +24,7 @@ class User < CouchRest::Model::Base
:uniqueness => true,
:if => :serverside?
- validate :login_is_unique_alias
+ validate :identity_is_valid
validates :password_salt, :password_verifier,
:format => { :with => /\A[\dA-Fa-f]+\z/, :message => "Only hex numbers allowed" }
@@ -42,6 +42,11 @@ class User < CouchRest::Model::Base
view :by_created_at
end # end of design
+ def reload
+ @identity = nil
+ super
+ end
+
def to_json(options={})
{
:login => login,
@@ -161,11 +166,10 @@ class User < CouchRest::Model::Base
# Validation Functions
##
- def login_is_unique_alias
- alias_identity = Identity.find_by_address(self.email_address)
- return if alias_identity.blank?
- if alias_identity.user != self
- errors.add(:login, "has already been taken")
+ def identity_is_valid
+ return if identity.valid?
+ identity.errors.each do |attribute, error|
+ self.errors.add(:login, error)
end
end
diff --git a/app/views/common/_action_buttons.html.haml b/app/views/common/_action_buttons.html.haml
index c74fcd1..266abe1 100644
--- a/app/views/common/_action_buttons.html.haml
+++ b/app/views/common/_action_buttons.html.haml
@@ -1,11 +1,11 @@
.home-buttons
.row-fluid.second
.login.span4
- %span.link= link_to(icon('ok-sign', icon_color) + t(:login), login_path, :class => 'btn')
- %span.info= t(:login_info)
+ %span.link= btn icon('ok-sign') + t(:login), login_path
+ %span.info= t(:login_info, default: "")
.signup.span4
- %span.link= link_to(icon('user', icon_color) + t(:signup), signup_path, :class => 'btn')
- %span.info= t(:signup_info)
+ %span.link= btn icon('user') + t(:signup), signup_path
+ %span.info= t(:signup_info, default: "")
.help.span4
- %span.link= link_to(icon('question-sign', icon_color) + t(:get_help), new_ticket_path, :class => 'btn')
- %span.info= t(:help_info)
+ %span.link= btn icon('question-sign') + t(:get_help), new_ticket_path
+ %span.info= t(:support_info, default: "")
diff --git a/app/views/common/_download_button.html.haml b/app/views/common/_download_button.html.haml
index e57c56b..9c26860 100644
--- a/app/views/common/_download_button.html.haml
+++ b/app/views/common/_download_button.html.haml
@@ -2,7 +2,7 @@
.row-fluid.first
.span2
.download.span8
- = link_to client_download_url, class: "btn btn-large btn-primary" do
+ = btn client_download_url, type: [:large, :primary] do
= big_icon('download')
- = t(:download_client)
+ = t(:download_bitmask)
.span2
diff --git a/app/views/common/_home_page_buttons.html.haml b/app/views/common/_home_page_buttons.html.haml
index 8c47983..fc6348e 100644
--- a/app/views/common/_home_page_buttons.html.haml
+++ b/app/views/common/_home_page_buttons.html.haml
@@ -1,8 +1,6 @@
-- icon_color = :black
-
= render 'common/download_button'
- if local_assigns[:divider]
.row-fluid
.span12
= render local_assigns[:divider]
-= render 'common/action_buttons', icon_color: icon_color
+= render 'common/action_buttons'
diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml
new file mode 100644
index 0000000..c7fec22
--- /dev/null
+++ b/app/views/errors/not_found.html.haml
@@ -0,0 +1,7 @@
+.hero-unit
+ %h1=t 'errors.title.page_not_found'
+ %h2=t 'errors.subtitle.page_not_found', default: ''
+ %p.lead=t 'errors.text.page_not_found', default: ''
+ %a.btn.btn-primary.btn-large{href:'/'}
+ %i.icon-home.icon-white
+ =t :home
diff --git a/app/views/errors/server_error.html.haml b/app/views/errors/server_error.html.haml
new file mode 100644
index 0000000..a4133da
--- /dev/null
+++ b/app/views/errors/server_error.html.haml
@@ -0,0 +1,7 @@
+.hero-unit
+ %h1=t 'errors.title.server_error'
+ %h2=t 'errors.subtitle.server_error', default: ''
+ %p.lead=t 'errors.text.server_error', default: ''
+ %a.btn.btn-primary.btn-large{href:'/'}
+ %i.icon-home.icon-white
+ =t :home
diff --git a/app/views/layouts/_footer.html.haml b/app/views/layouts/_footer.html.haml
index 340d36c..de53667 100644
--- a/app/views/layouts/_footer.html.haml
+++ b/app/views/layouts/_footer.html.haml
@@ -6,5 +6,5 @@
= link_to icon('info-sign') + t(:about), about_path
- if lookup_context.exists?('pages/contact')
= link_to icon('comment') + t(:contact), contact_path
- - if APP_CONFIG[:service_levels].present?
+ - if paid_service_level?
= link_to icon('shopping-cart') + t(:pricing), pricing_path
diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml
index a1dd47a..e827f60 100644
--- a/app/views/layouts/_header.html.haml
+++ b/app/views/layouts/_header.html.haml
@@ -2,9 +2,9 @@
%ul.nav.nav-tabs
= # this navigation isn't quite right. also, we will want to active for an identity controller once it is added.
%li{:class => ("active" if controller?('users', 'overviews') || params[:user_id])}
- = link_to t(:users), users_path
+ = link_to t(".users"), users_path
%li{:class => ("active" if controller?('tickets') && !params[:user_id])}
- = link_to t(:tickets), tickets_path
+ = link_to t(".tickets", cascade: true), tickets_path
%li
= link_to t(:logout), logout_path, :method => :delete
- if @user && @show_navigation
diff --git a/app/views/layouts/_messages.html.haml b/app/views/layouts/_messages.html.haml
index 7ff985f..18be54f 100644
--- a/app/views/layouts/_messages.html.haml
+++ b/app/views/layouts/_messages.html.haml
@@ -1,6 +1,6 @@
#messages
- flash.each do |name, msg|
- if msg.is_a?(String)
- %div{:class => "alert alert-#{name == :notice ? "success" : "error"}"}
+ %div{:class => "alert alert-#{name == :notice ? "success" : name}"}
%a.close{"data-dismiss" => "alert"} ×
= content_tag :div, format_flash(msg), :id => "flash_#{name}"
diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml
index b81c43d..94f71f7 100644
--- a/app/views/layouts/_navigation.html.haml
+++ b/app/views/layouts/_navigation.html.haml
@@ -2,6 +2,6 @@
= link_to_navigation t(:overview), @user, :active => (controller?(:users) && action?(:show))
= link_to_navigation t(:account_settings), edit_user_path(@user), :active => (controller?(:users) && !action?(:show))
- # will want link for identity settings
- = link_to_navigation t(:support_tickets), auto_tickets_path, :active => controller?(:tickets)
+ = link_to_navigation t(".tickets"), auto_tickets_path, :active => controller?(:tickets)
= link_to_navigation t(:billing_settings), billing_top_link(@user), :active => controller?(:customer, :payments, :subscriptions, :credit_card_info) if APP_CONFIG[:billing]
= link_to_navigation t(:logout), logout_path, :method => :delete
diff --git a/app/views/pages/pricing.html.haml b/app/views/pages/pricing.html.haml
index e339d27..983501e 100644
--- a/app/views/pages/pricing.html.haml
+++ b/app/views/pages/pricing.html.haml
@@ -1,5 +1,5 @@
%h1= t(:pricing)
-%p= link_to(icon('user') + t(:signup), signup_path, :class => 'btn')
+%p= btn icon('user') + t(:signup), signup_path
- levels = APP_CONFIG[:service_levels]
- if levels
diff --git a/app/views/pages/terms-of-service.en.md b/app/views/pages/terms-of-service.en.md
index 7b57027..93490b7 100644
--- a/app/views/pages/terms-of-service.en.md
+++ b/app/views/pages/terms-of-service.en.md
@@ -3,8 +3,8 @@
This document is our Terms of Service, which describes what activities are allowed, under what conditions we may terminate your account, and asserts our limited liability. It applies to all interactions with **<%=APP_CONFIG[:domain]%>**. Your use of **<%=APP_CONFIG[:domain]%>** services will constitute your agreement to these Terms of Service.
<p class="alert alert-info">
- <b>Summary:</b><br/>
- (1) If you do anything truly evil, we will terminate your account.<br/>
+ <b>Summary:</b><br>
+ (1) If you do anything truly evil, we will terminate your account.<br>
(2) We are not liable for any damages related to the use of this service.
</p>
diff --git a/app/views/users/_destroy_account.html.haml b/app/views/users/_destroy_account.html.haml
index 445f3c4..a2c4ddd 100644
--- a/app/views/users/_destroy_account.html.haml
+++ b/app/views/users/_destroy_account.html.haml
@@ -8,20 +8,20 @@
- else
= t(:admin_destroy_account, :username => @user.login)
%p= t(:destroy_account_info)
-= link_to user_path(@user), :method => :delete, :confirm => t(:are_you_sure), :class => "btn btn-danger" do
+= destroy_btn user_path(@user), :type => "danger" do
%i.icon-remove.icon-white
= t(:destroy_my_account)
- if @user != current_user and @user.enabled?
%legend
= t(:deactivate_account, :username => @user.login)
%p= t(:deactivate_description)
- = link_to deactivate_user_path(@user), :method => :post, :class => "btn btn-warning" do
+ = btn deactivate_user_path(@user), :method => :post, :type => "warning" do
%i.icon-pause.icon-white
= t(:deactivate)
- elsif @user != current_user and !@user.enabled?
%legend
= t(:enable_account, :username => @user.login)
%p= t(:enable_description)
- = link_to enable_user_path(@user), :method => :post, :class => "btn btn-warning" do
+ = btn enable_user_path(@user), :method => :post, :type => "warning" do
%i.icon-ok.icon-white
= t(:enable)
diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml
new file mode 100644
index 0000000..e38fdc8
--- /dev/null
+++ b/app/views/users/_overview.html.haml
@@ -0,0 +1,24 @@
+.overview
+
+ %h2.first= t(".welcome", username: @user.login, cascade: true)
+
+ - if admin?
+ %p
+ = t(:created)
+ = @user.created_at
+ %br
+ = t(:updated)
+ = @user.updated_at
+ %br
+ = t(:enabled)
+ = @user.enabled?
+
+ %p= t(:overview_intro, default: "")
+
+ %ul.unstyled
+ %li= icon('user') + link_to(t(".account"), edit_user_path(@user))
+ - # %li= icon('envelope') + link_to(t(:overview_email), {insert path for user identities, presuambly}
+ %li= icon('question-sign') + link_to(t(".tickets"), user_tickets_path(@user))
+ %li= icon('shopping-cart') + link_to(t(".billing"), billing_top_link(@user)) if APP_CONFIG[:billing]
+
+
diff --git a/app/views/users/new.html.haml b/app/views/users/new.html.haml
index bc36068..41a9d55 100644
--- a/app/views/users/new.html.haml
+++ b/app/views/users/new.html.haml
@@ -17,5 +17,5 @@
= f.input :login, :label => t(:username), :required => false, :input_html => { :id => :srp_username }
= f.input :password, :required => false, :validate => true, :input_html => { :id => :srp_password }
= f.input :password_confirmation, :required => false, :validate => true, :input_html => { :id => :srp_password_confirmation }
- = f.button :wrapped, value: t(:signup), cancel: home_path
+ = f.button :wrapped, cancel: home_path
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index ddc33ab..da8e467 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,30 +1,7 @@
-.overview
-
- %h2.first= t(:overview_welcome, :username => @user.login)
-
- - if admin?
- %p
- = t(:created)
- = @user.created_at
- %br
- = t(:updated)
- = @user.updated_at
- %br
- = t(:enabled)
- = @user.enabled?
-
- %p= t(:overview_intro)
-
- %ul.unstyled
- %li= icon('user') + link_to(t(:overview_account), edit_user_path(@user))
- - # %li= icon('envelope') + link_to(t(:overview_email), {insert path for user identities, presuambly}
- %li= icon('question-sign') + link_to(t(:overview_tickets), user_tickets_path(@user))
- %li= icon('shopping-cart') + link_to(t(:overview_billing), billing_top_link(@user)) if APP_CONFIG[:billing]
-
-
- .container-fluid
- .row-fluid
- %h4 To use bitmask services:
- = link_to client_download_url, class: "btn btn-primary" do
- %i.icon-arrow-down.icon-white
- = t(:download_client)
+= render 'overview'
+.container-fluid
+ .row-fluid
+ %h4 To use bitmask services:
+ = btn client_download_url, type: "primary" do
+ %i.icon-arrow-down.icon-white
+ = t(:download_bitmask)
diff --git a/config/application.rb b/config/application.rb
index 2c9c55a..8555f48 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -91,5 +91,8 @@ module LeapWeb
## see initializers/customization.rb
##
config.paths['app/views'].unshift "config/customization/views"
+
+ # handle http errors ourselves
+ config.exceptions_app = self.routes
end
end
diff --git a/config/initializers/add_controller_methods.rb b/config/initializers/add_controller_methods.rb
index f572ecb..03e8393 100644
--- a/config/initializers/add_controller_methods.rb
+++ b/config/initializers/add_controller_methods.rb
@@ -1,4 +1,5 @@
ActiveSupport.on_load(:application_controller) do
include ControllerExtension::Authentication
include ControllerExtension::TokenAuthentication
+ include ControllerExtension::Flash
end
diff --git a/config/initializers/i18n.rb b/config/initializers/i18n.rb
index c277a22..b209d00 100644
--- a/config/initializers/i18n.rb
+++ b/config/initializers/i18n.rb
@@ -8,3 +8,7 @@ MATCH_LOCALE = /(#{I18n.available_locales.join('|')})/
# I18n.available_locales is always an array of symbols, but for comparison with
# params we need it to be an array of strings.
LOCALES_STRING = I18n.available_locales.map(&:to_s)
+
+# enable using the cascade option
+# see svenfuchs.com/2011/2/11/organizing-translations-with-i18n-cascade-and-i18n-missingtranslations
+I18n::Backend::Simple.send(:include, I18n::Backend::Cascade)
diff --git a/config/locales/en.yml b/config/locales/en.yml
deleted file mode 100644
index cebf075..0000000
--- a/config/locales/en.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-en:
- privacy_policy: Privacy Policy
- terms_of_service: Terms of Service
- pricing: Pricing
- about: About Us
- contact: Contact
- no_such_thing: "No such %{thing}."
- thing_was_successfully_created: "%{thing} was successfully created."
- create_thing: "Create %{thing}"
-
- overview: "Overview"
- user_control_panel: "user control panel"
-
- created: "Created"
- created_by_on: "Created by %{user} on %{time}"
- updated: "Updated"
-
- none: "None"
- unknown: "Unknown"
- admin: "Admin"
- anonymous: "Anonymous"
- save: "Save"
- add: "Add"
- remove: "Remove"
- changes_saved: "Changes saved successfully."
- are_you_sure: "Are you sure? This change cannot be undone."
-
- download_client: "Download Bitmask"
- client_info: "The Bitmask application allows you to use %{provider} services."
- all_downloads_info: "It is available for %{clients}."
- other_downloads_info: "Bitmask is also available for %{clients}."
- login_info: "Log in to change your account settings, create support tickets, and manage payments."
- signup_info: "Get a user account via this website. We recommend registering via the Bitmask application instead unless you are only using Bitmask for Android."
- welcome: "Welcome to %{provider}."
- get_help: "Get Help"
- help_info: "Can't login? Create a new support ticket anonymously."
- example_email: 'user@domain.org'
- os:
- linux32: "Linux (32 bit)"
- linux64: "Linux (64 bit)"
- linux: "GNU/Linux"
- windows: "Windows"
- android: "Android"
- osx: "Mac OS"
- other: "(not available for your OS)"
-
diff --git a/config/locales/errors.en.yml b/config/locales/errors.en.yml
new file mode 100644
index 0000000..93feab1
--- /dev/null
+++ b/config/locales/errors.en.yml
@@ -0,0 +1,11 @@
+en:
+ errors:
+ title:
+ not_found: Page not found.
+ server_error: Ouch!
+ subtitle:
+ not_found: "The page you were looking for doesn't exist."
+ server_error: We ran into a server error.
+ text:
+ not_found: "You may have mistyped the address or the page may have moved."
+ server_error: The problem has been logged and we will look into it.
diff --git a/config/locales/flash.en.yml b/config/locales/flash.en.yml
new file mode 100644
index 0000000..7ad28f8
--- /dev/null
+++ b/config/locales/flash.en.yml
@@ -0,0 +1,10 @@
+en:
+ flash:
+ success: "%{resource} was successfully saved."
+ error: "%{resource} could not be saved."
+ create:
+ success: "%{resource} was successfully created."
+ error: "%{resource} could not be created."
+ update:
+ success: "%{resource} was successfully updated."
+ error: "%{resource} could not be updated."
diff --git a/config/locales/footer.en.yml b/config/locales/footer.en.yml
new file mode 100644
index 0000000..65f8ab2
--- /dev/null
+++ b/config/locales/footer.en.yml
@@ -0,0 +1,7 @@
+en:
+ # layout/footer
+ privacy_policy: Privacy Policy
+ terms_of_service: Terms of Service
+ pricing: Pricing
+ about: About Us
+ contact: Contact
diff --git a/config/locales/generic.en.yml b/config/locales/generic.en.yml
new file mode 100644
index 0000000..14dafac
--- /dev/null
+++ b/config/locales/generic.en.yml
@@ -0,0 +1,25 @@
+en:
+ signup: "Sign Up"
+ login: "Log In"
+ logout: "Log Out"
+
+ cancel: "Cancel"
+
+ created: "Created"
+ created_by_on: "Created by %{user} on %{time}"
+ updated: "Updated"
+
+ none: "None"
+ unknown: "Unknown"
+ admin: "Admin"
+ anonymous: "Anonymous"
+ save: "Save"
+ add: "Add"
+ remove: "Remove"
+ changes_saved: "Changes saved successfully."
+ are_you_sure: "Are you sure? This change cannot be undone."
+
+ example_email: 'user@domain.org'
+
+ no_such_thing: "No such %{thing}."
+ create_thing: "Create %{thing}"
diff --git a/config/locales/home.en.yml b/config/locales/home.en.yml
new file mode 100644
index 0000000..c3cdfb1
--- /dev/null
+++ b/config/locales/home.en.yml
@@ -0,0 +1,8 @@
+en:
+ home: Home
+ welcome: "Welcome to %{provider}."
+ download_bitmask: "Download Bitmask"
+
+ login_info: "Log in to change your account settings, create support tickets, and manage payments."
+ signup_info: "Get a user account via this website. We recommend registering via the Bitmask application instead unless you are only using Bitmask for Android."
+ support_info: "Can't login? Create a new support ticket anonymously."
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index 5d0c675..4b35883 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -29,8 +29,8 @@ en:
helpers:
submit:
user:
- create: "Sign up"
+ create: "Sign Up"
update: "Save"
session:
- create: "Log in"
+ create: "Log In"
diff --git a/config/locales/users.en.yml b/config/locales/users.en.yml
index 0ca5a73..f0fcb3d 100644
--- a/config/locales/users.en.yml
+++ b/config/locales/users.en.yml
@@ -1,10 +1,8 @@
en:
+ layouts:
+ users: "Users"
+ user_control_panel: "user control panel"
account_settings: "Account Settings"
- logout: "Logout"
- none: "None"
- signup: "Sign Up"
- cancel: "Cancel"
- login: "Log In"
username: "Username"
password: "Password"
change_password: "Change Password"
@@ -42,11 +40,13 @@ en:
#
# overview
#
- overview_welcome: "Welcome %{username}."
- overview_intro: "From this user control panel, you can:"
- overview_tickets: "Create and check support tickets."
- overview_email: "Modify email settings."
- overview_account: "Destroy your account."
+ users:
+ overview:
+ welcome: "Welcome %{username}."
+ intro: "From this user control panel, you can:"
+ tickets: "Create and check support tickets."
+ email: "Modify email settings."
+ account: "Destroy your account."
#
# rails
diff --git a/config/routes.rb b/config/routes.rb
index 745b97d..4ccfe62 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -6,6 +6,13 @@ LeapWeb::Application.routes.draw do
root :to => "home#index"
get '(:locale)' => 'home#index', :locale => MATCH_LOCALE, :as => 'home'
+ #
+ # HTTP Error Handling
+ # instead of the default error pages use the errors controller and views
+ #
+ match '/404' => 'errors#not_found'
+ match '/500' => 'errors#server_error'
+
scope "(:locale)", :locale => MATCH_LOCALE, :controller => 'pages', :action => 'show' do
get 'privacy-policy', :as => 'privacy_policy'
get 'terms-of-service', :as => 'terms_of_service'
@@ -25,7 +32,8 @@ LeapWeb::Application.routes.draw do
delete "logout" => "sessions#destroy", :as => "logout"
resources :users, :only => [:create, :update, :destroy, :index]
resources :messages, :only => [:index, :update]
- resource :cert, :only => [:show]
+ resource :cert, :only => [:show, :create]
+ resource :smtp_cert, :only => [:create]
resource :service, :only => [:show]
end
diff --git a/engines/billing/app/views/credit_card_info/edit.html.haml b/engines/billing/app/views/credit_card_info/edit.html.haml
index bd86a4c..9e44344 100644
--- a/engines/billing/app/views/credit_card_info/edit.html.haml
+++ b/engines/billing/app/views/credit_card_info/edit.html.haml
@@ -13,5 +13,4 @@
%dt= f.label :cvv, 'CVV'
%dd= f.text_field :cvv
= hidden_field_tag :tr_data, @tr_data
- = f.submit 'Save Payment Info', :class => :btn
- = link_to t(:cancel), edit_customer_path(@user.id), :class => :btn
+ = f.button :wrapped, 'Save Payment Info', cancel: edit_customer_path(@user.id)
diff --git a/engines/billing/app/views/customer/_customer_data.html.haml b/engines/billing/app/views/customer/_customer_data.html.haml
index e9df040..439ae5c 100644
--- a/engines/billing/app/views/customer/_customer_data.html.haml
+++ b/engines/billing/app/views/customer/_customer_data.html.haml
@@ -13,4 +13,4 @@
%dt Expiration Date
%dd= @default_cc.expiration_date
- if current_user == @user
- = link_to t(:edit_saved_data), edit_customer_path(@user.id), :class => :btn
+ = btn t(:edit_saved_data), edit_customer_path(@user.id)
diff --git a/engines/billing/app/views/customer/confirm.html.haml b/engines/billing/app/views/customer/confirm.html.haml
index 877a8ac..eab9616 100644
--- a/engines/billing/app/views/customer/confirm.html.haml
+++ b/engines/billing/app/views/customer/confirm.html.haml
@@ -11,4 +11,4 @@
- @result.customer.credit_cards.each do |cc|
%dd= cc.masked_number
- customer = Customer.find_by_user_id(@user.id)
-= link_to 'View Customer Info', show_customer_path(@user.id), :class=> :btn \ No newline at end of file
+= btn 'View Customer Info', show_customer_path(@user.id)
diff --git a/engines/billing/app/views/customer/edit.html.haml b/engines/billing/app/views/customer/edit.html.haml
index e882d53..f461fcc 100644
--- a/engines/billing/app/views/customer/edit.html.haml
+++ b/engines/billing/app/views/customer/edit.html.haml
@@ -16,8 +16,8 @@
%dt= t(:stored_credit_card)
%dd
= @default_cc.masked_number
- = link_to t(:change_credit_card), edit_credit_card_info_path(:id => @default_cc.token), :class => :btn
+ = btn t(:change_credit_card), edit_credit_card_info_path(:id => @default_cc.token)
= hidden_field_tag :tr_data, @tr_data
.form-actions
= f.submit t(:save_customer_info), :class => 'btn btn-primary'
- = link_to t(:cancel), show_customer_path(@user), :class=> :btn
+ = btn t(:cancel), show_customer_path(@user)
diff --git a/engines/billing/app/views/customer/show.html.haml b/engines/billing/app/views/customer/show.html.haml
index ec1779c..ce4d01a 100644
--- a/engines/billing/app/views/customer/show.html.haml
+++ b/engines/billing/app/views/customer/show.html.haml
@@ -9,7 +9,7 @@
- break if counter > 2 # not ruby-like, but object is a Braintree::ResourceCollection so limited methods available
= render :partial => "payments/transaction_details", :locals => {:transaction => t}
- counter += 1
- = link_to t(:transaction_history), user_payments_path(@user)
+ = btn :transaction_history, user_payments_path(@user)
%legend= t(:subscriptions)
- if @active_subscription
= render :partial => "subscriptions/subscription_details", :locals => {:subscription => @active_subscription}
@@ -19,9 +19,9 @@
- if current_user == @user
%p
.form-actions
- = link_to t(:subscribe_to_plan), new_subscription_path, :class => :btn
+ = btn :subscribe_to_plan, new_subscription_path
%p
= link_to t(:all_subscriptions), user_subscriptions_path(@user)
.form-actions
- = link_to t(:make_donation), new_payment_path, :class => 'btn btn-primary'
+ = btn :make_donation, new_payment_path, :type => 'primary'
diff --git a/engines/billing/app/views/subscriptions/destroy.html.haml b/engines/billing/app/views/subscriptions/destroy.html.haml
index 44b4333..e6e8578 100644
--- a/engines/billing/app/views/subscriptions/destroy.html.haml
+++ b/engines/billing/app/views/subscriptions/destroy.html.haml
@@ -4,4 +4,4 @@
Error:
= @result.message
%p
- = link_to 'Customer Information', show_customer_path(@user), :class=> :btn \ No newline at end of file
+ = btn 'Customer Information', show_customer_path(@user)
diff --git a/engines/billing/app/views/subscriptions/index.html.haml b/engines/billing/app/views/subscriptions/index.html.haml
index 3d4e8fd..1e3fb25 100644
--- a/engines/billing/app/views/subscriptions/index.html.haml
+++ b/engines/billing/app/views/subscriptions/index.html.haml
@@ -5,4 +5,4 @@
- pending_active_pastdue = true
= render :partial => "subscription_details", :locals => {:subscription => s}
- if !pending_active_pastdue and @user == current_user
- = link_to 'subscribe to plan', new_subscription_path, :class => :btn \ No newline at end of file
+ = btn 'subscribe to plan', new_subscription_path
diff --git a/engines/billing/app/views/subscriptions/show.html.haml b/engines/billing/app/views/subscriptions/show.html.haml
index 2699db9..246ebf0 100644
--- a/engines/billing/app/views/subscriptions/show.html.haml
+++ b/engines/billing/app/views/subscriptions/show.html.haml
@@ -3,4 +3,5 @@
Current
Subscription
= render :partial => "subscription_details", :locals => {:subscription => @subscription}
-= link_to t(:cancel_subscription), user_subscription_path(@user, @subscription.id), :confirm => t(:are_you_sure), :method => :delete, :class => 'btn btn-danger' if allow_cancel_subscription(@subscription)
+- if allow_cancel_subscription(@subscription)
+ = destroy_btn :cancel_subscription, user_subscription_path(@user, @subscription.id), type: 'danger'
diff --git a/engines/support/app/controllers/tickets_controller.rb b/engines/support/app/controllers/tickets_controller.rb
index 99357ab..1ccbd16 100644
--- a/engines/support/app/controllers/tickets_controller.rb
+++ b/engines/support/app/controllers/tickets_controller.rb
@@ -4,10 +4,10 @@ class TicketsController < ApplicationController
respond_to :html, :json
#has_scope :open, :type => boolean
- before_filter :require_login, :only => [:index]
- before_filter :fetch_ticket, :only => [:show, :update, :destroy]
- before_filter :require_ticket_access, :only => [:show, :update, :destroy]
before_filter :fetch_user
+ before_filter :require_login, :only => [:index]
+ before_filter :fetch_ticket, except: [:new, :create, :index]
+ before_filter :require_ticket_access, except: [:new, :create]
before_filter :set_title
def new
@@ -23,13 +23,13 @@ class TicketsController < ApplicationController
@ticket.comments.last.posted_by = current_user.id
@ticket.comments.last.private = false unless admin?
@ticket.created_by = current_user.id
- if @ticket.save
- flash[:notice] = t(:thing_was_successfully_created, :thing => t(:ticket))
- if !logged_in?
- flash[:notice] += " " + t(:access_ticket_text, :full_url => ticket_url(@ticket.id))
- end
+ flash_for @ticket
+ if @ticket.save && !logged_in?
+ flash[:success] += t 'tickets.access_ticket_text',
+ full_url: ticket_url(@ticket.id),
+ default: ""
end
- respond_with(@ticket, :location => auto_ticket_path(@ticket))
+ respond_with @ticket, :location => auto_ticket_path(@ticket)
end
def show
@@ -40,35 +40,33 @@ class TicketsController < ApplicationController
end
end
- def update
- if params[:button] == 'close'
- @ticket.is_open = false
- @ticket.save
- redirect_to_tickets
- elsif params[:button] == 'open'
- @ticket.is_open = true
- @ticket.save
- redirect_to auto_ticket_path(@ticket)
- else
- @ticket.attributes = cleanup_ticket_params(params[:ticket])
+ def close
+ @ticket.close
+ @ticket.save
+ redirect_to redirection_path
+ end
- if params[:button] == 'reply_and_close'
- @ticket.close
- end
+ def open
+ @ticket.reopen
+ @ticket.save
+ redirect_to redirection_path
+ end
- if @ticket.comments_changed?
- @ticket.comments.last.posted_by = current_user.id
- @ticket.comments.last.private = false unless admin?
- end
+ def update
+ @ticket.attributes = cleanup_ticket_params(params[:ticket])
- if @ticket.changed? and @ticket.save
- flash[:notice] = t(:changes_saved)
- redirect_to_tickets
- else
- flash[:error] = @ticket.errors.full_messages.join(". ") if @ticket.changed?
- redirect_to auto_ticket_path(@ticket)
- end
+ if params[:button] == 'reply_and_close'
+ @ticket.close
end
+
+ if @ticket.comments_changed?
+ @ticket.comments.last.posted_by = current_user.id
+ @ticket.comments.last.private = false unless admin?
+ end
+
+ flash_for @ticket, with_errors: true
+ @ticket.save
+ respond_with @ticket, location: redirection_path
end
def index
@@ -85,25 +83,20 @@ class TicketsController < ApplicationController
protected
def set_title
- @title = t(:tickets)
+ @title = t("layouts.title.tickets")
end
private
#
- # redirects to ticket index, if appropriate.
- # otherwise, just redirects to @ticket
+ # ticket index, if appropriate.
+ # otherwise, just @ticket
#
- def redirect_to_tickets
- if logged_in?
- if params[:button] == t(:reply_and_close)
- redirect_to auto_tickets_path
- else
- redirect_to auto_ticket_path(@ticket)
- end
+ def redirection_path
+ if logged_in? && params[:button] == t(:reply_and_close)
+ auto_tickets_path
else
- # if we are not logged in, there is no index to view
- redirect_to auto_ticket_path(@ticket)
+ auto_ticket_path(@ticket)
end
end
@@ -136,14 +129,24 @@ class TicketsController < ApplicationController
end
def ticket_access?
- admin? or
- @ticket.created_by.blank? or
- current_user.id == @ticket.created_by
+ admin? or (
+ @ticket &&
+ @ticket.created_by.blank?
+ ) or (
+ @ticket &&
+ @ticket.created_by == current_user.id
+ ) or (
+ @ticket.nil? &&
+ @user &&
+ @user.id == current_user.id
+ )
end
def fetch_user
if params[:user_id]
@user = User.find(params[:user_id])
+ else
+ @user = current_user
end
end
@@ -153,7 +156,7 @@ class TicketsController < ApplicationController
def search_options(params)
params.merge(
:admin_status => params[:user_id] ? 'mine' : 'all',
- :user_id => @user ? @user.id : current_user.id,
+ :user_id => @user.id,
:is_admin => admin?
)
end
diff --git a/engines/support/app/helpers/tickets_helper.rb b/engines/support/app/helpers/tickets_helper.rb
index 7af50d6..11b02e4 100644
--- a/engines/support/app/helpers/tickets_helper.rb
+++ b/engines/support/app/helpers/tickets_helper.rb
@@ -35,13 +35,7 @@ module TicketsHelper
#
def link_to_status(new_status)
- if new_status == "open"
- label = t(:open_tickets)
- elsif new_status == "closed"
- label = t(:closed_tickets)
- elsif new_status == "all"
- label = t(:all_tickets)
- end
+ label = t(:".#{new_status}", cascade: true)
link_to label, auto_tickets_path(:open_status => new_status, :sort_order => search_order)
end
@@ -62,11 +56,7 @@ module TicketsHelper
direction = 'desc'
end
- if order_field == 'updated'
- label = t(:updated)
- elsif order_field == 'created'
- label = t(:created)
- end
+ label = t(:".#{order_field}", cascade: true)
link_to auto_tickets_path(:sort_order => order_field + '_at_' + direction, :open_status => search_status) do
arrow + label
diff --git a/engines/support/app/models/ticket.rb b/engines/support/app/models/ticket.rb
index bf5df53..161507c 100644
--- a/engines/support/app/models/ticket.rb
+++ b/engines/support/app/models/ticket.rb
@@ -39,6 +39,8 @@ class Ticket < CouchRest::Model::Base
# * valid email address
validates :email, :allow_blank => true, :format => /\A(([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,}))?\Z/
+ # validates :comments, presence: true
+
def self.search(options = {})
@selection = TicketSelection.new(options)
@selection.tickets
diff --git a/engines/support/app/models/ticket_comment.rb b/engines/support/app/models/ticket_comment.rb
index bed5237..2c5df41 100644
--- a/engines/support/app/models/ticket_comment.rb
+++ b/engines/support/app/models/ticket_comment.rb
@@ -18,6 +18,11 @@ class TicketComment
# view :by_body
#end
+ # translations are in the same scope as those of a "proper" couchrest model
+ def self.i18n_scope
+ "couchrest"
+ end
+
def is_comment_validated?
!!posted_by
end
diff --git a/engines/support/app/views/tickets/_comment.html.haml b/engines/support/app/views/tickets/_comment.html.haml
index 778ca13..65ec394 100644
--- a/engines/support/app/views/tickets/_comment.html.haml
+++ b/engines/support/app/views/tickets/_comment.html.haml
@@ -1,4 +1,5 @@
-- if admin? or !comment.private # only show comment if user is admin or comment is not private
+- # only show comment if user is admin or comment is not private
+- if admin? or !comment.private
%tr
%td.user
%div
@@ -17,4 +18,4 @@
%span.label.label-important
= t(:private)
%td.comment
- = simple_format(comment.body) \ No newline at end of file
+ = simple_format(comment.body)
diff --git a/engines/support/app/views/tickets/_comments.html.haml b/engines/support/app/views/tickets/_comments.html.haml
new file mode 100644
index 0000000..0a3b345
--- /dev/null
+++ b/engines/support/app/views/tickets/_comments.html.haml
@@ -0,0 +1,8 @@
+%table.table.table-striped.table-bordered
+ %tbody
+ = render :partial => 'tickets/comment', :collection => @ticket.comments
+ %tr
+ %td.user
+ = current_user.login || t(:anonymous)
+ %td.comment
+ = render 'tickets/new_comment_form'
diff --git a/engines/support/app/views/tickets/_edit_form.html.haml b/engines/support/app/views/tickets/_edit_form.html.haml
index b8da779..889dac2 100644
--- a/engines/support/app/views/tickets/_edit_form.html.haml
+++ b/engines/support/app/views/tickets/_edit_form.html.haml
@@ -17,34 +17,30 @@
regarding_user_link = ''
end
-= simple_form_for @ticket do |f|
+- url = url_for([@ticket.is_open? ? :close : :open, @ticket])
+= simple_form_for @ticket, url: url do |f|
= hidden_ticket_fields
%p.first
- if @ticket.is_open?
%span.label.label-info
- %b{style: 'padding:10px'}= t(:open)
- = f.button :loading, t(:close), value: 'close', class: 'btn-mini'
+ %b{style: 'padding:10px'}= t("tickets.status.open")
+ = f.button :loading, t("tickets.action.close"), class: 'btn-mini'
- else
%span.label.label-success
- %b{style: 'padding:10px'}= t(:closed)
- = f.button :loading, t(:open), value: 'open', class: 'btn-mini'
- %span.label.label-clear= t(:created_by_on, :user => created_by, :time => @ticket.created_at.to_s(:short)).html_safe
+ %b{style: 'padding:10px'}= t("tickets.status.closed")
+ = f.button :loading, t("tickets.action.open"), class: 'btn-mini'
+ %span.label.label-clear
+ = t("tickets.created_by_on", user: created_by, time: @ticket.created_at.to_s(:short), cascade: true).html_safe
= simple_form_for @ticket do |f|
= hidden_ticket_fields
- %div= t(:subject)
- = f.text_field :subject, :class => 'large full-width'
+ = f.input :subject, input_html: {:class => 'large full-width'}
.row-fluid
.span4
- %div= t(:status)
- = f.select :is_open, [[t(:open), "true"], [t(:closed), "false"]]
+ = f.input :is_open, as: :select, collection: [:true, :false], include_blank: false
.span4
- %div= t(:email)
- = f.text_field :email
+ = f.input :email
.span4
- %div
- = t(:regarding_account)
- = regarding_user_link
- = f.text_field :regarding_user
- = f.button :loading, t(:save), :value => 'save'
+ = f.input :regarding_user, label: Ticket.human_attribute_name(:regarding_user) + regarding_user_link
+ = f.button :loading
- if admin?
- = link_to t(:destroy), auto_ticket_path(@ticket), :confirm => t(:are_you_sure), :method => :delete, :class => 'btn'
+ = destroy_btn t(".destroy", cascade: true), auto_ticket_path(@ticket)
diff --git a/engines/support/app/views/tickets/_new_comment_form.html.haml b/engines/support/app/views/tickets/_new_comment_form.html.haml
index 40c737f..711421d 100644
--- a/engines/support/app/views/tickets/_new_comment_form.html.haml
+++ b/engines/support/app/views/tickets/_new_comment_form.html.haml
@@ -7,7 +7,7 @@
= c.input :body, :label => false, :as => :text, :input_html => {:class => "full-width", :rows=> 5}
- if admin?
= c.input :private, :as => :boolean, :label => false, :inline_label => true
- = f.button :loading, t(:post_reply), class: 'btn-primary', value: 'post_reply'
+ = f.button :loading, t(".post_reply"), class: 'btn-primary', value: 'post_reply'
- if logged_in? && @ticket.is_open
- = f.button :loading, t(:reply_and_close), value: 'reply_and_close'
- = link_to t(:cancel), auto_tickets_path, :class => :btn
+ = f.button :loading, t(".reply_and_close"), value: 'reply_and_close'
+ = btn t(".cancel"), auto_tickets_path
diff --git a/engines/support/app/views/tickets/_tabs.html.haml b/engines/support/app/views/tickets/_tabs.html.haml
index 445a909..7872bb5 100644
--- a/engines/support/app/views/tickets/_tabs.html.haml
+++ b/engines/support/app/views/tickets/_tabs.html.haml
@@ -3,21 +3,17 @@
-#
- unless action?(:new) or action?(:create)
%ul.nav.nav-pills.pull-right.slim
- %li{:class=> ("active" if search_order.start_with? 'created_at')}
- = link_to_order('created')
- %li{:class=> ("active" if search_order.start_with? 'updated_at')}
- = link_to_order('updated')
+ - %w(created updated).each do |order|
+ %li{:class=> ("active" if search_order.start_with? order)}
+ = link_to_order(order)
-#
-# STATUS FILTER TABS
-#
%ul.nav.nav-tabs
- if logged_in?
- %li{:class => ("active" if search_status == 'open')}
- = link_to_status 'open'
- %li{:class => ("active" if search_status == 'closed')}
- = link_to_status 'closed'
- %li{:class => ("active" if search_status == 'all')}
- = link_to_status 'all'
+ - %w(open closed all).each do |status|
+ %li{:class => ("active" if search_status == status)}
+ = link_to_status status
%li{:class => ("active" if action?(:new) || action?(:create))}
- = link_to icon(:plus, :black) + t(:new_ticket), auto_new_ticket_path
+ = link_to icon(:plus, :black) + t(".new", cascade: true), auto_new_ticket_path
diff --git a/engines/support/app/views/tickets/edit.html.haml b/engines/support/app/views/tickets/edit.html.haml
new file mode 100644
index 0000000..03bda7d
--- /dev/null
+++ b/engines/support/app/views/tickets/edit.html.haml
@@ -0,0 +1,6 @@
+- @show_navigation = params[:user_id].present?
+- @comment = TicketComment.new
+
+.ticket
+ = render 'tickets/edit_form'
+ = render 'tickets/comments'
diff --git a/engines/support/app/views/tickets/index.html.haml b/engines/support/app/views/tickets/index.html.haml
index a4df6e3..526cd6d 100644
--- a/engines/support/app/views/tickets/index.html.haml
+++ b/engines/support/app/views/tickets/index.html.haml
@@ -5,15 +5,15 @@
%table.table.table-striped.table-bordered
%thead
%tr
- %th= t(:subject)
- %th= t(:created)
- %th= t(:updated)
- %th= t(:voices)
+ %th= t(".subject")
+ %th= t(".created")
+ %th= t(".updated")
+ %th= t(".voices")
%tbody
- if @tickets.any?
= render @tickets.all
- else
%tr
- %td{:colspan=>4}= t(:none)
+ %td{:colspan=>4}= t(".none")
= paginate @tickets
diff --git a/engines/support/app/views/tickets/new.html.haml b/engines/support/app/views/tickets/new.html.haml
index 3de5fe9..d3580f9 100644
--- a/engines/support/app/views/tickets/new.html.haml
+++ b/engines/support/app/views/tickets/new.html.haml
@@ -11,7 +11,7 @@
= f.input :email
= f.input :regarding_user
= f.simple_fields_for :comments, @comment do |c|
- = c.input :body, :label => t(:description), :as => :text, :input_html => {:class => "full-width", :rows=> 5}
+ = c.input :body, :as => :text, :input_html => {:class => "full-width", :rows=> 5}
- if admin?
= c.input :private, :as => :boolean, :label => false, :inline_label => true
= f.button :wrapped, cancel: (logged_in? ? auto_tickets_path : home_path)
diff --git a/engines/support/app/views/tickets/show.html.haml b/engines/support/app/views/tickets/show.html.haml
index 4f3c127..99afa2a 100644
--- a/engines/support/app/views/tickets/show.html.haml
+++ b/engines/support/app/views/tickets/show.html.haml
@@ -2,11 +2,4 @@
.ticket
= render 'tickets/edit_form'
- %table.table.table-striped.table-bordered
- %tbody
- = render :partial => 'tickets/comment', :collection => @ticket.comments
- %tr
- %td.user
- = current_user.login || t(:anonymous)
- %td.comment
- = render 'tickets/new_comment_form'
+ = render 'tickets/comments'
diff --git a/engines/support/config/locales/en.yml b/engines/support/config/locales/en.yml
index 342adea..8d2af67 100644
--- a/engines/support/config/locales/en.yml
+++ b/engines/support/config/locales/en.yml
@@ -1,22 +1,105 @@
en:
- access_ticket_text: >
- You can later access this ticket at the URL %{full_url}. You might want to bookmark this page to find it again.
- Anybody with this URL will be able to access this ticket, so if you are on a shared computer you might want to
- remove it from the browser history.
- support_tickets: "Support Tickets"
- all_tickets: "All Tickets"
- my_tickets: "My Tickets"
- open_tickets: "Open Tickets"
- closed_tickets: "Closed Tickets"
- new_ticket: "New Ticket"
- tickets: "Tickets"
- subject: "Subject"
- destroy: "Destroy"
- open: "Open"
- closed: "Closed"
- close: "Close"
- post_reply: "Post Reply"
- reply_and_close: "Reply and Close"
- description: "Description"
- ticket: "Ticket"
- regarding_account: "Regarding Account" \ No newline at end of file
+ support_tickets: "Support"
+ # translations used in the layout views or @title
+ layouts:
+ # fallback for all translations of "tickets" nested below:
+ tickets: "Tickets"
+ title:
+ tickets: "Tickets"
+ header:
+ tickets: "Tickets"
+ navigation:
+ tickets: "Support Tickets"
+ # Translations used in the views inside the tickets directory
+ tickets:
+ # If these do not exist they will be looked up in the global scope.
+ all: "All Tickets"
+ open: "Open Tickets"
+ closed: "Closed Tickets"
+ new: "New Ticket"
+ created: "Created at"
+ updated: "Updated at"
+ subject: "couchrest.models.tickets.attributes.subject"
+ status:
+ open: "Open"
+ closed: "Closed"
+ action:
+ open: "Open"
+ close: "Close"
+ confirm:
+ destroy:
+ are_you_sure: "Are you sure you want to destroy this ticket?"
+ # If you want to be more specific you can use the partial as a scope:
+ tabs:
+ all: "All Tickets"
+ open: "Open Tickets"
+ closed: "Closed Tickets"
+ index:
+ none: "No tickets have been found."
+ voices: "Voices"
+ destroy: "Destroy"
+ post_reply: "Post Reply"
+ reply_and_close: "Reply and Close"
+ access_ticket_text: >
+ You can later access this ticket at the URL %{full_url}. You might want to bookmark this page to find it again.
+ Anybody with this URL will be able to access this ticket, so if you are on a shared computer you might want to
+ remove it from the browser history.
+ # rails i18n
+ helpers:
+ # translations used for submit buttons. simple form picks these up
+ submit:
+ ticket:
+ create: "Submit Ticket"
+ update: "Update Ticket"
+ # translations for the model and its attributes
+ # serve as fallback for simpleform labels
+ couchrest:
+ models:
+ ticket: "Ticket"
+ ticket_comment: "Comment"
+ attributes:
+ ticket:
+ # these will fallback to translations in the "attributes" scope
+ subject: "Subject"
+ email: "Email"
+ regarding_user: "Regarding User"
+ regarding_account: "Regarding Account"
+ is_open: "Status"
+ ticket_comment:
+ body: "Description"
+ private: "private"
+ simple_form:
+ # labels next to the field
+ labels:
+ # these will fallback to the human_name translations of the model
+ ticket:
+ # these will fallback to translations in "simple_form.labels.defaults"
+ regarding_:
+ # you can be specific about translations for a given action:
+ #edit:
+ # regaring_user:
+ email: "Email"
+ comments:
+ # these will fall back to "simple_form.labels.comments"
+ body: "Description"
+ # comments: ~
+ options:
+ ticket:
+ is_open:
+ "true": "Open"
+ "false": "Closed"
+ # mouse over hints for the given fields
+ hints:
+ ticket:
+ email: "Please provide an email address so we can get back to you."
+ # these will fallback to translations in "simple_form.hints.defaults"
+ # placeholders inside the fields before anything was typed
+ #placeholders:
+ # ticket: ~
+ # these will fallback to translations in "simple_form.placeholders.defaults"
+
+ # these are generic defaults. They should be moved into the toplevel
+ # attributes:
+ #simple_form:
+ #labels:
+ # defaults:
diff --git a/engines/support/config/routes.rb b/engines/support/config/routes.rb
index 23e0c11..ca471b3 100644
--- a/engines/support/config/routes.rb
+++ b/engines/support/config/routes.rb
@@ -1,8 +1,16 @@
Rails.application.routes.draw do
- scope "(:locale)", :locale => MATCH_LOCALE do
- resources :tickets, :except => :edit
+ scope "(:locale)", locale: MATCH_LOCALE do
+
+ resources :tickets, except: :edit do
+ member do
+ put 'open'
+ put 'close'
+ end
+ end
+
resources :users do
- resources :tickets, :except => :edit
+ resources :tickets, except: :edit
end
+
end
end
diff --git a/engines/support/test/factories.rb b/engines/support/test/factories.rb
index be04f15..bcf41e8 100644
--- a/engines/support/test/factories.rb
+++ b/engines/support/test/factories.rb
@@ -6,13 +6,23 @@ FactoryGirl.define do
factory :ticket_with_comment do
comments_attributes do
- { "0" => { "body" => Faker::Lorem.sentences.join(" ") } }
+ { "0" => {
+ "body" => Faker::Lorem.sentences.join(" "),
+ "posted_by" => created_by
+ } }
end
end
factory :ticket_with_creator do
created_by { FactoryGirl.create(:user).id }
end
+
+ end
+
+ # TicketComments can not be saved. so only use this with build
+ # and add to a ticket afterwards
+ factory :ticket_comment do
+ body { Faker::Lorem.sentences.join(" ") }
end
end
diff --git a/engines/support/test/functional/ticket_comments_test.rb b/engines/support/test/functional/ticket_comments_test.rb
new file mode 100644
index 0000000..5cbe233
--- /dev/null
+++ b/engines/support/test/functional/ticket_comments_test.rb
@@ -0,0 +1,101 @@
+require 'test_helper'
+
+class TicketsCommentsTest < ActionController::TestCase
+ tests TicketsController
+
+ teardown do
+ # destroy all tickets that were created during the test
+ Ticket.all.each{|t| t.destroy}
+ end
+
+ test "add comment to unauthenticated ticket" do
+ ticket = FactoryGirl.create :ticket, :created_by => nil
+
+ assert_difference('Ticket.find(ticket.id).comments.count') do
+ put :update, :id => ticket.id,
+ :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} }
+ end
+
+ assert_equal ticket, assigns(:ticket) # still same ticket, with different comments
+ assert_not_equal ticket.comments, assigns(:ticket).comments # ticket == assigns(:ticket), but they have different comments (which we want)
+
+ end
+
+
+ test "add comment to own authenticated ticket" do
+
+ login
+ ticket = FactoryGirl.create :ticket, :created_by => @current_user.id
+
+ #they should be able to comment if it is their ticket:
+ assert_difference('Ticket.find(ticket.id).comments.count') do
+ put :update, :id => ticket.id,
+ :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} }
+ end
+ assert_not_equal ticket.comments, assigns(:ticket).comments
+ assert_not_nil assigns(:ticket).comments.last.posted_by
+ assert_equal assigns(:ticket).comments.last.posted_by, @current_user.id
+
+ end
+
+
+ test "cannot comment if it is another users ticket" do
+ other_user = find_record :user
+ login :is_admin? => false, :email => nil
+ ticket = FactoryGirl.create :ticket, :created_by => other_user.id
+ # they should *not* be able to comment if it is not their ticket
+ put :update, :id => ticket.id, :ticket => {:comments_attributes => {"0" => {"body" =>"not allowed comment"}} }
+ assert_response :redirect
+ assert_access_denied
+
+ assert_equal ticket.comments.map(&:body), assigns(:ticket).comments.map(&:body)
+ end
+
+ test "authenticated comment on an anonymous ticket adds to my tickets" do
+ login
+ ticket = FactoryGirl.create :ticket
+ other_ticket = FactoryGirl.create :ticket
+ put :update, :id => ticket.id,
+ :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} }
+ assert_not_nil assigns(:ticket).comments.last.posted_by
+ assert_equal assigns(:ticket).comments.last.posted_by, @current_user.id
+ visible_tickets = Ticket.search admin_status: 'mine',
+ user_id: @current_user.id, is_admin: false
+ assert_equal [ticket], visible_tickets.all
+ end
+
+
+
+ test "admin add comment to authenticated ticket" do
+
+ other_user = find_record :user
+ login :is_admin? => true
+
+ ticket = FactoryGirl.create :ticket, :created_by => other_user.id
+
+ #admin should be able to comment:
+ assert_difference('Ticket.find(ticket.id).comments.count') do
+ put :update, :id => ticket.id,
+ :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} }
+ end
+ assert_not_equal ticket.comments, assigns(:ticket).comments
+ assert_not_nil assigns(:ticket).comments.last.posted_by
+ assert_equal assigns(:ticket).comments.last.posted_by, @current_user.id
+ end
+
+ test "commenting on a ticket adds to tickets that are mine" do
+ testticket = FactoryGirl.create :ticket
+ user = find_record :admin_user
+ login user
+ get :index, {:user_id => user.id, :open_status => "open"}
+ assert_difference('assigns[:all_tickets].count') do
+ put :update, :id => testticket.id, :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}}}
+ get :index, {:user_id => user.id, :open_status => "open"}
+ end
+
+ assert assigns(:all_tickets).include?(assigns(:ticket))
+ assert_not_nil assigns(:ticket).comments.last.posted_by
+ assert_equal assigns(:ticket).comments.last.posted_by, @current_user.id
+ end
+
+end
diff --git a/engines/support/test/functional/tickets_controller_test.rb b/engines/support/test/functional/tickets_controller_test.rb
index fc4a6f8..ebaa3a4 100644
--- a/engines/support/test/functional/tickets_controller_test.rb
+++ b/engines/support/test/functional/tickets_controller_test.rb
@@ -1,5 +1,14 @@
require 'test_helper'
+#
+# Tests for the basic actions in the TicketsController
+#
+# Also see
+# TicketCommentsTest
+# TicketsListTest
+#
+# for detailed functional tests for comments and index action.
+#
class TicketsControllerTest < ActionController::TestCase
teardown do
@@ -36,8 +45,7 @@ class TicketsControllerTest < ActionController::TestCase
user = find_record :user
ticket = find_record :ticket, :created_by => user.id
get :show, :id => ticket.id
- assert_response :redirect
- assert_redirected_to login_url
+ assert_login_required
end
test "user tickets are visible to creator" do
@@ -48,13 +56,19 @@ class TicketsControllerTest < ActionController::TestCase
assert_response :success
end
- test "other users tickets are not visible" do
+ test "ticket of other user is not visible" do
other_user = find_record :user
ticket = find_record :ticket, :created_by => other_user.id
login
get :show, :id => ticket.id
- assert_response :redirect
- assert_redirected_to home_url
+ assert_access_denied
+ end
+
+ test "ticket list of other user is not visible" do
+ other_user = find_record :user
+ login
+ get :index, :user_id => other_user.id
+ assert_access_denied
end
test "should create unauthenticated ticket" do
@@ -104,180 +118,20 @@ class TicketsControllerTest < ActionController::TestCase
assert_equal assigns(:ticket).comments.first.posted_by, @current_user.id
end
- test "add comment to unauthenticated ticket" do
- ticket = FactoryGirl.create :ticket, :created_by => nil
-
- assert_difference('Ticket.find(ticket.id).comments.count') do
- put :update, :id => ticket.id,
- :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} }
- end
-
- assert_equal ticket, assigns(:ticket) # still same ticket, with different comments
- assert_not_equal ticket.comments, assigns(:ticket).comments # ticket == assigns(:ticket), but they have different comments (which we want)
-
- end
-
-
- test "add comment to own authenticated ticket" do
-
+ test "close ticket" do
login
- ticket = FactoryGirl.create :ticket, :created_by => @current_user.id
-
- #they should be able to comment if it is their ticket:
- assert_difference('Ticket.find(ticket.id).comments.count') do
- put :update, :id => ticket.id,
- :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} }
- end
- assert_not_equal ticket.comments, assigns(:ticket).comments
- assert_not_nil assigns(:ticket).comments.last.posted_by
- assert_equal assigns(:ticket).comments.last.posted_by, @current_user.id
-
- end
-
-
- test "cannot comment if it is not your ticket" do
-
- other_user = find_record :user
- login :is_admin? => false, :email => nil
- ticket = FactoryGirl.create :ticket, :created_by => other_user.id
- # they should *not* be able to comment if it is not their ticket
- put :update, :id => ticket.id, :ticket => {:comments_attributes => {"0" => {"body" =>"not allowed comment"}} }
- assert_response :redirect
- assert_access_denied
-
- assert_equal ticket.comments.map(&:body), assigns(:ticket).comments.map(&:body)
-
- end
-
-
- test "admin add comment to authenticated ticket" do
-
- other_user = find_record :user
- login :is_admin? => true
-
- ticket = FactoryGirl.create :ticket, :created_by => other_user.id
-
- #admin should be able to comment:
- assert_difference('Ticket.find(ticket.id).comments.count') do
- put :update, :id => ticket.id,
- :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} }
- end
- assert_not_equal ticket.comments, assigns(:ticket).comments
- assert_not_nil assigns(:ticket).comments.last.posted_by
- assert_equal assigns(:ticket).comments.last.posted_by, @current_user.id
- end
-
- test "tickets by admin" do
- other_user = find_record :user
- ticket = FactoryGirl.create :ticket, :created_by => other_user.id
-
- login :is_admin? => true
-
- get :index, {:admin_status => "all", :open_status => "open"}
- assert assigns(:all_tickets).count > 0
-
- # if we close one ticket, the admin should have 1 less open ticket
- assert_difference('assigns[:all_tickets].count', -1) do
- assigns(:tickets).first.close
- assigns(:tickets).first.save
- get :index, {:admin_status => "all", :open_status => "open"}
- end
- end
-
-
- test "admin_status mine vs all" do
- testticket = FactoryGirl.create :ticket
- user = find_record :user
- login :is_admin? => true, :email => nil
-
- get :index, {:open_status => "open"}
- assert assigns(:all_tickets).include?(testticket)
- get :index, {:user_id => user.id, :open_status => "open"}
- assert !assigns(:all_tickets).include?(testticket)
+ open_ticket = FactoryGirl.create :ticket_with_comment,
+ created_by: @current_user.id
+ post :close, id: open_ticket.id
+ assert !open_ticket.reload.is_open
end
- test "commenting on a ticket adds to tickets that are mine" do
- testticket = FactoryGirl.create :ticket
- user = find_record :admin_user
- login user
- get :index, {:user_id => user.id, :open_status => "open"}
- assert_difference('assigns[:all_tickets].count') do
- put :update, :id => testticket.id, :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}}}
- get :index, {:user_id => user.id, :open_status => "open"}
- end
-
- assert assigns(:all_tickets).include?(assigns(:ticket))
- assert_not_nil assigns(:ticket).comments.last.posted_by
- assert_equal assigns(:ticket).comments.last.posted_by, @current_user.id
- end
-
- test "admin ticket ordering" do
- tickets = FactoryGirl.create_list :ticket, 2
-
- login :is_admin? => true, :email => nil
- get :index, {:admin_status => "all", :open_status => "open", :sort_order => 'created_at_desc'}
-
- # this will consider all tickets, not just those on first page
- first_tick = assigns(:all_tickets).all.first
- last_tick = assigns(:all_tickets).all.last
- assert first_tick.created_at > last_tick.created_at
-
- # and now reverse order:
- get :index, {:admin_status => "all", :open_status => "open", :sort_order => 'created_at_asc'}
-
- assert_equal first_tick, assigns(:all_tickets).last
- assert_equal last_tick, assigns(:all_tickets).first
-
- assert_not_equal first_tick, assigns(:all_tickets).first
- assert_not_equal last_tick, assigns(:all_tickets).last
-
- end
-
- test "tickets for regular user" do
+ test "reopen ticket" do
login
- ticket = FactoryGirl.create :ticket
- other_ticket = FactoryGirl.create :ticket
-
- put :update, :id => ticket.id,
- :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} }
- assert_not_nil assigns(:ticket).comments.last.posted_by
- assert_equal assigns(:ticket).comments.last.posted_by, @current_user.id
-
- get :index, {:open_status => "open"}
- assert assigns(:all_tickets).count > 0
- assert assigns(:all_tickets).include?(ticket)
- assert !assigns(:all_tickets).include?(other_ticket)
-
- # user should have one more ticket if a new tick gets a comment by this user
- assert_difference('assigns[:all_tickets].count') do
- put :update, :id => other_ticket.id, :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}}}
- get :index, {:open_status => "open"}
- end
- assert assigns(:all_tickets).include?(other_ticket)
-
- # if we close one ticket, the user should have 1 less open ticket
- assert_difference('assigns[:all_tickets].count', -1) do
- other_ticket.reload
- other_ticket.close
- other_ticket.save
- get :index, {:open_status => "open"}
- end
-
- number_open_tickets = assigns(:all_tickets).count
-
- # look at closed tickets:
- get :index, {:open_status => "closed"}
- assert !assigns(:all_tickets).include?(ticket)
- assert assigns(:all_tickets).include?(other_ticket)
- number_closed_tickets = assigns(:all_tickets).count
-
- # all tickets should equal closed + open
- get :index, {:open_status => "all"}
- assert assigns(:all_tickets).include?(ticket)
- assert assigns(:all_tickets).include?(other_ticket)
- assert_equal assigns(:all_tickets).count, number_closed_tickets + number_open_tickets
-
-
+ open_ticket = FactoryGirl.create :ticket_with_comment,
+ created_by: @current_user.id, is_open: false
+ post :open, id: open_ticket.id
+ assert open_ticket.reload.is_open
end
end
diff --git a/engines/support/test/functional/tickets_list_test.rb b/engines/support/test/functional/tickets_list_test.rb
new file mode 100644
index 0000000..4c4cdef
--- /dev/null
+++ b/engines/support/test/functional/tickets_list_test.rb
@@ -0,0 +1,122 @@
+require 'test_helper'
+
+class TicketsListTest < ActionController::TestCase
+ tests TicketsController
+
+ teardown do
+ # destroy all records that were created during the test
+ Ticket.all.each{|t| t.destroy}
+ User.all.each{|u| u.account.destroy}
+ end
+
+
+ test "tickets by admin" do
+ other_user = find_record :user
+ ticket = FactoryGirl.create :ticket, :created_by => other_user.id
+
+ login :is_admin? => true
+
+ get :index, {:admin_status => "all", :open_status => "open"}
+ assert assigns(:all_tickets).count > 0
+
+ # if we close one ticket, the admin should have 1 less open ticket
+ assert_difference('assigns[:all_tickets].count', -1) do
+ assigns(:tickets).first.close
+ assigns(:tickets).first.save
+ get :index, {:admin_status => "all", :open_status => "open"}
+ end
+ end
+
+
+ test "admin_status mine vs all" do
+ testticket = FactoryGirl.create :ticket
+ user = find_record :user
+ login :is_admin? => true, :email => nil
+
+ get :index, {:open_status => "open"}
+ assert assigns(:all_tickets).include?(testticket)
+ get :index, {:user_id => user.id, :open_status => "open"}
+ assert !assigns(:all_tickets).include?(testticket)
+ end
+
+ test "admin ticket ordering" do
+ tickets = FactoryGirl.create_list :ticket, 2
+
+ login :is_admin? => true, :email => nil
+ get :index, {:admin_status => "all", :open_status => "open", :sort_order => 'created_at_desc'}
+
+ # this will consider all tickets, not just those on first page
+ first_tick = assigns(:all_tickets).all.first
+ last_tick = assigns(:all_tickets).all.last
+ assert first_tick.created_at > last_tick.created_at
+
+ # and now reverse order:
+ get :index, {:admin_status => "all", :open_status => "open", :sort_order => 'created_at_asc'}
+
+ assert_equal first_tick, assigns(:all_tickets).last
+ assert_equal last_tick, assigns(:all_tickets).first
+
+ assert_not_equal first_tick, assigns(:all_tickets).first
+ assert_not_equal last_tick, assigns(:all_tickets).last
+
+ end
+
+ test "own tickets include tickets commented upon" do
+ login
+ ticket = FactoryGirl.create :ticket
+ other_ticket = FactoryGirl.create :ticket
+ comment = FactoryGirl.build(:ticket_comment, posted_by: @current_user.id)
+ ticket.comments << comment
+ ticket.save
+
+ get :index, {:open_status => "open"}
+ assert assigns(:all_tickets).count > 0
+ assert assigns(:all_tickets).include?(ticket)
+ assert !assigns(:all_tickets).include?(other_ticket)
+ end
+
+ test "list all tickets created by user" do
+ login
+ ticket = FactoryGirl.create :ticket_with_comment,
+ created_by: @current_user.id
+ other_ticket = FactoryGirl.create :ticket_with_comment,
+ created_by: @current_user.id
+ get :index, {:open_status => "open"}
+ assert_equal 2, assigns[:all_tickets].count
+ end
+
+ test "closing ticket removes from open tickets list" do
+ login
+ ticket = FactoryGirl.create :ticket_with_comment,
+ created_by: @current_user.id
+ other_ticket = FactoryGirl.create :ticket_with_comment,
+ created_by: @current_user.id
+ other_ticket.reload
+ other_ticket.close
+ other_ticket.save
+ get :index, {:open_status => "open"}
+ assert_equal 1, assigns[:all_tickets].count
+ end
+
+ test "list closed tickets only" do
+ login
+ open_ticket = FactoryGirl.create :ticket_with_comment,
+ created_by: @current_user.id
+ closed_ticket = FactoryGirl.create :ticket_with_comment,
+ created_by: @current_user.id, is_open: false
+ get :index, {:open_status => "closed"}
+ assert_equal [closed_ticket], assigns(:all_tickets).all
+ end
+
+ test "list all tickets inludes closed + open" do
+ login
+ open_ticket = FactoryGirl.create :ticket_with_comment,
+ created_by: @current_user.id
+ closed_ticket = FactoryGirl.create :ticket_with_comment,
+ created_by: @current_user.id, is_open: false
+ get :index, {:open_status => "all"}
+ assert_equal 2, assigns(:all_tickets).count
+ assert assigns(:all_tickets).include?(open_ticket)
+ assert assigns(:all_tickets).include?(closed_ticket)
+ end
+end
diff --git a/engines/support/test/integration/create_ticket_test.rb b/engines/support/test/integration/create_ticket_test.rb
index 0f8453c..90e9a8a 100644
--- a/engines/support/test/integration/create_ticket_test.rb
+++ b/engines/support/test/integration/create_ticket_test.rb
@@ -7,7 +7,7 @@ class CreateTicketTest < BrowserIntegrationTest
click_on 'Get Help'
fill_in 'Subject', with: 'test ticket'
fill_in 'Description', with: 'description of the problem goes here'
- click_on 'Create Ticket'
+ click_on 'Submit Ticket'
assert page.has_content?("Ticket was successfully created.")
assert page.has_content?("You can later access this ticket at the URL")
assert page.has_content?(current_url)
@@ -20,12 +20,12 @@ class CreateTicketTest < BrowserIntegrationTest
click_on 'Get Help'
fill_in 'Subject', with: 'test ticket'
fill_in 'Email', with: 'invalid data'
- fill_in 'Regarding user', with: 'some user'
+ fill_in 'Regarding User', with: 'some user'
fill_in 'Description', with: 'description of the problem goes here'
- click_on 'Create Ticket'
+ click_on 'Submit Ticket'
assert page.has_content?("is invalid")
assert_equal 'invalid data', find_field('Email').value
- assert_equal 'some user', find_field('Regarding user').value
+ assert_equal 'some user', find_field('Regarding User').value
end
test "prefills fields" do
@@ -35,7 +35,7 @@ class CreateTicketTest < BrowserIntegrationTest
click_on "New Ticket"
email = "#{@user.login}@#{APP_CONFIG[:domain]}"
assert_equal email, find_field('Email').value
- assert_equal @user.login, find_field('Regarding user').value
+ assert_equal @user.login, find_field('Regarding User').value
end
test "no prefill of email without email service" do
@@ -44,7 +44,7 @@ class CreateTicketTest < BrowserIntegrationTest
click_on "Support Tickets"
click_on "New Ticket"
assert_equal "", find_field('Email').value
- assert_equal @user.login, find_field('Regarding user').value
+ assert_equal @user.login, find_field('Regarding User').value
end
test "cleared email field should remain clear" do
@@ -55,7 +55,7 @@ class CreateTicketTest < BrowserIntegrationTest
fill_in 'Subject', with: 'test ticket'
fill_in 'Email', with: ''
fill_in 'Description', with: 'description of the problem goes here'
- click_on 'Create Ticket'
+ click_on 'Submit Ticket'
ticket = Ticket.last
assert_equal "", ticket.email
ticket.destroy
diff --git a/lib/extensions/couchrest.rb b/lib/extensions/couchrest.rb
index 95f5d92..df83c9f 100644
--- a/lib/extensions/couchrest.rb
+++ b/lib/extensions/couchrest.rb
@@ -1,5 +1,9 @@
module CouchRest
module Model
+ class Base
+ extend ActiveModel::Naming
+ extend ActiveModel::Translation
+ end
module Designs
class View
diff --git a/lib/leap_web/version.rb b/lib/leap_web/version.rb
index b92d0a3..56df918 100644
--- a/lib/leap_web/version.rb
+++ b/lib/leap_web/version.rb
@@ -1,3 +1,3 @@
module LeapWeb
- VERSION = "0.5.1" unless defined?(LeapWeb::VERSION)
+ VERSION = "0.5.2" unless defined?(LeapWeb::VERSION)
end
diff --git a/public/404.html b/public/404.html
deleted file mode 100644
index 9a48320..0000000
--- a/public/404.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title>The page you were looking for doesn't exist (404)</title>
- <style type="text/css">
- body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
- div.dialog {
- width: 25em;
- padding: 0 4em;
- margin: 4em auto 0 auto;
- border: 1px solid #ccc;
- border-right-color: #999;
- border-bottom-color: #999;
- }
- h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
- </style>
-</head>
-
-<body>
- <!-- This file lives in public/404.html -->
- <div class="dialog">
- <h1>The page you were looking for doesn't exist.</h1>
- <p>You may have mistyped the address or the page may have moved.</p>
- </div>
-</body>
-</html>
diff --git a/public/500.html b/public/500.html
deleted file mode 100644
index f3648a0..0000000
--- a/public/500.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title>We're sorry, but something went wrong (500)</title>
- <style type="text/css">
- body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
- div.dialog {
- width: 25em;
- padding: 0 4em;
- margin: 4em auto 0 auto;
- border: 1px solid #ccc;
- border-right-color: #999;
- border-bottom-color: #999;
- }
- h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
- </style>
-</head>
-
-<body>
- <!-- This file lives in public/500.html -->
- <div class="dialog">
- <h1>We're sorry, but something went wrong.</h1>
- </div>
-</body>
-</html>
diff --git a/test/functional/test_helpers_test.rb b/test/functional/test_helpers_test.rb
index 845e516..ca85482 100644
--- a/test/functional/test_helpers_test.rb
+++ b/test/functional/test_helpers_test.rb
@@ -27,7 +27,7 @@ class TestHelpersTest < ActionController::TestCase
def test_login_adds_token_header
login
token_present = @controller.authenticate_with_http_token do |token, options|
- assert_equal @token.id, token
+ assert_equal @token.to_s, token
end
# authenticate_with_http_token just returns nil and does not
# execute the block if there is no token. So we have to also
diff --git a/test/functional/v1/certs_controller_test.rb b/test/functional/v1/certs_controller_test.rb
index fb8e9c4..ec34b01 100644
--- a/test/functional/v1/certs_controller_test.rb
+++ b/test/functional/v1/certs_controller_test.rb
@@ -2,26 +2,34 @@ require 'test_helper'
class V1::CertsControllerTest < ActionController::TestCase
- test "send unlimited cert without login" do
+ test "create unlimited cert without login" do
with_config allow_anonymous_certs: true do
cert = expect_cert('UNLIMITED')
- get :show
+ post :create
assert_response :success
assert_equal cert.to_s, @response.body
end
end
- test "send limited cert" do
+ test "create limited cert" do
with_config allow_limited_certs: true do
login
cert = expect_cert('LIMITED')
- get :show
+ post :create
assert_response :success
assert_equal cert.to_s, @response.body
end
end
- test "send unlimited cert" do
+ test "create unlimited cert" do
+ login effective_service_level: ServiceLevel.new(id: 2)
+ cert = expect_cert('UNLIMITED')
+ post :create
+ assert_response :success
+ assert_equal cert.to_s, @response.body
+ end
+
+ test "GET still works as an alias" do
login effective_service_level: ServiceLevel.new(id: 2)
cert = expect_cert('UNLIMITED')
get :show
@@ -30,7 +38,7 @@ class V1::CertsControllerTest < ActionController::TestCase
end
test "redirect if no eip service offered" do
- get :show
+ post :create
assert_response :redirect
end
diff --git a/test/functional/v1/sessions_controller_test.rb b/test/functional/v1/sessions_controller_test.rb
index df0d681..8bb6acd 100644
--- a/test/functional/v1/sessions_controller_test.rb
+++ b/test/functional/v1/sessions_controller_test.rb
@@ -48,7 +48,7 @@ class V1::SessionsControllerTest < ActionController::TestCase
assert_response :success
assert json_response.keys.include?("id")
assert json_response.keys.include?("token")
- assert token = Token.find(json_response['token'])
+ assert token = Token.find_by_token(json_response['token'])
assert_equal @user.id, token.user_id
end
diff --git a/test/functional/v1/smtp_certs_controller_test.rb b/test/functional/v1/smtp_certs_controller_test.rb
new file mode 100644
index 0000000..9281ae6
--- /dev/null
+++ b/test/functional/v1/smtp_certs_controller_test.rb
@@ -0,0 +1,36 @@
+require 'test_helper'
+
+class V1::SmtpCertsControllerTest < ActionController::TestCase
+
+ test "no smtp cert without login" do
+ with_config allow_anonymous_certs: true do
+ post :create
+ assert_login_required
+ end
+ end
+
+ test "require service level with email" do
+ login
+ post :create
+ assert_access_denied
+ end
+
+ test "send cert with username" do
+ login effective_service_level: ServiceLevel.new(id: 2)
+ cert = expect_cert(@current_user.email_address)
+ cert.expects(:fingerprint).returns('fingerprint')
+ post :create
+ assert_response :success
+ assert_equal cert.to_s, @response.body
+ end
+
+ protected
+
+ def expect_cert(prefix)
+ cert = stub :to_s => "#{prefix.downcase} cert"
+ ClientCertificate.expects(:new).
+ with(:prefix => prefix).
+ returns(cert)
+ return cert
+ end
+end
diff --git a/test/integration/api/cert_test.rb b/test/integration/api/cert_test.rb
new file mode 100644
index 0000000..74d439a
--- /dev/null
+++ b/test/integration/api/cert_test.rb
@@ -0,0 +1,30 @@
+require 'test_helper'
+
+class CertTest < ApiIntegrationTest
+
+ test "retrieve eip cert" do
+ login
+ get '/1/cert', {}, RACK_ENV
+ assert_text_response
+ assert_response_includes "BEGIN RSA PRIVATE KEY"
+ assert_response_includes "END RSA PRIVATE KEY"
+ assert_response_includes "BEGIN CERTIFICATE"
+ assert_response_includes "END CERTIFICATE"
+ end
+
+ test "fetching certs requires login by default" do
+ get '/1/cert', {}, RACK_ENV
+ assert_json_response error: I18n.t(:not_authorized)
+ end
+
+ test "retrieve anonymous eip cert" do
+ with_config allow_anonymous_certs: true do
+ get '/1/cert', {}, RACK_ENV
+ assert_text_response
+ assert_response_includes "BEGIN RSA PRIVATE KEY"
+ assert_response_includes "END RSA PRIVATE KEY"
+ assert_response_includes "BEGIN CERTIFICATE"
+ assert_response_includes "END CERTIFICATE"
+ end
+ end
+end
diff --git a/test/integration/api/smtp_cert_test.rb b/test/integration/api/smtp_cert_test.rb
new file mode 100644
index 0000000..f72362d
--- /dev/null
+++ b/test/integration/api/smtp_cert_test.rb
@@ -0,0 +1,52 @@
+require 'test_helper'
+require 'openssl'
+
+class SmtpCertTest < ApiIntegrationTest
+
+ test "retrieve smtp cert" do
+ @user = FactoryGirl.create :user, effective_service_level_code: 2
+ login
+ post '/1/smtp_cert', {}, RACK_ENV
+ assert_text_response
+ assert_response_includes "BEGIN RSA PRIVATE KEY"
+ assert_response_includes "END RSA PRIVATE KEY"
+ assert_response_includes "BEGIN CERTIFICATE"
+ assert_response_includes "END CERTIFICATE"
+ end
+
+ test "cert and key" do
+ @user = FactoryGirl.create :user, effective_service_level_code: 2
+ login
+ post '/1/smtp_cert', {}, RACK_ENV
+ assert_text_response
+ cert = OpenSSL::X509::Certificate.new(get_response.body)
+ key = OpenSSL::PKey::RSA.new(get_response.body)
+ assert cert.check_private_key(key)
+ prefix = "/CN=#{@user.email_address}"
+ assert_equal prefix, cert.subject.to_s.slice(0,prefix.size)
+ end
+
+ test "fingerprint is stored with identity" do
+ @user = FactoryGirl.create :user, effective_service_level_code: 2
+ login
+ post '/1/smtp_cert', {}, RACK_ENV
+ assert_text_response
+ cert = OpenSSL::X509::Certificate.new(get_response.body)
+ fingerprint = OpenSSL::Digest::SHA1.hexdigest(cert.to_der).scan(/../).join(':')
+ today = DateTime.now.to_date.to_s
+ assert_equal({fingerprint => today}, @user.reload.identity.cert_fingerprints)
+ end
+
+ test "fetching smtp certs requires email account" do
+ login
+ post '/1/smtp_cert', {}, RACK_ENV
+ assert_json_response error: I18n.t(:not_authorized)
+ end
+
+ test "no anonymous smtp certs" do
+ with_config allow_anonymous_certs: true do
+ post '/1/smtp_cert', {}, RACK_ENV
+ assert_json_response error: I18n.t(:not_authorized)
+ end
+ end
+end
diff --git a/test/integration/api/token_test.rb b/test/integration/api/token_test.rb
new file mode 100644
index 0000000..ad3ac22
--- /dev/null
+++ b/test/integration/api/token_test.rb
@@ -0,0 +1,15 @@
+require 'test_helper'
+require_relative 'srp_test'
+
+class TokenTest < SrpTest
+
+ setup do
+ register_user
+ end
+
+ test "stores token SHA512 encoded" do
+ authenticate
+ token = server_auth['token']
+ assert Token.find(Digest::SHA512.hexdigest(token))
+ end
+end
diff --git a/test/integration/browser/account_test.rb b/test/integration/browser/account_test.rb
index 491a9e1..aea5406 100644
--- a/test/integration/browser/account_test.rb
+++ b/test/integration/browser/account_test.rb
@@ -9,7 +9,7 @@ class AccountTest < BrowserIntegrationTest
test "signup successfully" do
username, password = submit_signup
assert page.has_content?("Welcome #{username}")
- click_on 'Logout'
+ click_on 'Log Out'
assert page.has_content?("Log In")
assert_equal '/', current_path
assert user = User.find_by_login(username)
@@ -22,9 +22,15 @@ class AccountTest < BrowserIntegrationTest
assert page.has_content?("Welcome #{username}")
end
+ test "signup with reserved username" do
+ username = 'certmaster'
+ submit_signup username
+ assert page.has_content?("is reserved.")
+ end
+
test "successful login" do
username, password = submit_signup
- click_on 'Logout'
+ click_on 'Log Out'
attempt_login(username, password)
assert page.has_content?("Welcome #{username}")
within('.sidenav li.active') do
@@ -44,6 +50,7 @@ class AccountTest < BrowserIntegrationTest
click_on I18n.t('account_settings')
click_on I18n.t('destroy_my_account')
assert page.has_content?(I18n.t('account_destroyed'))
+ assert_equal 1, Identity.by_address.key("#{username}@test.me").count
attempt_login(username, password)
assert_invalid_login(page)
end
@@ -83,7 +90,7 @@ class AccountTest < BrowserIntegrationTest
fill_in 'Password confirmation', with: "other password"
click_on 'Save'
end
- click_on 'Logout'
+ click_on 'Log Out'
attempt_login(@user.login, "other password")
assert page.has_content?("Welcome #{@user.login}")
end
@@ -102,7 +109,8 @@ class AccountTest < BrowserIntegrationTest
# at some point we're done:
page.assert_no_selector 'input[value="Saving..."]'
assert page.has_field? 'Public key', with: pgp_key.to_s
- assert_equal pgp_key, @user.reload.public_key
+ @user.reload
+ assert_equal pgp_key, @user.public_key
end
end
diff --git a/test/integration/browser/session_test.rb b/test/integration/browser/session_test.rb
index fb20847..d52508a 100644
--- a/test/integration/browser/session_test.rb
+++ b/test/integration/browser/session_test.rb
@@ -4,7 +4,7 @@ class SessionTest < BrowserIntegrationTest
test "valid session" do
login
- assert page.has_content?("Logout")
+ assert page.has_content?("Log Out")
end
test "expired session" do
diff --git a/test/support/api_integration_test.rb b/test/support/api_integration_test.rb
new file mode 100644
index 0000000..bd10f11
--- /dev/null
+++ b/test/support/api_integration_test.rb
@@ -0,0 +1,26 @@
+class ApiIntegrationTest < ActionDispatch::IntegrationTest
+
+ DUMMY_TOKEN = Token.new
+ RACK_ENV = {'HTTP_AUTHORIZATION' => %Q(Token token="#{DUMMY_TOKEN.to_s}")}
+
+ def login(user = nil)
+ @user ||= user ||= FactoryGirl.create(:user)
+ # DUMMY_TOKEN will be frozen. So let's use a dup
+ @token ||= DUMMY_TOKEN.dup
+ # make sure @token is up to date if it already exists
+ @token.reload if @token.persisted?
+ @token.user_id = @user.id
+ @token.last_seen_at = Time.now
+ @token.save
+ end
+
+ teardown do
+ if @user && @user.persisted?
+ Identity.destroy_all_for @user
+ @user.reload.destroy
+ end
+ if @token && @token.persisted?
+ @token.reload.destroy
+ end
+ end
+end
diff --git a/test/support/assert_responses.rb b/test/support/assert_responses.rb
index b01166f..19c2768 100644
--- a/test/support/assert_responses.rb
+++ b/test/support/assert_responses.rb
@@ -8,21 +8,27 @@ module AssertResponses
@response || last_response
end
- def assert_attachement_filename(name)
- assert_equal %Q(attachment; filename="#{name}"),
- get_response.headers["Content-Disposition"]
+ def content_type
+ get_response.content_type.to_s.split(';').first
end
def json_response
+ return nil unless content_type == 'application/json'
response = JSON.parse(get_response.body)
response.respond_to?(:with_indifferent_access) ?
response.with_indifferent_access :
response
end
+ def assert_text_response(body = nil)
+ assert_equal 'text/plain', content_type
+ unless body.nil?
+ assert_equal body, get_response.body
+ end
+ end
+
def assert_json_response(object)
- assert_equal 'application/json',
- get_response.content_type.to_s.split(';').first
+ assert_equal 'application/json', content_type
if object.is_a? Hash
object.stringify_keys! if object.respond_to? :stringify_keys!
assert_equal object, json_response
@@ -35,6 +41,20 @@ module AssertResponses
object.stringify_keys! if object.respond_to? :stringify_keys!
assert_json_response :errors => object
end
+
+ # checks for the presence of a key in a json response
+ # or a string in a text response
+ def assert_response_includes(string_or_key)
+ response = json_response || get_response.body
+ assert response.include?(string_or_key),
+ "response should have included #{string_or_key}"
+ end
+
+ def assert_attachement_filename(name)
+ assert_equal %Q(attachment; filename="#{name}"),
+ get_response.headers["Content-Disposition"]
+ end
+
end
class ::ActionController::TestCase
diff --git a/test/support/auth_test_helper.rb b/test/support/auth_test_helper.rb
index 57f9f9b..38c2ea1 100644
--- a/test/support/auth_test_helper.rb
+++ b/test/support/auth_test_helper.rb
@@ -19,6 +19,10 @@ module AuthTestHelper
return @current_user
end
+ def assert_login_required
+ assert_access_denied(true, false)
+ end
+
def assert_access_denied(denied = true, logged_in = true)
if denied
if @response.content_type == 'application/json'
@@ -46,8 +50,9 @@ module AuthTestHelper
protected
def header_for_token_auth
- @token = find_record(:token, :authenticate => @current_user)
- ActionController::HttpAuthentication::Token.encode_credentials @token.id
+ @token = stub_record(:token, :authenticate => @current_user)
+ Token.stubs(:find_by_token).with(@token.token).returns(@token)
+ ActionController::HttpAuthentication::Token.encode_credentials @token.token
end
def expect_warden_logout
diff --git a/test/support/browser_integration_test.rb b/test/support/browser_integration_test.rb
index 1c872ff..4fec59f 100644
--- a/test/support/browser_integration_test.rb
+++ b/test/support/browser_integration_test.rb
@@ -54,6 +54,7 @@ class BrowserIntegrationTest < ActionDispatch::IntegrationTest
end
# currently this only works for tests with poltergeist.
+ # ApiIntegrationTest has a working implementation for RackTest
def login(user = nil)
@user ||= user ||= FactoryGirl.create(:user)
token = Token.create user_id: user.id
diff --git a/test/test_helper.rb b/test/test_helper.rb
index d001ac7..7959ddb 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -27,4 +27,6 @@ class ActiveSupport::TestCase
File.join(Rails.root, 'test', 'files', name)
end
+ require 'i18n/missing_translations'
+ at_exit { I18n.missing_translations.dump }
end
diff --git a/test/unit/identity_test.rb b/test/unit/identity_test.rb
index eca104f..49b2075 100644
--- a/test/unit/identity_test.rb
+++ b/test/unit/identity_test.rb
@@ -7,6 +7,22 @@ class IdentityTest < ActiveSupport::TestCase
@user = find_record :user
end
+ test "blank identity does not crash on valid?" do
+ id = Identity.new
+ assert !id.valid?
+ end
+
+ test "enabled identity requires destination" do
+ id = Identity.new user: @user, address: @user.email_address
+ assert !id.valid?
+ assert_equal ["can't be blank"], id.errors[:destination]
+ end
+
+ test "disabled identity requires no destination" do
+ id = Identity.new address: @user.email_address
+ assert id.valid?
+ end
+
test "initial identity for a user" do
id = Identity.for(@user)
assert_equal @user.email_address, id.address
@@ -39,7 +55,7 @@ class IdentityTest < ActiveSupport::TestCase
id = Identity.create_for @user, address: alias_name, destination: forward_address
dup = Identity.build_for @user, address: alias_name, destination: forward_address
assert !dup.valid?
- assert_equal ["This alias already exists"], dup.errors[:base]
+ assert_equal ["has already been taken"], dup.errors[:destination]
id.destroy
end
@@ -48,7 +64,7 @@ class IdentityTest < ActiveSupport::TestCase
id = Identity.create_for @user, address: alias_name, destination: forward_address
taken = Identity.build_for other_user, address: alias_name
assert !taken.valid?
- assert_equal ["This email has already been taken"], taken.errors[:base]
+ assert_equal ["has already been taken"], taken.errors[:address]
id.destroy
end
@@ -107,6 +123,7 @@ class IdentityTest < ActiveSupport::TestCase
other_user = find_record :user
taken = Identity.build_for other_user, address: id.address
assert !taken.valid?
+ assert_equal ["has already been taken"], taken.errors[:address]
Identity.destroy_all_disabled
end
diff --git a/test/unit/token_test.rb b/test/unit/token_test.rb
index a3c6cf6..b143345 100644
--- a/test/unit/token_test.rb
+++ b/test/unit/token_test.rb
@@ -14,17 +14,22 @@ class ClientCertificateTest < ActiveSupport::TestCase
assert_equal @user, sample.authenticate
end
- test "token id is secure" do
+ test "token is secure" do
sample = Token.new(:user_id => @user.id)
other = Token.new(:user_id => @user.id)
- assert sample.id,
- "id is set on initialization"
- assert sample.id[0..10] != other.id[0..10],
- "token id prefixes should not repeat"
- assert /[g-zG-Z]/.match(sample.id),
- "should use non hex chars in the token id"
- assert sample.id.size > 16,
- "token id should be more than 16 chars long"
+ assert sample.token,
+ "token is set on initialization"
+ assert sample.token[0..10] != other.token[0..10],
+ "token prefixes should not repeat"
+ assert /[g-zG-Z]/.match(sample.token),
+ "should use non hex chars in the token"
+ assert sample.token.size > 16,
+ "token should be more than 16 chars long"
+ end
+
+ test "token id is hash of the token" do
+ sample = Token.new(:user_id => @user.id)
+ assert_equal Digest::SHA512.hexdigest(sample.token), sample.id
end
test "token checks for user" do
diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb
index ffbb7d8..b3c831b 100644
--- a/test/unit/user_test.rb
+++ b/test/unit/user_test.rb
@@ -65,4 +65,11 @@ class UserTest < ActiveSupport::TestCase
assert_equal key, @user.public_key
end
+ #
+ ## Regression tests
+ #
+ test "make sure valid does not crash" do
+ assert !User.new.valid?
+ end
+
end