summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml2
-rw-r--r--Gemfile5
-rw-r--r--Gemfile.lock182
-rw-r--r--INSTALL.md6
-rw-r--r--README.md68
-rw-r--r--Readme.md24
-rw-r--r--app/assets/javascripts/application.js3
-rw-r--r--app/assets/stylesheets/application.scss13
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/views/home/index.html.haml9
-rw-r--r--app/views/layouts/_navigation.html.haml6
-rw-r--r--app/views/layouts/application.html.erb14
-rw-r--r--app/views/layouts/application.html.haml23
-rw-r--r--certs/app/controllers/certs_controller.rb2
-rw-r--r--certs/test/functional/certs_controller_test.rb7
-rw-r--r--certs/test/test_helper.rb2
-rw-r--r--common_dependencies.rb2
-rw-r--r--config/config.yml8
-rw-r--r--config/config.yml.example8
-rw-r--r--config/deploy.rb37
-rw-r--r--config/initializers/client_side_validations.rb14
-rw-r--r--config/initializers/load_config.rb1
-rw-r--r--core/config/initializers/simple_form.rb307
-rw-r--r--core/config/initializers/simple_form_bootstrap.rb45
-rw-r--r--core/config/locales/simple_form.en.yml26
-rw-r--r--core/lib/extensions/testing.rb23
-rw-r--r--help/app/controllers/tickets_controller.rb73
-rw-r--r--help/app/models/ticket.rb36
-rw-r--r--help/app/models/ticket_comment.rb14
-rw-r--r--help/app/views/tickets/_comment.html.haml13
-rw-r--r--help/app/views/tickets/_new_comment.html.haml3
-rw-r--r--help/app/views/tickets/index.html.haml10
-rw-r--r--help/app/views/tickets/new.html.haml16
-rw-r--r--help/app/views/tickets/show.html.haml26
-rw-r--r--help/config/routes.rb3
-rw-r--r--help/test/functional/tickets_controller_test.rb64
-rw-r--r--help/test/unit/ticket_comment_test.rb11
-rw-r--r--help/test/unit/ticket_test.rb8
-rw-r--r--public/config/eip-service.json14
-rw-r--r--test/dummy/app/controllers/application_controller.rb2
-rw-r--r--test/test_helper.rb5
-rw-r--r--ui_dependencies.rb6
m---------users/app/assets/javascripts/srp0
-rw-r--r--users/app/assets/javascripts/users.js.coffee62
-rw-r--r--users/app/controllers/controller_extension/authentication.rb38
-rw-r--r--users/app/controllers/sessions_controller.rb27
-rw-r--r--users/app/controllers/users_controller.rb42
-rw-r--r--users/app/models/session.rb34
-rw-r--r--users/app/models/user.rb41
-rw-r--r--users/app/views/sessions/_admin_nav.html.haml6
-rw-r--r--users/app/views/sessions/_nav.html.haml13
-rw-r--r--users/app/views/sessions/new.html.haml15
-rw-r--r--users/app/views/sessions/new.json.erb3
-rw-r--r--users/app/views/users/_cancel_account.html.haml6
-rw-r--r--users/app/views/users/_form.html.haml15
-rw-r--r--users/app/views/users/_user.html.haml10
-rw-r--r--users/app/views/users/edit.html.haml5
-rw-r--r--users/app/views/users/index.html.haml17
-rw-r--r--users/app/views/users/new.html.haml13
-rw-r--r--users/config/initializers/add_controller_methods.rb3
-rw-r--r--users/config/initializers/warden.rb7
-rw-r--r--users/config/locales/en.yml14
-rw-r--r--users/config/routes.rb8
-rw-r--r--users/leap_web_users.gemspec3
-rw-r--r--users/lib/leap_web_users/engine.rb4
-rw-r--r--users/lib/warden/session_serializer.rb13
-rw-r--r--users/lib/warden/strategies/secure_remote_password.rb58
-rw-r--r--users/test/functional/application_controller_test.rb28
-rw-r--r--users/test/functional/helper_methods_test.rb39
-rw-r--r--users/test/functional/sessions_controller_test.rb95
-rw-r--r--users/test/functional/users_controller_test.rb121
-rw-r--r--users/test/integration/api/account_flow_test.rb52
-rwxr-xr-xusers/test/integration/api/python/flow_with_srp.py2
-rw-r--r--users/test/support/auth_test_helper.rb35
-rw-r--r--users/test/support/stub_record_helper.rb41
-rw-r--r--users/test/test_helper.rb3
-rw-r--r--users/test/unit/user_test.rb11
-rw-r--r--users/test/unit/warden_strategy_secure_remote_password_test.rb63
79 files changed, 1432 insertions, 660 deletions
diff --git a/.gitignore b/.gitignore
index 3567ebd..a3f0974 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,9 +13,11 @@
# Ignore all logfiles and tempfiles.
/log/*.log
/tmp
+*~
/pkg
/*/pkg
/log
+Gemfile.lock
*/Gemfile.lock
test/dummy/log/*
test/dummy/tmp/*
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..984e24a
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,2 @@
+services:
+ - couchdb
diff --git a/Gemfile b/Gemfile
index 10c661a..8b13e51 100644
--- a/Gemfile
+++ b/Gemfile
@@ -9,7 +9,8 @@ eval(File.read(File.dirname(__FILE__) + '/ui_dependencies.rb'))
gem "leap_web_core", :path => 'core'
gem 'leap_web_users', :path => 'users'
gem 'leap_web_certs', :path => 'certs'
-# gem 'leap_web_help', :path => 'help'
+gem 'leap_web_help', :path => 'help'
# To use debugger
-gem 'ruby-debug'
+gem 'debugger', :platforms => :mri_19
+gem 'ruby-debug', :platforms => :mri_18
diff --git a/Gemfile.lock b/Gemfile.lock
deleted file mode 100644
index fe49476..0000000
--- a/Gemfile.lock
+++ /dev/null
@@ -1,182 +0,0 @@
-PATH
- remote: certs
- specs:
- leap_web_certs (0.1.0)
- leap_web_core (= 0.1.0)
-
-PATH
- remote: core
- specs:
- leap_web_core (0.1.0)
- couchrest (~> 1.1.3)
- couchrest_model (~> 2.0.0.beta2)
- couchrest_session_store (~> 0.0.1)
- json
- rails (~> 3.2.8)
-
-PATH
- remote: users
- specs:
- leap_web_users (0.1.0)
- leap_web_core (= 0.1.0)
- ruby-srp (~> 0.1.1)
-
-GEM
- remote: https://rubygems.org/
- remote: http://rubygems.org/
- specs:
- actionmailer (3.2.8)
- actionpack (= 3.2.8)
- mail (~> 2.4.4)
- actionpack (3.2.8)
- activemodel (= 3.2.8)
- activesupport (= 3.2.8)
- builder (~> 3.0.0)
- erubis (~> 2.7.0)
- journey (~> 1.0.4)
- rack (~> 1.4.0)
- rack-cache (~> 1.2)
- rack-test (~> 0.6.1)
- sprockets (~> 2.1.3)
- activemodel (3.2.8)
- activesupport (= 3.2.8)
- builder (~> 3.0.0)
- activerecord (3.2.8)
- activemodel (= 3.2.8)
- activesupport (= 3.2.8)
- arel (~> 3.0.2)
- tzinfo (~> 0.3.29)
- activeresource (3.2.8)
- activemodel (= 3.2.8)
- activesupport (= 3.2.8)
- activesupport (3.2.8)
- i18n (~> 0.6)
- multi_json (~> 1.0)
- arel (3.0.2)
- bootstrap-sass (2.0.4.2)
- builder (3.0.3)
- coffee-rails (3.2.2)
- coffee-script (>= 2.2.0)
- railties (~> 3.2.0)
- coffee-script (2.2.0)
- coffee-script-source
- execjs
- coffee-script-source (1.3.3)
- columnize (0.3.6)
- couchrest (1.1.3)
- mime-types (~> 1.15)
- multi_json (~> 1.0)
- rest-client (~> 1.6.1)
- couchrest_model (2.0.0.beta2)
- activemodel (~> 3.0)
- couchrest (~> 1.1.3)
- mime-types (~> 1.15)
- tzinfo (~> 0.3.22)
- couchrest_session_store (0.0.1)
- couchrest
- couchrest_model
- erubis (2.7.0)
- execjs (1.4.0)
- multi_json (~> 1.0)
- haml (3.1.7)
- haml-rails (0.3.5)
- actionpack (>= 3.1, < 4.1)
- activesupport (>= 3.1, < 4.1)
- haml (~> 3.1)
- railties (>= 3.1, < 4.1)
- hike (1.2.1)
- i18n (0.6.1)
- journey (1.0.4)
- jquery-rails (2.1.3)
- railties (>= 3.1.0, < 5.0)
- thor (~> 0.14)
- json (1.7.5)
- libv8 (3.3.10.4)
- linecache (0.46)
- rbx-require-relative (> 0.0.4)
- mail (2.4.4)
- i18n (>= 0.4.0)
- mime-types (~> 1.16)
- treetop (~> 1.4.8)
- metaclass (0.0.1)
- mime-types (1.19)
- mocha (0.12.6)
- metaclass (~> 0.0.1)
- multi_json (1.3.6)
- polyglot (0.3.3)
- rack (1.4.1)
- rack-cache (1.2)
- rack (>= 0.4)
- rack-ssl (1.3.2)
- rack
- rack-test (0.6.2)
- rack (>= 1.0)
- rails (3.2.8)
- actionmailer (= 3.2.8)
- actionpack (= 3.2.8)
- activerecord (= 3.2.8)
- activeresource (= 3.2.8)
- activesupport (= 3.2.8)
- bundler (~> 1.0)
- railties (= 3.2.8)
- railties (3.2.8)
- actionpack (= 3.2.8)
- activesupport (= 3.2.8)
- rack-ssl (~> 1.3.2)
- rake (>= 0.8.7)
- rdoc (~> 3.4)
- thor (>= 0.14.6, < 2.0)
- rake (0.9.2.2)
- rbx-require-relative (0.0.9)
- rdoc (3.12)
- json (~> 1.4)
- rest-client (1.6.7)
- mime-types (>= 1.16)
- ruby-debug (0.10.4)
- columnize (>= 0.1)
- ruby-debug-base (~> 0.10.4.0)
- ruby-debug-base (0.10.4)
- linecache (>= 0.3)
- ruby-srp (0.1.1)
- sass (3.2.1)
- sass-rails (3.2.5)
- railties (~> 3.2.0)
- sass (>= 3.1.10)
- tilt (~> 1.3)
- simple_form (2.0.4)
- actionpack (~> 3.0)
- activemodel (~> 3.0)
- sprockets (2.1.3)
- hike (~> 1.2)
- rack (~> 1.0)
- tilt (~> 1.1, != 1.3.0)
- therubyracer (0.10.2)
- libv8 (~> 3.3.10)
- thor (0.16.0)
- tilt (1.3.3)
- treetop (1.4.10)
- polyglot
- polyglot (>= 0.3.1)
- tzinfo (0.3.33)
- uglifier (1.2.7)
- execjs (>= 0.3.0)
- multi_json (~> 1.3)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- bootstrap-sass (~> 2.0.4)
- coffee-rails (~> 3.2.2)
- haml (~> 3.1.7)
- haml-rails (~> 0.3.4)
- jquery-rails
- leap_web_certs!
- leap_web_core!
- leap_web_users!
- mocha
- ruby-debug
- sass-rails (~> 3.2.5)
- simple_form
- therubyracer
- uglifier (~> 1.2.7)
diff --git a/INSTALL.md b/INSTALL.md
index 31a3492..9e93eb0 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -6,11 +6,11 @@ The webapp only depends on very basic ruby packages and installs the other requi
### Packages ###
-For now we are using ruby 1.8.7. The following packages need to be installed:
+The following packages need to be installed:
* git
-* ruby1.8
-* rubygems1.8
+* ruby (1.8.7 and 1.9.3 work)
+* rubygems
* couchdb
### Gems ###
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0e77d82
--- /dev/null
+++ b/README.md
@@ -0,0 +1,68 @@
+LEAP Web
+---------------------
+
+"LEAP Web" is the web-based component of the LEAP Platform, providing the following services:
+
+* REST API for user registration.
+* Admin interface to manage users.
+* Client certificate distribution and renewal.
+* User support help tickets.
+
+This web application is written in Ruby on Rails 3, using CouchDB as the backend data store.
+
+Original code specific to this web application is licensed under the GNU Affero General Public License (version 3.0 or higher). See http://www.gnu.org/licenses/agpl-3.0.html for more information.
+
+Documentation
+---------------------------
+
+For more information, see these files in the ``doc`` directory:
+
+* DEPLOY -- for notes on deployment.
+* DEVELOP -- for developer notes.
+* CUSTOM -- how to customize.
+
+Installation
+---------------------------
+
+Typically, this application is installed automatically as part of the LEAP Platform. To install it manually for testing or development, follow these instructions:
+
+### Install system requirements
+
+ sudo apt-get install git ruby1.8 rubygems1.8 couchdb
+ sudo gem install bundler
+
+On Debian Wheezy or later, there is a Debian package for bundler, so you can alternately run ``sudo apt-get install bundler``.
+
+### Download source
+
+ git clone git://leap.se/leap_web
+ cd leap_web
+ git submodule update --init
+
+### Install required ruby libraries
+
+ cd leap_web
+ bundle
+
+Typically, you run ``bundle`` as a normal user and it will ask you for a sudo password when it is time to install the required gems. If you don't have sudo, run ``bundle`` as root.
+
+Configuration
+----------------------------
+
+The webapp can hand out certs for the EIP client. These certs are either picked from a pool in CouchDB or from a file. For now you can either run [Leap CA](http://github.com/leapcode/leap_ca) to fill the pool or you can put your certs file in config/cert.
+
+We also ship provider information through the webapp. For now please add your eip-service.json to the public/config directory.
+
+Copy the example configuration file and customize as appropriate:
+ cp config/config.yml.example config/config.yml
+
+Running
+-----------------------------
+
+ cd leap_web
+ rails server
+
+Then open http://localhost:3000 in your web browser.
+
+To peruse the database, visit http://localhost:5984/_utils/
+
diff --git a/Readme.md b/Readme.md
deleted file mode 100644
index 8b51b4d..0000000
--- a/Readme.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Leap Web #
-
-Web application for LEAP. Currently Leap Web allows Leap providers to manage users, hand out certs for the EIP.
-
-## Functions ##
-
-### Supported ###
-
-* *User Management* - User Registration and Authentication
-* *Cert Distribution* - Certs for the Encrypted Internet Proxy
-
-### Under Development ###
-
-* *Help Desk* - Managing Help Requests
-
-
-## Documentation ##
-
-* [INSTALL](https://github.com/leapcode/leap_web/blob/master/INSTALL.md) for installation instructions
-* [DEPLOY](https://github.com/leapcode/leap_web/blob/master/DEPLOY.md) for deployment
-* [DEVELOP](https://github.com/leapcode/leap_web/blob/master/DEVELOP.md) for developer notes.
-* [CUSTOM](https://github.com/leapcode/leap_web/blob/master/CUSTOM.md) to customize.
-
-
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index a0b89db..f7ca1ec 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -15,3 +15,6 @@
//= require srp
//= require users
//= require_tree .
+//= require bootstrap
+//= require rails.validations
+//= require rails.validations.simple_form
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index cbd46a7..8dec07d 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -1 +1,14 @@
@import "bootstrap";
+body {
+ padding: 40px;
+}
+@import "bootstrap-responsive";
+
+
+table.table-hover .btn {
+ opacity: 0;
+}
+
+table.table-hover tr:hover .btn {
+ opacity: 1;
+}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e8065d9..be7aa1f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,3 +1,5 @@
class ApplicationController < ActionController::Base
protect_from_forgery
+
+ ActiveSupport.run_load_hooks(:application_controller, self)
end
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
index 11f18de..9e68674 100644
--- a/app/views/home/index.html.haml
+++ b/app/views/home/index.html.haml
@@ -1,4 +1,11 @@
-%h1 Leap Web Demo
Try to fetch a
= link_to "cert", cert_path
+
+%p
+Try to create a
+= link_to "ticket", new_ticket_path
+
+%p
+See all
+= link_to "tickets", tickets_path
diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml
new file mode 100644
index 0000000..b75eed7
--- /dev/null
+++ b/app/views/layouts/_navigation.html.haml
@@ -0,0 +1,6 @@
+= link_to "Leap Web", root_path, :class => 'brand'
+%ul.nav
+ // = render '/tickets/nav'
+
+%ul.nav.pull-right
+ = render '/sessions/nav'
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
deleted file mode 100644
index ce68ec8..0000000
--- a/app/views/layouts/application.html.erb
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title>LeapWeb</title>
- <%= stylesheet_link_tag "application", :media => "all" %>
- <%= javascript_include_tag "application" %>
- <%= csrf_meta_tags %>
-</head>
-<body>
-
-<%= yield %>
-
-</body>
-</html>
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
new file mode 100644
index 0000000..a57d65e
--- /dev/null
+++ b/app/views/layouts/application.html.haml
@@ -0,0 +1,23 @@
+!!!
+%html
+ %head
+ %meta{:content => "width=device-width, initial-scale=1.0", :name => "viewport"}
+ %title= content_for?(:title) ? yield(:title) : "Leap Web"
+ %meta{:content => content_for?(:description) ? yield(:description) : "Leap Web", :name => "description"}
+ = stylesheet_link_tag "application", :media => "all"
+ = javascript_include_tag "application"
+ = csrf_meta_tags
+ = yield(:head)
+ %body
+ %header.navbar.navbar-fixed-top
+ %nav.navbar-inner
+ .container
+ = render 'layouts/navigation'
+ #main{:role => "main"}
+ .container
+ .content
+ .row
+ .span12
+ //= render 'layouts/messages'
+ = yield
+ %footer
diff --git a/certs/app/controllers/certs_controller.rb b/certs/app/controllers/certs_controller.rb
index 6988a38..402bef3 100644
--- a/certs/app/controllers/certs_controller.rb
+++ b/certs/app/controllers/certs_controller.rb
@@ -1,5 +1,7 @@
class CertsController < ApplicationController
+ before_filter :authorize
+
# GET /cert
def show
@cert = Cert.pick_from_pool
diff --git a/certs/test/functional/certs_controller_test.rb b/certs/test/functional/certs_controller_test.rb
index 295515b..9bba8c0 100644
--- a/certs/test/functional/certs_controller_test.rb
+++ b/certs/test/functional/certs_controller_test.rb
@@ -4,7 +4,14 @@ class CertsControllerTest < ActionController::TestCase
setup do
end
+ test "should require login" do
+ get :show
+ assert_response :redirect
+ assert_redirected_to login_url
+ end
+
test "should send cert" do
+ login
cert = stub :zipped => "adsf", :zipname => "cert_stub.zip"
Cert.expects(:pick_from_pool).returns(cert)
get :show
diff --git a/certs/test/test_helper.rb b/certs/test/test_helper.rb
index 08d4d41..f6b4eb8 100644
--- a/certs/test/test_helper.rb
+++ b/certs/test/test_helper.rb
@@ -1,7 +1,7 @@
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../../test/dummy/config/environment', __FILE__)
require 'rails/test_help'
-require 'mocha'
+require 'mocha/setup'
Rails.backtrace_cleaner.remove_silencers!
diff --git a/common_dependencies.rb b/common_dependencies.rb
index 5312a80..a6691cf 100644
--- a/common_dependencies.rb
+++ b/common_dependencies.rb
@@ -1,6 +1,6 @@
source "http://rubygems.org"
group :test do
- gem 'mocha', :require => false
+ gem 'mocha', '~> 0.13.0', :require => false
end
diff --git a/config/config.yml b/config/config.yml
new file mode 100644
index 0000000..c34dd10
--- /dev/null
+++ b/config/config.yml
@@ -0,0 +1,8 @@
+development:
+ admins: [admin, admin2]
+
+test:
+ admins: [admin, admin2]
+
+production:
+ admins: []
diff --git a/config/config.yml.example b/config/config.yml.example
new file mode 100644
index 0000000..c34dd10
--- /dev/null
+++ b/config/config.yml.example
@@ -0,0 +1,8 @@
+development:
+ admins: [admin, admin2]
+
+test:
+ admins: [admin, admin2]
+
+production:
+ admins: []
diff --git a/config/deploy.rb b/config/deploy.rb
deleted file mode 100644
index 9dc058a..0000000
--- a/config/deploy.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require "bundler/capistrano"
-
-set :application, "webapp"
-
-set :scm, :git
-set :repository, "git://leap.se/leap_web"
-set :branch, "deploy"
-
-set :deploy_via, :remote_cache
-set :deploy_to, '/home/webapp'
-set :use_sudo, false
-
-set :normalize_asset_timestamps, false
-
-set :user, "webapp"
-
-set :git_enable_submodules, 1 # we're using an srp js submodule for now
-
-role :web, "94.103.43.3" # Your HTTP server, Apache/etc
-role :app, "94.103.43.3" # This may be the same as your `Web` server
-# role :db, "your primary db-server here", :primary => true # This is where Rails migrations will run
-# role :db, "your slave db-server here"
-
-# if you want to clean up old releases on each deploy uncomment this:
-# after "deploy:restart", "deploy:cleanup"
-
-# if you're still using the script/reaper helper you will need
-# these http://github.com/rails/irs_process_scripts
-
-# If you are using Passenger mod_rails uncomment this:
-# namespace :deploy do
-# task :start do ; end
-# task :stop do ; end
-# task :restart, :roles => :app, :except => { :no_release => true } do
-# run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
-# end
-# end
diff --git a/config/initializers/client_side_validations.rb b/config/initializers/client_side_validations.rb
new file mode 100644
index 0000000..252aded
--- /dev/null
+++ b/config/initializers/client_side_validations.rb
@@ -0,0 +1,14 @@
+# ClientSideValidations Initializer
+
+# Uncomment to disable uniqueness validator, possible security issue
+ClientSideValidations::Config.disabled_validators = [:uniqueness]
+
+# Uncomment the following block if you want each input field to have the validation messages attached.
+ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
+ unless html_tag =~ /^<label/
+ %{<div class="field_with_errors">#{html_tag}<label for="#{instance.send(:tag_id)}" class="message">#{instance.error_message.first}</label></div>}.html_safe
+ else
+ %{<div class="field_with_errors">#{html_tag}</div>}.html_safe
+ end
+end
+
diff --git a/config/initializers/load_config.rb b/config/initializers/load_config.rb
new file mode 100644
index 0000000..e687429
--- /dev/null
+++ b/config/initializers/load_config.rb
@@ -0,0 +1 @@
+APP_CONFIG = YAML.load_file("#{Rails.root}/config/config.yml")[Rails.env]
diff --git a/core/config/initializers/simple_form.rb b/core/config/initializers/simple_form.rb
index b346dfa..e3f8d09 100644
--- a/core/config/initializers/simple_form.rb
+++ b/core/config/initializers/simple_form.rb
@@ -1,181 +1,142 @@
# Use this setup block to configure all options available in SimpleForm.
+SimpleForm.setup do |config|
+ # Wrappers are used by the form builder to generate a
+ # complete input. You can remove any component from the
+ # wrapper, change the order or even add your own to the
+ # stack. The options given below are used to wrap the
+ # whole input.
+ config.wrappers :default, :class => :input,
+ :hint_class => :field_with_hint, :error_class => :field_with_errors do |b|
+ ## Extensions enabled by default
+ # Any of these extensions can be disabled for a
+ # given input by passing: `f.input EXTENSION_NAME => false`.
+ # You can make any of these extensions optional by
+ # renaming `b.use` to `b.optional`.
+
+ # Determines whether to use HTML5 (:email, :url, ...)
+ # and required attributes
+ b.use :html5
+
+ # Calculates placeholders automatically from I18n
+ # You can also pass a string as f.input :placeholder => "Placeholder"
+ b.use :placeholder
+
+ ## Optional extensions
+ # They are disabled unless you pass `f.input EXTENSION_NAME => :lookup`
+ # to the input. If so, they will retrieve the values from the model
+ # if any exists. If you want to enable the lookup for any of those
+ # extensions by default, you can change `b.optional` to `b.use`.
+
+ # Calculates maxlength from length validations for string inputs
+ b.optional :maxlength
+
+ # Calculates pattern from format validations for string inputs
+ b.optional :pattern
+
+ # Calculates min and max from length validations for numeric inputs
+ b.optional :min_max
+
+ # Calculates readonly automatically from readonly attributes
+ b.optional :readonly
+
+ ## Inputs
+ b.use :label_input
+ b.use :hint, :wrap_with => { :tag => :span, :class => :hint }
+ b.use :error, :wrap_with => { :tag => :span, :class => :error }
+ end
-if defined? SimpleForm
- SimpleForm.setup do |config|
- # Wrappers are used by the form builder to generate a
- # complete input. You can remove any component from the
- # wrapper, change the order or even add your own to the
- # stack. The options given below are used to wrap the
- # whole input.
- config.wrappers :default, :class => :input,
- :hint_class => :field_with_hint, :error_class => :field_with_errors do |b|
- ## Extensions enabled by default
- # Any of these extensions can be disabled for a
- # given input by passing: `f.input EXTENSION_NAME => false`.
- # You can make any of these extensions optional by
- # renaming `b.use` to `b.optional`.
-
- # Determines whether to use HTML5 (:email, :url, ...)
- # and required attributes
- b.use :html5
-
- # Calculates placeholders automatically from I18n
- # You can also pass a string as f.input :placeholder => "Placeholder"
- b.use :placeholder
-
- ## Optional extensions
- # They are disabled unless you pass `f.input EXTENSION_NAME => :lookup`
- # to the input. If so, they will retrieve the values from the model
- # if any exists. If you want to enable the lookup for any of those
- # extensions by default, you can change `b.optional` to `b.use`.
-
- # Calculates maxlength from length validations for string inputs
- b.optional :maxlength
-
- # Calculates pattern from format validations for string inputs
- b.optional :pattern
-
- # Calculates min and max from length validations for numeric inputs
- b.optional :min_max
-
- # Calculates readonly automatically from readonly attributes
- b.optional :readonly
-
- ## Inputs
- b.use :label_input
- b.use :hint, :wrap_with => { :tag => :span, :class => :hint }
- b.use :error, :wrap_with => { :tag => :span, :class => :error }
- end
-
- config.wrappers :bootstrap, :tag => 'div', :class => 'control-group', :error_class => 'error' do |b|
- b.use :html5
- b.use :placeholder
- b.use :label
- b.wrapper :tag => 'div', :class => 'controls' do |ba|
- ba.use :input
- ba.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
- ba.use :hint, :wrap_with => { :tag => 'p', :class => 'help-block' }
- end
- end
-
- config.wrappers :prepend, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
- b.use :html5
- b.use :placeholder
- b.use :label
- b.wrapper :tag => 'div', :class => 'controls' do |input|
- input.wrapper :tag => 'div', :class => 'input-prepend' do |prepend|
- prepend.use :input
- end
- input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' }
- input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
- end
- end
-
- config.wrappers :append, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
- b.use :html5
- b.use :placeholder
- b.use :label
- b.wrapper :tag => 'div', :class => 'controls' do |input|
- input.wrapper :tag => 'div', :class => 'input-append' do |append|
- append.use :input
- end
- input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' }
- input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
- end
- end
-
- # Wrappers for forms and inputs using the Twitter Bootstrap toolkit.
- # Check the Bootstrap docs (http://twitter.github.com/bootstrap)
- # to learn about the different styles for forms and inputs,
- # buttons and other elements.
- config.default_wrapper = :bootstrap
-
- # Define the way to render check boxes / radio buttons with labels.
- # Defaults to :nested for bootstrap config.
- # :inline => input + label
- # :nested => label > input
- config.boolean_style = :nested
-
- # Default class for buttons
- config.button_class = 'btn'
-
- # Method used to tidy up errors. Specify any Rails Array method.
- # :first lists the first message for each field.
- # Use :to_sentence to list all errors for each field.
- # config.error_method = :first
-
- # Default tag used for error notification helper.
- config.error_notification_tag = :div
-
- # CSS class to add for error notification helper.
- config.error_notification_class = 'alert alert-error'
-
- # ID to add for error notification helper.
- # config.error_notification_id = nil
-
- # Series of attempts to detect a default label method for collection.
- # config.collection_label_methods = [ :to_label, :name, :title, :to_s ]
-
- # Series of attempts to detect a default value method for collection.
- # config.collection_value_methods = [ :id, :to_s ]
-
- # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
- # config.collection_wrapper_tag = nil
-
- # You can define the class to use on all collection wrappers. Defaulting to none.
- # config.collection_wrapper_class = nil
-
- # You can wrap each item in a collection of radio/check boxes with a tag,
- # defaulting to :span. Please note that when using :boolean_style = :nested,
- # SimpleForm will force this option to be a label.
- # config.item_wrapper_tag = :span
-
- # You can define a class to use in all item wrappers. Defaulting to none.
- # config.item_wrapper_class = nil
-
- # How the label text should be generated altogether with the required text.
- # config.label_text = lambda { |label, required| "#{required} #{label}" }
-
- # You can define the class to use on all labels. Default is nil.
- config.label_class = 'control-label'
-
- # You can define the class to use on all forms. Default is simple_form.
- config.form_class = 'form-horizontal'
-
- # You can define which elements should obtain additional classes
- # config.generate_additional_classes_for = [:wrapper, :label, :input]
-
- # Whether attributes are required by default (or not). Default is true.
- # config.required_by_default = true
-
- # Tell browsers whether to use default HTML5 validations (novalidate option).
- # Default is enabled.
- config.browser_validations = true
-
- # Collection of methods to detect if a file type was given.
- # config.file_methods = [ :mounted_as, :file?, :public_filename ]
-
- # Custom mappings for input types. This should be a hash containing a regexp
- # to match as key, and the input type that will be used when the field name
- # matches the regexp as value.
- # config.input_mappings = { /count/ => :integer }
-
- # Default priority for time_zone inputs.
- # config.time_zone_priority = nil
+ # The default wrapper to be used by the FormBuilder.
+ config.default_wrapper = :default
- # Default priority for country inputs.
- # config.country_priority = nil
+ # Define the way to render check boxes / radio buttons with labels.
+ # Defaults to :nested for bootstrap config.
+ # :inline => input + label
+ # :nested => label > input
+ config.boolean_style = :nested
- # Default size for text inputs.
- # config.default_input_size = 50
+ # Default class for buttons
+ config.button_class = 'btn'
- # When false, do not use translations for labels.
- # config.translate_labels = true
+ # Method used to tidy up errors. Specify any Rails Array method.
+ # :first lists the first message for each field.
+ # Use :to_sentence to list all errors for each field.
+ # config.error_method = :first
- # Automatically discover new inputs in Rails' autoload path.
- # config.inputs_discovery = true
+ # Default tag used for error notification helper.
+ config.error_notification_tag = :div
- # Cache SimpleForm inputs discovery
- # config.cache_discovery = !Rails.env.development?
- end
+ # CSS class to add for error notification helper.
+ config.error_notification_class = 'alert alert-error'
+
+ # ID to add for error notification helper.
+ # config.error_notification_id = nil
+
+ # Series of attempts to detect a default label method for collection.
+ # config.collection_label_methods = [ :to_label, :name, :title, :to_s ]
+
+ # Series of attempts to detect a default value method for collection.
+ # config.collection_value_methods = [ :id, :to_s ]
+
+ # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
+ # config.collection_wrapper_tag = nil
+
+ # You can define the class to use on all collection wrappers. Defaulting to none.
+ # config.collection_wrapper_class = nil
+
+ # You can wrap each item in a collection of radio/check boxes with a tag,
+ # defaulting to :span. Please note that when using :boolean_style = :nested,
+ # SimpleForm will force this option to be a label.
+ # config.item_wrapper_tag = :span
+
+ # You can define a class to use in all item wrappers. Defaulting to none.
+ # config.item_wrapper_class = nil
+
+ # How the label text should be generated altogether with the required text.
+ # config.label_text = lambda { |label, required| "#{required} #{label}" }
+
+ # You can define the class to use on all labels. Default is nil.
+ config.label_class = 'control-label'
+
+ # You can define the class to use on all forms. Default is simple_form.
+ # config.form_class = :simple_form
+
+ # You can define which elements should obtain additional classes
+ # config.generate_additional_classes_for = [:wrapper, :label, :input]
+
+ # Whether attributes are required by default (or not). Default is true.
+ # config.required_by_default = true
+
+ # Tell browsers whether to use default HTML5 validations (novalidate option).
+ # Default is enabled.
+ config.browser_validations = false
+
+ # Collection of methods to detect if a file type was given.
+ # config.file_methods = [ :mounted_as, :file?, :public_filename ]
+
+ # Custom mappings for input types. This should be a hash containing a regexp
+ # to match as key, and the input type that will be used when the field name
+ # matches the regexp as value.
+ # config.input_mappings = { /count/ => :integer }
+
+ # Custom wrappers for input types. This should be a hash containing an input
+ # type as key and the wrapper that will be used for all inputs with specified type.
+ # config.wrapper_mappings = { :string => :prepend }
+
+ # Default priority for time_zone inputs.
+ # config.time_zone_priority = nil
+
+ # Default priority for country inputs.
+ # config.country_priority = nil
+
+ # Default size for text inputs.
+ # config.default_input_size = 50
+
+ # When false, do not use translations for labels.
+ # config.translate_labels = true
+
+ # Automatically discover new inputs in Rails' autoload path.
+ # config.inputs_discovery = true
+
+ # Cache SimpleForm inputs discovery
+ # config.cache_discovery = !Rails.env.development?
end
diff --git a/core/config/initializers/simple_form_bootstrap.rb b/core/config/initializers/simple_form_bootstrap.rb
new file mode 100644
index 0000000..1a22967
--- /dev/null
+++ b/core/config/initializers/simple_form_bootstrap.rb
@@ -0,0 +1,45 @@
+# Use this setup block to configure all options available in SimpleForm.
+SimpleForm.setup do |config|
+ config.wrappers :bootstrap, :tag => 'div', :class => 'control-group', :error_class => 'error' do |b|
+ b.use :html5
+ b.use :placeholder
+ b.use :label
+ b.wrapper :tag => 'div', :class => 'controls' do |ba|
+ ba.use :input
+ ba.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
+ ba.use :hint, :wrap_with => { :tag => 'p', :class => 'help-block' }
+ end
+ end
+
+ config.wrappers :prepend, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
+ b.use :html5
+ b.use :placeholder
+ b.use :label
+ b.wrapper :tag => 'div', :class => 'controls' do |input|
+ input.wrapper :tag => 'div', :class => 'input-prepend' do |prepend|
+ prepend.use :input
+ end
+ input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' }
+ input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
+ end
+ end
+
+ config.wrappers :append, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
+ b.use :html5
+ b.use :placeholder
+ b.use :label
+ b.wrapper :tag => 'div', :class => 'controls' do |input|
+ input.wrapper :tag => 'div', :class => 'input-append' do |append|
+ append.use :input
+ end
+ input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' }
+ input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
+ end
+ end
+
+ # Wrappers for forms and inputs using the Twitter Bootstrap toolkit.
+ # Check the Bootstrap docs (http://twitter.github.com/bootstrap)
+ # to learn about the different styles for forms and inputs,
+ # buttons and other elements.
+ config.default_wrapper = :bootstrap
+end
diff --git a/core/config/locales/simple_form.en.yml b/core/config/locales/simple_form.en.yml
new file mode 100644
index 0000000..0df11fe
--- /dev/null
+++ b/core/config/locales/simple_form.en.yml
@@ -0,0 +1,26 @@
+en:
+ simple_form:
+ "yes": 'Yes'
+ "no": 'No'
+ required:
+ text: 'required'
+ mark: '*'
+ # You can uncomment the line below if you need to overwrite the whole required html.
+ # When using html, text and mark won't be used.
+ # html: '<abbr title="required">*</abbr>'
+ error_notification:
+ default_message: "Please review the problems below:"
+ # Labels and hints examples
+ # labels:
+ # defaults:
+ # password: 'Password'
+ # user:
+ # new:
+ # email: 'E-mail to sign in.'
+ # edit:
+ # email: 'E-mail.'
+ # hints:
+ # defaults:
+ # username: 'User name to sign in.'
+ # password: 'No special characters, please.'
+
diff --git a/core/lib/extensions/testing.rb b/core/lib/extensions/testing.rb
index 14a5698..925c023 100644
--- a/core/lib/extensions/testing.rb
+++ b/core/lib/extensions/testing.rb
@@ -1,17 +1,32 @@
module LeapWebCore
module AssertResponses
+ # response that works with different TestCases:
+ # ActionController::TestCase has @response
+ # ActionDispatch::IntegrationTest has @response
+ # Rack::Test::Methods defines last_response
+ def get_response
+ @response || last_response
+ end
+
def assert_attachement_filename(name)
assert_equal %Q(attachment; filename="#{name}"),
- @response.headers["Content-Disposition"]
+ get_response.headers["Content-Disposition"]
end
-
def assert_json_response(object)
- object.stringify_keys! if object.respond_to? :stringify_keys!
- assert_equal object, JSON.parse(@response.body)
+ if object.is_a? Hash
+ object.stringify_keys! if object.respond_to? :stringify_keys!
+ assert_equal object, JSON.parse(get_response.body)
+ else
+ assert_equal object.to_json, get_response.body
+ end
end
+ def assert_json_error(object)
+ object.stringify_keys! if object.respond_to? :stringify_keys!
+ assert_json_response :errors => object
+ end
end
end
diff --git a/help/app/controllers/tickets_controller.rb b/help/app/controllers/tickets_controller.rb
new file mode 100644
index 0000000..b5f3a63
--- /dev/null
+++ b/help/app/controllers/tickets_controller.rb
@@ -0,0 +1,73 @@
+class TicketsController < ApplicationController
+
+ respond_to :html #, :json
+ #has_scope :open, :type => boolean
+
+ def new
+ @ticket = Ticket.new
+ @ticket.comments.build
+ end
+
+ def create
+ @ticket = Ticket.new(params[:ticket])
+ if current_user
+ @ticket.created_by = current_user.id
+ @ticket.email = current_user.email if current_user.email
+ @ticket.comments.last.posted_by = current_user.id
+ else
+ @ticket.comments.last.posted_by = nil #hacky, but protecting this attribute doesn't work right, so this should make sure it isn't set.
+ end
+
+ flash[:notice] = 'Ticket was successfully created.' if @ticket.save
+ respond_with(@ticket)
+
+ end
+
+=begin
+ def edit
+ @ticket = Ticket.find(params[:id])
+ @ticket.comments.build
+ # build ticket comments?
+ end
+=end
+
+ def show
+ @ticket = Ticket.find(params[:id])
+ # @ticket.comments.build
+ # build ticket comments?
+ end
+
+ def update
+ @ticket = Ticket.find(params[:id])
+ @ticket.attributes = params[:ticket]
+
+ @ticket.comments.last.posted_by = (current_user ? current_user.id : nil) #protecting posted_by isn't working, so this should protect it.
+
+ if @ticket.save
+ flash[:notice] = 'Ticket was successfully updated.'
+ respond_with @ticket
+ else
+ #redirect_to [:show, @ticket] #
+ flash[:alert] = 'Ticket has not been changed'
+ redirect_to @ticket
+ #respond_with(@ticket) # why does this go to edit?? redirect???
+ end
+ end
+
+ def index
+ # @tickets = Ticket.by_title #not actually what we will want
+ respond_with(@tickets = Ticket.all) #we'll want only tickets that this user can access
+ end
+
+ private
+
+ # not using now, as we are using comment_attributes= from the Ticket model
+=begin
+ def add_comment
+ comment = TicketComment.new(params[:comment])
+ comment.posted_by = User.current.id if User.current #could be nil
+ comment.posted_at = Time.now # TODO: it seems strange to have this here, and not in model
+ @ticket.comments << comment
+ end
+=end
+end
diff --git a/help/app/models/ticket.rb b/help/app/models/ticket.rb
index 784d7ef..f38fed2 100644
--- a/help/app/models/ticket.rb
+++ b/help/app/models/ticket.rb
@@ -15,8 +15,8 @@ class Ticket < CouchRest::Model::Base
=end
#belongs_to :user #from leap_web_users. doesn't necessarily belong to a user though
- property :created_by, Integer #nil unless user was authenticated for ticket creation, #THIS should not be changed after being set
- property :regarding_user, Integer # form cannot be submitted if they type in a username w/out corresponding ID. this field can be nil. for authenticated ticket creation by non-admins, should this just automatically be set to be same as created_by? or maybe we don't use this field unless created_by is nil?
+ property :created_by, String, :protected => true #Integer #nil unless user was authenticated for ticket creation, #THIS should not be changed after being set
+ #property :regarding_user, String#Integer # form cannot be submitted if they type in a username w/out corresponding ID. this field can be nil. for authenticated ticket creation by non-admins, should this just automatically be set to be same as created_by? or maybe we don't use this field unless created_by is nil?
#also, both created_by and regarding_user could be nil---say user forgets username, or has general question
property :title, String
property :email, String #verify
@@ -29,18 +29,27 @@ class Ticket < CouchRest::Model::Base
timestamps!
- before_validation :set_created_by, :set_code, :on => :create
+ #before_validation :set_created_by, :set_code, :set_email, :on => :create
+ before_validation :set_code, :set_email, :on => :create
+
+
+ #named_scope :open, :conditions => {:is_open => true} #??
design do
view :by_title
end
+ validates :title, :presence => true
+ #validates :comments, :presence => true #do we want it like this?
+
+ # html5 has built-in validation which isn't ideal, as it says 'please enter an email address' for invalid email addresses, which implies an email address is required, and it is not.
validates :email, :format => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/, :if => :email #email address is optional
- def set_created_by
- self.created_by = User.current if User.current
- end
+ #TODO:
+ #def set_created_by
+ # self.created_by = User.current if User.current
+ #end
def is_creator_validated?
!!created_by
@@ -51,6 +60,12 @@ class Ticket < CouchRest::Model::Base
self.code = SecureRandom.hex(8) if !is_creator_validated?
end
+
+ def set_email
+ self.email = nil if self.email == ""
+ # in controller set to be current users email if that exists
+ end
+
def close
self.is_open = false
save
@@ -61,6 +76,15 @@ class Ticket < CouchRest::Model::Base
save
end
+ def comments_attributes=(attributes)
+
+ comment = TicketComment.new(attributes.values.first) #TicketComment.new(attributes)
+ #comment.posted_by = User.current.id if User.current #we want to avoid User.current, and current_user won't work here. instead will set in tickets_controller
+ comment.posted_at = Time.now
+ comments << comment
+
+ end
+
=begin
def validate
if email_address and not email_address.strip =~ RFC822::EmailAddress
diff --git a/help/app/models/ticket_comment.rb b/help/app/models/ticket_comment.rb
index 652133a..49e5c6c 100644
--- a/help/app/models/ticket_comment.rb
+++ b/help/app/models/ticket_comment.rb
@@ -2,13 +2,15 @@ class TicketComment
include CouchRest::Model::Embeddable
#belongs_to :ticket #is this best way to do it? will want to access all of a tickets comments, so maybe this isn't the way?
- property :posted_by, Integer, :protected => true# maybe this should be current_user if that is set, meaning the user is logged in #String # user??
+ property :posted_by, String#, :protected => true #Integer#this should be current_user if that is set, meaning the user is logged in #cannot have it be protected and set via comments_attributes=. also, if it is protected and we set in the tickets_controller, it gets unset. TODO---is this okay to have it not protected and manually check it? We do not users to be able to set this.
# if the current user is not set, then we could just say the comment comes from an 'unauthenticated user', which would be somebody with the secret URL
- property :posted_at, Time, :protected => true
+ property :posted_at, Time#, :protected => true
#property :posted_verified, TrueClass, :protected => true #should be true if current_user is set when the comment is created
property :body, String
- before_validation :set_time#, :set_posted_by
+ # ? timestamps!
+ validates :body, :presence => true
+ #before_validation :set_time#, :set_posted_by
#design do
# view :by_posted_at
@@ -18,10 +20,14 @@ class TicketComment
def is_comment_validated?
!!posted_by
end
-
+
+=begin
+ #TODO.
+ #this is resetting all comments associated with the ticket:
def set_time
self.posted_at = Time.now
end
+=end
=begin
def set_posted_by
diff --git a/help/app/views/tickets/_comment.html.haml b/help/app/views/tickets/_comment.html.haml
new file mode 100644
index 0000000..1ba3bd1
--- /dev/null
+++ b/help/app/views/tickets/_comment.html.haml
@@ -0,0 +1,13 @@
+- # style is super ugly but just for now
+%div{:style => "border: solid 1px"}
+ - if User.find(comment.posted_by)
+ Posted by
+ = User.find(comment.posted_by).login
+ - else
+ Unauthenticated post
+ %p
+ Posted at
+ = comment.posted_at
+ %p
+ = comment.body
+ %p \ No newline at end of file
diff --git a/help/app/views/tickets/_new_comment.html.haml b/help/app/views/tickets/_new_comment.html.haml
new file mode 100644
index 0000000..a924dfd
--- /dev/null
+++ b/help/app/views/tickets/_new_comment.html.haml
@@ -0,0 +1,3 @@
+= #do we want this partial? not using it now
+= simple_fields_for :comment do |c|
+ = c.input :body, :label => 'Comment', :as => :text
diff --git a/help/app/views/tickets/index.html.haml b/help/app/views/tickets/index.html.haml
new file mode 100644
index 0000000..6db2140
--- /dev/null
+++ b/help/app/views/tickets/index.html.haml
@@ -0,0 +1,10 @@
+%h2 tickets index (just as space)
+Create a
+= link_to "new ticket", new_ticket_path
+= # below shouldn't be unless logged in
+%h2 Tickets
+= # want to have selection option to see tickets, that are open, closed or all
+- @tickets.each do |ticket|
+ %p
+ = link_to ticket.title, ticket
+= #render(:partial => "ticket", :collection => @tickets)
diff --git a/help/app/views/tickets/new.html.haml b/help/app/views/tickets/new.html.haml
new file mode 100644
index 0000000..537b97f
--- /dev/null
+++ b/help/app/views/tickets/new.html.haml
@@ -0,0 +1,16 @@
+%h2=t :new_ticket
+= simple_form_for(@ticket, :html => {:novalidate => true}) do |f| #turn off html5 validations to test
+ = #@ticket.errors.messages
+ = f.input :title
+ = #f.input :email #if there is no current_user
+ = f.input :email if !current_user #hmm--might authenticated users want to submit an alternate email?
+
+ = f.simple_fields_for :comments do |c|
+ = c.input :body, :label => 'Comment', :as => :text
+
+ = #render :partial => 'new_comment' #what we were using
+ = # regarding_user if not logged in
+ = # email if not logged in
+ = #f.button :submit, :value => t(:submit), :class => 'btn-primary'
+ = f.button :submit
+ = link_to t(:cancel), tickets_path, :class => :btn
diff --git a/help/app/views/tickets/show.html.haml b/help/app/views/tickets/show.html.haml
new file mode 100644
index 0000000..a9b994e
--- /dev/null
+++ b/help/app/views/tickets/show.html.haml
@@ -0,0 +1,26 @@
+- if flash[:notice]
+ =flash[:notice]
+- if flash[:alert]
+ =flash[:alert]
+%h2= @ticket.title
+is open?
+= @ticket.is_open
+- if @ticket.code
+ code:
+ = @ticket.code
+- if @ticket.email
+ email:
+ = @ticket.email
+- if User.find(@ticket.created_by)
+ Created by
+ = User.find(@ticket.created_by).login
+- else
+ Unauthenticated ticket creator
+= render(:partial => "comment", :collection => @ticket.comments)
+
+= simple_form_for (@ticket, :html => {:novalidate => true}) do |f| #turn off html5 validations to test
+ = f.simple_fields_for :comments, TicketComment.new do |c|
+ = c.input :body, :label => 'Comment', :as => :text
+ = #render :partial => 'new_comment'
+ = f.button :submit
+ = link_to t(:cancel), tickets_path, :class => :btn \ No newline at end of file
diff --git a/help/config/routes.rb b/help/config/routes.rb
index 1daf9a4..5e57e02 100644
--- a/help/config/routes.rb
+++ b/help/config/routes.rb
@@ -1,2 +1,5 @@
Rails.application.routes.draw do
+
+ resources :tickets, :only => [:new, :create, :index, :show, :update]
+ #resources :ticket, :only => [:show]
end
diff --git a/help/test/functional/tickets_controller_test.rb b/help/test/functional/tickets_controller_test.rb
new file mode 100644
index 0000000..b9e03ac
--- /dev/null
+++ b/help/test/functional/tickets_controller_test.rb
@@ -0,0 +1,64 @@
+require 'test_helper'
+
+class TicketsControllerTest < ActionController::TestCase
+
+ test "should get index" do
+ get :index
+ assert_response :success
+ assert_not_nil assigns(:tickets)
+ end
+
+ test "should get new" do
+ get :new
+ assert_equal Ticket, assigns(:ticket).class
+ assert_response :success
+ end
+
+
+ test "should create unauthenticated ticket" do
+ params = {:title => "ticket test title", :comments_attributes => {"0" => {"body" =>"body of test ticket"}}}
+
+ assert_difference('Ticket.count') do
+ post :create, :ticket => params
+ end
+
+ assert_response :redirect
+ #assert_equal assigns(:ticket).email, User.current.email
+ #assert_equal User.find(assigns(:ticket).created_by).login, User.current.login
+ assert_nil assigns(:ticket).created_by
+
+ assert_equal 1, assigns(:ticket).comments.count
+ end
+
+
+ test "should create authenticated ticket" do
+
+ params = {:title => "ticket test title", :comments_attributes => {"0" => {"body" =>"body of test ticket"}}}
+
+ login :email => "test@email.net"
+
+ assert_difference('Ticket.count') do
+ post :create, :ticket => params
+ end
+
+ assert_response :redirect
+ ticket = assigns(:ticket)
+ assert ticket
+ assert_equal @current_user.id, ticket.created_by
+ assert_equal @current_user.email, ticket.email
+
+ assert_equal 1, assigns(:ticket).comments.count
+ end
+
+ test "add comment to ticket" do
+
+ ticket = Ticket.last
+ assert_difference('Ticket.last.comments.count') do
+ put :update, :id => ticket.id,
+ :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} }
+ end
+ assert_equal ticket, assigns(:ticket)
+
+ end
+
+end
diff --git a/help/test/unit/ticket_comment_test.rb b/help/test/unit/ticket_comment_test.rb
index 883720f..1fe1fe2 100644
--- a/help/test/unit/ticket_comment_test.rb
+++ b/help/test/unit/ticket_comment_test.rb
@@ -16,8 +16,8 @@ class TicketCommentTest < ActiveSupport::TestCase
comment2 = TicketComment.new :body => "help my email is broken!"
assert comment2.valid?
- assert_not_nil comment2.posted_at
- assert_nil comment2.posted_by #if not logged in
+ #assert_not_nil comment2.posted_at #?
+ #assert_nil comment2.posted_by #if not logged in #TODO
#comment.ticket = testticket #Ticket.find_by_title("testing")
#assert_equal testticket.title, comment.ticket.title
@@ -49,9 +49,10 @@ class TicketCommentTest < ActiveSupport::TestCase
testticket.comments << comment2 #this should validate comment2
testticket.valid?
assert_equal testticket.comments.count, 2
- assert_not_nil comment.posted_at
- assert_not_nil testticket.comments.last.posted_at
- assert testticket.comments.first.posted_at < testticket.comments.last.posted_at
+ # where should posted_at be set?
+ #assert_not_nil comment.posted_at
+ #assert_not_nil testticket.comments.last.posted_at
+ #assert testticket.comments.first.posted_at < testticket.comments.last.posted_at
end
end
diff --git a/help/test/unit/ticket_test.rb b/help/test/unit/ticket_test.rb
index c3a4759..6b63a23 100644
--- a/help/test/unit/ticket_test.rb
+++ b/help/test/unit/ticket_test.rb
@@ -41,18 +41,20 @@ class TicketTest < ActiveSupport::TestCase
assert @sample.is_creator_validated?
end
+=begin
+# TODO: do once have current_user stuff in order
test "code if & only if not creator-validated" do
+ User.current_test = nil
t1 = Ticket.create :title => 'test title'
assert_not_nil t1.code
assert_nil t1.created_by
- User.current = 4
+ User.current_test = 4
t2 = Ticket.create :title => 'test title'
assert_nil t2.code
assert_not_nil t2.created_by
-
-
end
+=end
end
diff --git a/public/config/eip-service.json b/public/config/eip-service.json
deleted file mode 100644
index 29959d7..0000000
--- a/public/config/eip-service.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "serial": 1,
- "version": "0.1.0",
- "capabilities": {
- "transport": ["openvpn"],
- "ports": ["80","53", "443", "1194"],
- "protocols": ["udp"],
- "static_ips": false,
- "adblock": false
- },
- "gateways": [
- {"country_code": "tr", "name": "turkey", "label": {"en":"Ankara, Turkey"}, "capabilities": {}, "hosts": ["94.103.43.4"]}
- ]
-}
diff --git a/test/dummy/app/controllers/application_controller.rb b/test/dummy/app/controllers/application_controller.rb
index e8065d9..be7aa1f 100644
--- a/test/dummy/app/controllers/application_controller.rb
+++ b/test/dummy/app/controllers/application_controller.rb
@@ -1,3 +1,5 @@
class ApplicationController < ActionController::Base
protect_from_forgery
+
+ ActiveSupport.run_load_hooks(:application_controller, self)
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index f7d48ec..0016771 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -2,7 +2,10 @@ ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
-require 'mocha'
+require 'mocha/setup'
+
+# Load support files from all engines
+Dir["#{File.dirname(__FILE__)}/../*/test/support/**/*.rb"].each { |f| require f }
class ActiveSupport::TestCase
# Add more helper methods to be used by all tests here...
diff --git a/ui_dependencies.rb b/ui_dependencies.rb
index 21de1c6..454e9a8 100644
--- a/ui_dependencies.rb
+++ b/ui_dependencies.rb
@@ -1,7 +1,9 @@
gem "haml", "~> 3.1.7"
-gem "bootstrap-sass", "~> 2.0.4"
+gem "bootstrap-sass", "~> 2.1.0"
gem "jquery-rails"
gem "simple_form"
+gem 'client_side_validations'
+gem 'client_side_validations-simple_form'
group :assets do
gem "haml-rails", "~> 0.3.4"
@@ -10,7 +12,7 @@ group :assets do
gem "uglifier", "~> 1.2.7"
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
- gem 'therubyracer', :platforms => :ruby
+ gem 'therubyracer', "~> 0.10.2", :platforms => :ruby
end
diff --git a/users/app/assets/javascripts/srp b/users/app/assets/javascripts/srp
-Subproject d6a78049f3356d9d645143362eca74434410bf6
+Subproject fff770a866b44abce6fe0fc5d5ffde034225436
diff --git a/users/app/assets/javascripts/users.js.coffee b/users/app/assets/javascripts/users.js.coffee
index 160a7f0..76a6d79 100644
--- a/users/app/assets/javascripts/users.js.coffee
+++ b/users/app/assets/javascripts/users.js.coffee
@@ -1,41 +1,35 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
-#
+preventDefault = (event) ->
+ event.preventDefault()
-validate_password = (event) ->
+srp.session = new srp.Session()
+srp.signedUp = ->
+ srp.login
- password = $('#srp_password').val()
- confirmation = $('#srp_password_confirmation').val()
- login = $('#srp_username').val()
+srp.loggedIn = ->
+ window.location = '/'
- if password != confirmation
- alert "Password and Confirmation do not match!"
- $('#srp_password').focus()
- return false
- if password == login
- alert "Password and Login may not match!"
- $('#srp_password').focus()
- return false
- if password.length < 8
- alert "Password needs to be at least 8 characters long!"
- $('#srp_password').focus()
- return false
-
- return true
-
+#// TODO: not sure this is what we want.
+srp.updated = ->
+ window.location = '/'
-insert_verifier = (event) ->
- # TODO: verify password confimation
- srp = new SRP
- salt = srp.session.getSalt()
- $('#srp_salt').val(salt)
- $('#srp_password_verifier').val(srp.session.getV().toString(16))
- # clear the password so we do not submit it
- $('#srp_password').val('cleared out - use verifier instead')
- $('#srp_password_confirmation').val('using srp - store verifier')
+srp.error = (message) ->
+ if $.isPlainObject(message) && message.errors
+ for field, error of message.errors
+ element = $('form input[name$="['+field+']"]')
+ next unless element
+ element.trigger('element:validate:fail.ClientSideValidations', error).data('valid', false)
+ else
+ alert(message)
+
+pollUsers = (query, process) ->
+ $.get( "/users.json", query: query).done(process)
$(document).ready ->
- $('#new_user').submit validate_password
- $('#new_user').submit insert_verifier
+ $('#new_user').submit preventDefault
+ $('#new_user').submit srp.signup
+ $('#new_session').submit preventDefault
+ $('#new_session').submit srp.login
+ $('.user.form.edit').submit srp.update
+ $('.user.form.edit').submit preventDefault
+ $('.user.typeahead').typeahead({source: pollUsers});
diff --git a/users/app/controllers/controller_extension/authentication.rb b/users/app/controllers/controller_extension/authentication.rb
new file mode 100644
index 0000000..6ac7a5b
--- /dev/null
+++ b/users/app/controllers/controller_extension/authentication.rb
@@ -0,0 +1,38 @@
+module ControllerExtension::Authentication
+ extend ActiveSupport::Concern
+
+ private
+
+ included do
+ helper_method :current_user, :logged_in?, :admin?
+ end
+
+ def authentication_errors
+ return unless errors = warden.winning_strategy.try(:message)
+ errors.inject({}) do |translated,err|
+ translated[err.first] = I18n.t(err.last)
+ translated
+ end
+ end
+
+ def logged_in?
+ !!current_user
+ end
+
+ def authorize
+ access_denied unless logged_in?
+ end
+
+ def access_denied
+ redirect_to login_url, :alert => "Not authorized"
+ end
+
+ def admin?
+ current_user && current_user.is_admin?
+ end
+
+ def authorize_admin
+ access_denied unless admin?
+ end
+
+end
diff --git a/users/app/controllers/sessions_controller.rb b/users/app/controllers/sessions_controller.rb
index 284c0e2..bc910b5 100644
--- a/users/app/controllers/sessions_controller.rb
+++ b/users/app/controllers/sessions_controller.rb
@@ -3,33 +3,24 @@ class SessionsController < ApplicationController
skip_before_filter :verify_authenticity_token
def new
+ @session = Session.new
+ if authentication_errors
+ @errors = authentication_errors
+ render :status => 422
+ end
end
def create
- @user = User.find_by_param(params[:login])
- session[:handshake] = @user.initialize_auth(params['A'].hex)
- User.current = @user #?
- render :json => session[:handshake]
- rescue RECORD_NOT_FOUND
- render :json => {:errors => {:login => ["unknown user"]}}
+ authenticate!
end
def update
- # TODO: validate the id belongs to the session
- @user = User.find_by_param(params[:id])
- @srp_session = session.delete(:handshake)
- @srp_session.authenticate!(params[:client_auth].hex)
- session[:user_id] = @user.id
- User.current = @user #?
- render :json => @srp_session
- rescue WRONG_PASSWORD
- session[:handshake] = nil
- render :json => {:errors => {"password" => ["wrong password"]}}
+ authenticate!
+ render :json => session.delete(:handshake)
end
def destroy
- session[:user_id] = nil
- User.current = nil #?
+ logout
redirect_to root_path
end
end
diff --git a/users/app/controllers/users_controller.rb b/users/app/controllers/users_controller.rb
index 82d2eac..cffc8c6 100644
--- a/users/app/controllers/users_controller.rb
+++ b/users/app/controllers/users_controller.rb
@@ -1,18 +1,48 @@
class UsersController < ApplicationController
- skip_before_filter :verify_authenticity_token
+ skip_before_filter :verify_authenticity_token, :only => [:create]
+
+ before_filter :fetch_user, :only => [:edit, :update, :destroy]
+ before_filter :authorize_admin, :only => [:index]
respond_to :json, :html
+ def index
+ if params[:query]
+ @users = User.by_login.startkey(params[:query]).endkey(params[:query].succ)
+ else
+ @users = User.by_created_at.descending
+ end
+ @users = @users.limit(10)
+ respond_with @users.map(&:login).sort
+ end
+
def new
@user = User.new
end
def create
- @user = User.create!(params[:user])
- respond_with(@user, :location => root_url, :notice => "Signed up!")
- rescue VALIDATION_FAILED => e
- @user = e.document
- respond_with(@user, :location => new_user_path)
+ @user = User.create(params[:user])
+ respond_with @user
+ end
+
+ def edit
+ end
+
+ def update
+ @user.update_attributes(params[:user])
+ respond_with @user
+ end
+
+ def destroy
+ @user.destroy
+ redirect_to admin? ? users_path : login_path
+ end
+
+ protected
+
+ def fetch_user
+ @user = User.find_by_param(params[:id])
+ access_denied unless admin? or (@user == current_user)
end
end
diff --git a/users/app/models/session.rb b/users/app/models/session.rb
new file mode 100644
index 0000000..a9fdb1b
--- /dev/null
+++ b/users/app/models/session.rb
@@ -0,0 +1,34 @@
+class Session < SRP::Session
+ include ActiveModel::Validations
+
+ attr_accessor :login
+
+ validates :login,
+ :presence => true,
+ :format => { :with => /\A[A-Za-z\d_]+\z/,
+ :message => "Only letters, digits and _ allowed" }
+
+ def initialize(user = nil, aa = nil)
+ super(user, aa) if user
+ end
+
+ def persisted?
+ false
+ end
+
+ def new_record?
+ true
+ end
+
+ def to_model
+ self
+ end
+
+ def to_key
+ [object_id]
+ end
+
+ def to_param
+ nil
+ end
+end
diff --git a/users/app/models/user.rb b/users/app/models/user.rb
index 1afb9db..325c981 100644
--- a/users/app/models/user.rb
+++ b/users/app/models/user.rb
@@ -9,42 +9,46 @@ class User < CouchRest::Model::Base
:presence => true
validates :login,
- :uniqueness => true
+ :uniqueness => true,
+ :if => :serverside?
validates :login,
:format => { :with => /\A[A-Za-z\d_]+\z/,
:message => "Only letters, digits and _ allowed" }
validates :password_salt, :password_verifier,
- :format => { :with => /\A[\dA-Fa-f]+\z/,
- :message => "Only hex numbers allowed" }
+ :format => { :with => /\A[\dA-Fa-f]+\z/, :message => "Only hex numbers allowed" }
+
+ validates :password, :presence => true,
+ :confirmation => true,
+ :format => { :with => /.{8}.*/, :message => "needs to be at least 8 characters long" }
timestamps!
design do
view :by_login
+ view :by_created_at
end
class << self
- def find_by_param(login)
- return find_by_login(login) || raise(RECORD_NOT_FOUND)
- end
+ alias_method :find_by_param, :find
# valid set of attributes for testing
def valid_attributes_hash
{ :login => "me",
- :password_verifier => "1234ABC",
+ :password_verifier => "1234ABCD",
:password_salt => "4321AB" }
end
end
- def to_param
- self.login
- end
+ alias_method :to_param, :id
def to_json(options={})
- super(options.merge(:only => ['login', 'password_salt']))
+ {
+ :login => login,
+ :ok => valid?
+ }.to_json(options)
end
def initialize_auth(aa)
@@ -63,11 +67,18 @@ class User < CouchRest::Model::Base
login
end
- def self.current
- Thread.current[:user]
+ # Since we are storing admins by login, we cannot allow admins to change their login.
+ def is_admin?
+ APP_CONFIG['admins'].include? self.login
end
- def self.current=(user)
- Thread.current[:user] = user
+
+ protected
+ def password
+ password_verifier
end
+ # used as a condition for validations that are server side only
+ def serverside?
+ true
+ end
end
diff --git a/users/app/views/sessions/_admin_nav.html.haml b/users/app/views/sessions/_admin_nav.html.haml
new file mode 100644
index 0000000..14dfbdc
--- /dev/null
+++ b/users/app/views/sessions/_admin_nav.html.haml
@@ -0,0 +1,6 @@
+%a#admin-menu{"data-toggle" => "dropdown", :role => :button}
+ Admin
+%ul.dropdown-menu{:role => "menu", "aria-labelledby" => "admin-menu"}
+ %li
+ = link_to Ticket.model_name.human(:count => ""), tickets_path, {:tabindex => -1}
+ = link_to User.model_name.human(:count => ""), users_path, {:tabindex => -1}
diff --git a/users/app/views/sessions/_nav.html.haml b/users/app/views/sessions/_nav.html.haml
new file mode 100644
index 0000000..5306d0e
--- /dev/null
+++ b/users/app/views/sessions/_nav.html.haml
@@ -0,0 +1,13 @@
+- if logged_in?
+ - if admin?
+ %li.dropdown
+ = render 'sessions/admin_nav'
+ %li
+ = link_to current_user.login, edit_user_path(current_user)
+ %li
+ = link_to t(:logout), logout_path
+- else
+ %li
+ = link_to t(:login), login_path
+ %li
+ = link_to t(:signup), signup_path
diff --git a/users/app/views/sessions/new.html.haml b/users/app/views/sessions/new.html.haml
index 39ee7bf..a04f584 100644
--- a/users/app/views/sessions/new.html.haml
+++ b/users/app/views/sessions/new.html.haml
@@ -1,7 +1,8 @@
-%h2=t :login
-= simple_form_for :session, :url => sessions_path, :html => { :id => :new_session } do |f|
- %legend=t :login_message
- = f.input :login, :input_html => { :id => :srp_username }
- = f.input :password, :required => true, :input_html => { :id => :srp_password }
- = f.button :submit, :value => t(:login), :class => 'btn-primary'
- = link_to t(:cancel), root_url, :class => :btn
+.span8.offset2
+ %h2=t :login
+ = simple_form_for @session, :validate => true, :html => { :id => :new_session, :class => 'form-horizontal' } do |f|
+ %legend=t :login_message
+ = f.input :login, :input_html => { :id => :srp_username }
+ = f.input :password, :required => true, :input_html => { :id => :srp_password }
+ = f.button :submit, :value => t(:login), :class => 'btn-primary'
+ = link_to t(:cancel), root_url, :class => :btn
diff --git a/users/app/views/sessions/new.json.erb b/users/app/views/sessions/new.json.erb
new file mode 100644
index 0000000..36154b8
--- /dev/null
+++ b/users/app/views/sessions/new.json.erb
@@ -0,0 +1,3 @@
+{
+"errors": <%= raw @errors.to_json %>
+}
diff --git a/users/app/views/users/_cancel_account.html.haml b/users/app/views/users/_cancel_account.html.haml
new file mode 100644
index 0000000..41580b0
--- /dev/null
+++ b/users/app/views/users/_cancel_account.html.haml
@@ -0,0 +1,6 @@
+%legend
+ =t :cancel_account
+ %small You will not be able to login anymore.
+= link_to user_path(@user), :method => :delete, :class => "btn btn-danger" do
+ %i.icon-remove.icon-white
+ Remove my Account
diff --git a/users/app/views/users/_form.html.haml b/users/app/views/users/_form.html.haml
new file mode 100644
index 0000000..39e26a6
--- /dev/null
+++ b/users/app/views/users/_form.html.haml
@@ -0,0 +1,15 @@
+- only = local_assigns[:only]
+- html = {:class => 'form-horizontal user form ' + (@user.new_record? ? 'new' : 'edit')}
+= simple_form_for @user, :validate => true, :format => :json, :html => html do |f|
+ %legend
+ = t(only || :signup_message)
+ - if !only || only == :change_login
+ = f.input :login, :input_html => { :id => :srp_username }
+ - if !only || only == :change_password
+ = f.input :password, :required => true, :validate => true, :input_html => { :id => :srp_password }
+ = f.input :password_confirmation, :required => true, :input_html => { :id => :srp_password_confirmation }
+ .pull-right
+ = f.button :submit, :class => 'btn-primary'
+ - unless only
+ = link_to t(:cancel), root_url, :class => :btn
+ .clearfix
diff --git a/users/app/views/users/_user.html.haml b/users/app/views/users/_user.html.haml
new file mode 100644
index 0000000..7db0041
--- /dev/null
+++ b/users/app/views/users/_user.html.haml
@@ -0,0 +1,10 @@
+%tr
+ %td= user.login
+ %td= time_ago_in_words(user.created_at) + " ago"
+ %td
+ = link_to edit_user_path(user), :class => "btn btn-mini btn-primary" do
+ %i.icon-edit.icon-white
+ Edit
+ = link_to user_path(user), :method => :delete, :class => "btn btn-danger btn-mini" do
+ %i.icon-remove.icon-white
+ Remove
diff --git a/users/app/views/users/edit.html.haml b/users/app/views/users/edit.html.haml
new file mode 100644
index 0000000..25da71a
--- /dev/null
+++ b/users/app/views/users/edit.html.haml
@@ -0,0 +1,5 @@
+.span8.offset2
+ %h2=t :settings
+ = render :partial => 'form', :locals => {:only => :change_login}
+ = render :partial => 'form', :locals => {:only => :change_password}
+ = render 'cancel_account' if @user == current_user
diff --git a/users/app/views/users/index.html.haml b/users/app/views/users/index.html.haml
new file mode 100644
index 0000000..9e6a179
--- /dev/null
+++ b/users/app/views/users/index.html.haml
@@ -0,0 +1,17 @@
+.page-header
+ %h1= User.model_name.human(:count =>User.count)
+.row
+ .span8
+ %h2= params[:query] ? "Users starting with '#{params[:query]}'" : "Last users who signed up"
+ %table.table.table-hover
+ %tr
+ %th Login
+ %th Created
+ %th Action
+ = render @users.all
+ .span4
+ %h4 Find user
+ = form_tag users_path, :method => :get, :class => "form-search" do
+ .input-append
+ = text_field_tag :query, "", :class => "user typeahead span2 search-query", :autocomplete => :off
+ %button.btn{:type => :submit} Search
diff --git a/users/app/views/users/new.html.haml b/users/app/views/users/new.html.haml
index f6ece3a..c1c4208 100644
--- a/users/app/views/users/new.html.haml
+++ b/users/app/views/users/new.html.haml
@@ -1,10 +1,3 @@
-%h2=t :signup
-= simple_form_for @user do |f|
- %legend=t :signup_message
- = f.input :login, :input_html => { :id => :srp_username }
- = f.input :password, :required => true, :input_html => { :id => :srp_password }
- = f.input :password_confirmation, :required => true, :input_html => { :id => :srp_password_confirmation }
- = f.input :password_verifier, :as => :hidden, :input_html => { :id => :srp_password_verifier }
- = f.input :password_salt, :as => :hidden, :input_html => { :id => :srp_salt }
- = f.button :submit, :value => t(:signup), :class => 'btn-primary'
- = link_to t(:cancel), root_url, :class => :btn
+.span8.offset2
+ %h2=t :signup
+ = render 'form'
diff --git a/users/config/initializers/add_controller_methods.rb b/users/config/initializers/add_controller_methods.rb
new file mode 100644
index 0000000..2579176
--- /dev/null
+++ b/users/config/initializers/add_controller_methods.rb
@@ -0,0 +1,3 @@
+ActiveSupport.on_load(:application_controller) do
+ include ControllerExtension::Authentication
+end
diff --git a/users/config/initializers/warden.rb b/users/config/initializers/warden.rb
new file mode 100644
index 0000000..45feb6c
--- /dev/null
+++ b/users/config/initializers/warden.rb
@@ -0,0 +1,7 @@
+Rails.configuration.middleware.use RailsWarden::Manager do |config|
+ config.default_strategies :secure_remote_password
+ config.failure_app = SessionsController
+end
+
+RailsWarden.unauthenticated_action = :new
+
diff --git a/users/config/locales/en.yml b/users/config/locales/en.yml
new file mode 100644
index 0000000..1260494
--- /dev/null
+++ b/users/config/locales/en.yml
@@ -0,0 +1,14 @@
+en:
+ signup: "Sign up"
+ signup_message: "Please create an account."
+ cancel: "Cancel"
+ login: "Login"
+ login_message: "Please login with your account."
+ wrong_password: "wrong password"
+ user_not_found: "could not be found"
+
+ activemodel:
+ models:
+ user:
+ one: User
+ other: "%{count} Users"
diff --git a/users/config/routes.rb b/users/config/routes.rb
index cfc0407..1d144b4 100644
--- a/users/config/routes.rb
+++ b/users/config/routes.rb
@@ -1,10 +1,10 @@
Rails.application.routes.draw do
- get "log_in" => "sessions#new", :as => "log_in"
- get "log_out" => "sessions#destroy", :as => "log_out"
+ get "login" => "sessions#new", :as => "login"
+ get "logout" => "sessions#destroy", :as => "logout"
resources :sessions, :only => [:new, :create, :update, :destroy]
- get "sign_up" => "users#new", :as => "sign_up"
- resources :users, :only => [:new, :create]
+ get "signup" => "users#new", :as => "signup"
+ resources :users
end
diff --git a/users/leap_web_users.gemspec b/users/leap_web_users.gemspec
index f64a76a..0682a99 100644
--- a/users/leap_web_users.gemspec
+++ b/users/leap_web_users.gemspec
@@ -17,5 +17,6 @@ Gem::Specification.new do |s|
s.add_dependency "leap_web_core", LeapWeb::VERSION
- s.add_dependency "ruby-srp", "~> 0.1.1"
+ s.add_dependency "ruby-srp", "~> 0.1.4"
+ s.add_dependency "rails_warden"
end
diff --git a/users/lib/leap_web_users/engine.rb b/users/lib/leap_web_users/engine.rb
index 9b7545e..7033576 100644
--- a/users/lib/leap_web_users/engine.rb
+++ b/users/lib/leap_web_users/engine.rb
@@ -1,8 +1,12 @@
# thou shall require all your dependencies in an engine.
require "leap_web_core"
require "leap_web_core/ui_dependencies"
+require "rails_warden"
require "ruby-srp"
+require "warden/session_serializer"
+require "warden/strategies/secure_remote_password"
+
module LeapWebUsers
class Engine < ::Rails::Engine
diff --git a/users/lib/warden/session_serializer.rb b/users/lib/warden/session_serializer.rb
new file mode 100644
index 0000000..81d7076
--- /dev/null
+++ b/users/lib/warden/session_serializer.rb
@@ -0,0 +1,13 @@
+module Warden
+ # Setup Session Serialization
+ class SessionSerializer
+ def serialize(record)
+ [record.class.name, record.id]
+ end
+
+ def deserialize(keys)
+ klass, id = keys
+ klass.constantize.find(id)
+ end
+ end
+end
diff --git a/users/lib/warden/strategies/secure_remote_password.rb b/users/lib/warden/strategies/secure_remote_password.rb
new file mode 100644
index 0000000..594e27e
--- /dev/null
+++ b/users/lib/warden/strategies/secure_remote_password.rb
@@ -0,0 +1,58 @@
+module Warden
+ module Strategies
+ class SecureRemotePassword < Warden::Strategies::Base
+
+ def valid?
+ handshake? || authentication?
+ end
+
+ def authenticate!
+ if authentication?
+ validate!
+ else # handshake
+ initialize!
+ end
+ end
+
+ protected
+
+ def handshake?
+ params['A'] && params['login']
+ end
+
+ def authentication?
+ params['client_auth'] && session[:handshake]
+ end
+
+ def validate!
+ user = session[:handshake].authenticate(params['client_auth'].hex)
+ user ? success!(user) : fail!(:password => "wrong_password")
+ end
+
+ def initialize!
+ if user = User.find_by_login(id)
+ session[:handshake] = user.initialize_auth(params['A'].hex)
+ custom! json_response(session[:handshake])
+ else
+ fail! :login => "user_not_found"
+ end
+ end
+
+ def json_response(object)
+ [ 200,
+ {"Content-Type" => "application/json; charset=utf-8"},
+ [object.to_json]
+ ]
+ end
+
+ def id
+ params["id"] || params["login"]
+ end
+ end
+ end
+ Warden::Strategies.add :secure_remote_password,
+ Warden::Strategies::SecureRemotePassword
+
+end
+
+
diff --git a/users/test/functional/application_controller_test.rb b/users/test/functional/application_controller_test.rb
new file mode 100644
index 0000000..857bae5
--- /dev/null
+++ b/users/test/functional/application_controller_test.rb
@@ -0,0 +1,28 @@
+require 'test_helper'
+
+class ApplicationControllerTest < ActionController::TestCase
+
+ def setup
+ # so we can test the effect on the response
+ @controller.response = @response
+ end
+
+ def test_authorize_redirect
+ @controller.send(:authorize)
+ assert_access_denied
+ end
+
+ def test_authorized
+ login
+ @controller.send(:authorize)
+ assert_access_denied(false)
+ end
+
+ def test_authorize_admin
+ login
+ @current_user.expects(:is_admin?).returns(false)
+ @controller.send(:authorize_admin)
+ assert_access_denied
+ end
+
+end
diff --git a/users/test/functional/helper_methods_test.rb b/users/test/functional/helper_methods_test.rb
new file mode 100644
index 0000000..2b2375c
--- /dev/null
+++ b/users/test/functional/helper_methods_test.rb
@@ -0,0 +1,39 @@
+#
+# Testing and documenting the helper methods available from
+# ApplicationController
+#
+
+require 'test_helper'
+
+class HelperMethodsTest < ActionController::TestCase
+ tests ApplicationController
+
+ # we test them right in here...
+ include ApplicationController._helpers
+
+ # they all reference the controller.
+ def controller
+ @controller
+ end
+
+ def test_current_user
+ login
+ assert_equal @current_user, current_user
+ end
+
+ def test_logged_in
+ login
+ assert logged_in?
+ end
+
+ def test_logged_out
+ assert !logged_in?
+ end
+
+ def test_admin
+ login
+ @current_user.expects(:is_admin?).returns(bool = stub)
+ assert_equal bool, admin?
+ end
+
+end
diff --git a/users/test/functional/sessions_controller_test.rb b/users/test/functional/sessions_controller_test.rb
index b6e56a7..9df4455 100644
--- a/users/test/functional/sessions_controller_test.rb
+++ b/users/test/functional/sessions_controller_test.rb
@@ -1,79 +1,72 @@
require 'test_helper'
+# This is a simple controller unit test.
+# We're stubbing out both warden and srp.
+# There's an integration test testing the full rack stack and srp
class SessionsControllerTest < ActionController::TestCase
- def setup
+ setup do
+ @user = stub :login => "me", :id => 123
@client_hex = 'a123'
- @client_rnd = @client_hex.hex
- @server_hex = 'b123'
- @server_rnd = @server_hex.hex
- @server_rnd_exp = 'e123'.hex
- @salt = 'stub user salt'
- @server_handshake = stub :aa => @client_rnd, :bb => @server_rnd, :b => @server_rnd_exp
- @server_auth = 'adfe'
end
test "should get login screen" do
+ request.env['warden'].expects(:winning_strategy)
get :new
assert_response :success
+ assert_equal "text/html", response.content_type
+ assert_template "sessions/new"
end
- test "should perform handshake" do
- user = stub :login => "me", :id => 123
- user.expects(:initialize_auth).
- with(@client_rnd).
- returns(@server_handshake)
- @server_handshake.expects(:to_json).
- returns({'B' => @server_hex, 'salt' => @salt}.to_json)
- User.expects(:find_by_param).with(user.login).returns(user)
- post :create, :login => user.login, 'A' => @client_hex
- assert_equal @server_handshake, session[:handshake]
+ test "renders json" do
+ request.env['warden'].expects(:winning_strategy)
+ get :new, :format => :json
assert_response :success
- assert_json_response :B => @server_hex, :salt => @salt
+ assert_json_error nil
end
- test "should report user not found" do
- unknown = "login_that_does_not_exist"
- User.expects(:find_by_param).with(unknown).raises(RECORD_NOT_FOUND)
- post :create, :login => unknown
- assert_response :success
- assert_json_response :errors => {"login" => ["unknown user"]}
+ test "renders warden errors" do
+ strategy = stub :message => {:field => :translate_me}
+ request.env['warden'].stubs(:winning_strategy).returns(strategy)
+ I18n.expects(:t).with(:translate_me).at_least_once.returns("translation stub")
+ get :new, :format => :json
+ assert_response 422
+ assert_json_error :field => "translation stub"
end
- test "should authorize" do
- session[:handshake] = @server_handshake
- user = stub :login => "me", :id => 123
- @server_handshake.expects(:authenticate!).
- with(@client_rnd).
- returns(@server_auth)
- @server_handshake.expects(:to_json).
- returns({:M2 => @server_auth}.to_json)
- User.expects(:find_by_param).with(user.login).returns(user)
- post :update, :id => user.login, :client_auth => @client_hex
- assert_nil session[:handshake]
- assert_json_response :M2 => @server_auth
- assert_equal user.id, session[:user_id]
+ # Warden takes care of parsing the params and
+ # rendering the response. So not much to test here.
+ test "should perform handshake" do
+ request.env['warden'].expects(:authenticate!)
+ # make sure we don't get a template missing error:
+ @controller.stubs(:render)
+ post :create, :login => @user.login, 'A' => @client_hex
end
- test "should report wrong password" do
- session[:handshake] = @server_handshake
- user = stub :login => "me", :id => 123
- @server_handshake.expects(:authenticate!).
- with(@client_rnd).
- raises(WRONG_PASSWORD)
- User.expects(:find_by_param).with(user.login).returns(user)
- post :update, :id => user.login, :client_auth => @client_hex
+ test "should authorize" do
+ request.env['warden'].expects(:authenticate!)
+ handshake = stub(:to_json => "JSON")
+ session[:handshake] = handshake
+ post :update, :id => @user.login, :client_auth => @client_hex
assert_nil session[:handshake]
- assert_nil session[:user_id]
- assert_json_response :errors => {"password" => ["wrong password"]}
+ assert_response :success
+ assert_equal handshake.to_json, @response.body
end
- test "logout should reset sessions user_id" do
- session[:user_id] = "set"
+ test "logout should reset warden user" do
+ expect_warden_logout
delete :destroy
- assert_nil session[:user_id]
assert_response :redirect
assert_redirected_to root_url
end
+ def expect_warden_logout
+ raw = mock('raw session') do
+ expects(:inspect)
+ end
+ request.env['warden'].expects(:raw_session).returns(raw)
+ request.env['warden'].expects(:logout)
+ end
+
+
end
diff --git a/users/test/functional/users_controller_test.rb b/users/test/functional/users_controller_test.rb
index 1cb28a6..939d105 100644
--- a/users/test/functional/users_controller_test.rb
+++ b/users/test/functional/users_controller_test.rb
@@ -1,33 +1,132 @@
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
+ include StubRecordHelper
+
test "should get new" do
get :new
+
assert_equal User, assigns(:user).class
assert_response :success
end
test "should create new user" do
- params = User.valid_attributes_hash
- user = stub params.merge(:id => 123)
- params.stringify_keys!
- User.expects(:create!).with(params).returns(user)
- post :create, :user => params
+ user = stub_record User
+ User.expects(:create).with(user.params).returns(user)
+
+ post :create, :user => user.params, :format => :json
+
assert_nil session[:user_id]
- assert_response :redirect
- assert_redirected_to root_url
+ assert_json_response user
+ assert_response :success
end
test "should redirect to signup form on failed attempt" do
params = User.valid_attributes_hash.slice(:login)
user = User.new(params)
params.stringify_keys!
- User.expects(:create!).with(params).raises(VALIDATION_FAILED.new(user))
- post :create, :user => params
- assert_nil session[:user_id]
+ assert !user.valid?
+ User.expects(:create).with(params).returns(user)
+
+ post :create, :user => params, :format => :json
+
+ assert_json_error user.errors.messages
+ assert_response 422
+ end
+
+ test "should get edit view" do
+ user = find_record User
+
+ login user
+ get :edit, :id => user.id
+
assert_equal user, assigns[:user]
+ end
+
+ test "should process updated params" do
+ user = find_record User
+ user.expects(:update_attributes).with(user.params).returns(true)
+
+ login user
+ put :update, :user => user.params, :id => user.id, :format => :json
+
+ assert_equal user, assigns[:user]
+ assert_response 204
+ assert_equal " ", @response.body
+ end
+
+ test "admin can update user" do
+ user = find_record User
+ user.expects(:update_attributes).with(user.params).returns(true)
+
+ login :is_admin? => true
+ put :update, :user => user.params, :id => user.id, :format => :json
+
+ assert_equal user, assigns[:user]
+ assert_response 204
+ assert_equal " ", @response.body
+ end
+
+ test "admin can destroy user" do
+ user = find_record User
+ user.expects(:destroy)
+
+ login :is_admin? => true
+ delete :destroy, :id => user.id
+
+ assert_response :redirect
+ assert_redirected_to users_path
+ end
+
+ test "user can cancel account" do
+ user = find_record User
+ user.expects(:destroy)
+
+ login user
+ delete :destroy, :id => @current_user.id
+
assert_response :redirect
- assert_redirected_to new_user_path
+ assert_redirected_to login_path
+ end
+
+ test "non-admin can't destroy user" do
+ user = stub_record User
+
+ login
+ delete :destroy, :id => user.id
+
+ assert_access_denied
+ end
+
+ test "admin can list users" do
+ login :is_admin? => true
+ get :index
+
+ assert_response :success
+ assert assigns(:users)
+ end
+
+ test "non-admin can't list users" do
+ login
+ get :index
+
+ assert_access_denied
+ end
+
+ test "admin can autocomplete users" do
+ login :is_admin? => true
+ get :index, :format => :json
+
+ assert_response :success
+ assert assigns(:users)
+ end
+
+ test "admin can search users" do
+ login :is_admin? => true
+ get :index, :query => "a"
+
+ assert_response :success
+ assert assigns(:users)
end
end
diff --git a/users/test/integration/api/account_flow_test.rb b/users/test/integration/api/account_flow_test.rb
index 66de1e5..add12fe 100644
--- a/users/test/integration/api/account_flow_test.rb
+++ b/users/test/integration/api/account_flow_test.rb
@@ -1,23 +1,19 @@
require 'test_helper'
-class AccountFlowTest < ActionDispatch::IntegrationTest
+CONFIG_RU = (Rails.root + 'config.ru').to_s
+OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first
- # this test wraps the api and implements the interface the ruby-srp client.
- def handshake(login, aa)
- post "sessions", :login => login, 'A' => aa.to_s(16)
- assert_response :success
- response = JSON.parse(@response.body)
- if response['errors']
- raise RECORD_NOT_FOUND.new(response['errors'])
- else
- return response['B'].hex
- end
+class AccountFlowTest < ActiveSupport::TestCase
+ include Rack::Test::Methods
+ include Warden::Test::Helpers
+ include LeapWebCore::AssertResponses
+
+ def app
+ OUTER_APP
end
- def validate(m)
- put "sessions/" + @login, :client_auth => m.to_s(16)
- assert_response :success
- return JSON.parse(@response.body)
+ def teardown
+ Warden.test_reset!
end
def setup
@@ -38,13 +34,30 @@ class AccountFlowTest < ActionDispatch::IntegrationTest
@user.destroy if @user # make sure we can run this test again
end
+ # this test wraps the api and implements the interface the ruby-srp client.
+ def handshake(login, aa)
+ post "/sessions.json", :login => login, 'A' => aa.to_s(16), :format => :json
+ response = JSON.parse(last_response.body)
+ if response['errors']
+ raise RECORD_NOT_FOUND.new(response['errors'])
+ else
+ return response['B'].hex
+ end
+ end
+
+ def validate(m)
+ put "/sessions/" + @login + '.json', :client_auth => m.to_s(16), :format => :json
+ return JSON.parse(last_response.body)
+ end
+
test "signup response" do
- assert_json_response @user_params.slice(:login, :password_salt)
- assert_response :success
+ assert_json_response :login => @login, :ok => true
+ assert last_response.successful?
end
test "signup and login with srp via api" do
server_auth = @srp.authenticate(self)
+ assert last_response.successful?
assert_nil server_auth["errors"]
assert server_auth["M2"]
end
@@ -52,7 +65,8 @@ class AccountFlowTest < ActionDispatch::IntegrationTest
test "signup and wrong password login attempt" do
srp = SRP::Client.new(@login, "wrong password")
server_auth = srp.authenticate(self)
- assert_equal ["wrong password"], server_auth["errors"]['password']
+ assert_json_error :password => "wrong password"
+ assert !last_response.successful?
assert_nil server_auth["M2"]
end
@@ -62,6 +76,8 @@ class AccountFlowTest < ActionDispatch::IntegrationTest
assert_raises RECORD_NOT_FOUND do
server_auth = srp.authenticate(self)
end
+ assert_json_error :login => "could not be found"
+ assert !last_response.successful?
assert_nil server_auth
end
diff --git a/users/test/integration/api/python/flow_with_srp.py b/users/test/integration/api/python/flow_with_srp.py
index 0a11aec..b599252 100755
--- a/users/test/integration/api/python/flow_with_srp.py
+++ b/users/test/integration/api/python/flow_with_srp.py
@@ -16,7 +16,7 @@ def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for x in range(size))
# using globals for a start
-server = 'http://springbok/1/'
+server = 'http://springbok.leap.se/1/'
login = id_generator()
password = id_generator() + id_generator()
diff --git a/users/test/support/auth_test_helper.rb b/users/test/support/auth_test_helper.rb
new file mode 100644
index 0000000..f3506ae
--- /dev/null
+++ b/users/test/support/auth_test_helper.rb
@@ -0,0 +1,35 @@
+module AuthTestHelper
+ include StubRecordHelper
+ extend ActiveSupport::Concern
+
+ # Controller will fetch current user from warden.
+ # Make it pick up our current_user
+ included do
+ setup do
+ request.env['warden'] ||= stub :user => nil
+ end
+ end
+
+ def login(user_or_method_hash = {})
+ @current_user = stub_record(User, user_or_method_hash)
+ unless @current_user.respond_to? :is_admin?
+ @current_user.stubs(:is_admin?).returns(false)
+ end
+ request.env['warden'] = stub :user => @current_user
+ return @current_user
+ end
+
+ def assert_access_denied(denied = true)
+ if denied
+ assert_equal({:alert => "Not authorized"}, flash.to_hash)
+ assert_redirected_to login_path
+ else
+ assert flash[:alert].blank?
+ end
+ end
+
+end
+
+class ActionController::TestCase
+ include AuthTestHelper
+end
diff --git a/users/test/support/stub_record_helper.rb b/users/test/support/stub_record_helper.rb
new file mode 100644
index 0000000..2e1a533
--- /dev/null
+++ b/users/test/support/stub_record_helper.rb
@@ -0,0 +1,41 @@
+module StubRecordHelper
+
+ # Will expect find_by_param or find_by_id to be called on klass and
+ # return the record given.
+ # If no record is given but a hash or nil will create a stub based on
+ # that instead and returns the stub.
+ def find_record(klass, record_or_method_hash = {})
+ record = stub_record(klass, record_or_method_hash)
+ finder = klass.respond_to?(:find_by_param) ? :find_by_param : :find_by_id
+ klass.expects(finder).with(record.to_param).returns(record)
+ return record
+ end
+
+ # Create a stub that has the usual functions of a database record.
+ # It won't fail on rendering a form for example.
+ #
+ # If the second parameter is a record we return the record itself.
+ # This way you can build functions that either take a record or a
+ # method hash to stub from. See find_record for an example.
+ def stub_record(klass, record_or_method_hash = {}, persisted = true)
+ if record_or_method_hash && !record_or_method_hash.is_a?(Hash)
+ return record_or_method_hash
+ end
+ stub record_params_for(klass, record_or_method_hash, persisted)
+ end
+
+ def record_params_for(klass, params = {}, persisted = true)
+ if klass.respond_to?(:valid_attributes_hash)
+ params.reverse_merge!(klass.valid_attributes_hash)
+ end
+ params[:params] = params.stringify_keys
+ params.reverse_merge! :id => "A123",
+ :to_param => "A123",
+ :class => klass,
+ :to_key => ['123'],
+ :to_json => %Q({"stub":"#{klass.name}"}),
+ :new_record? => !persisted,
+ :persisted? => persisted
+ end
+
+end
diff --git a/users/test/test_helper.rb b/users/test/test_helper.rb
index 08d4d41..52dff53 100644
--- a/users/test/test_helper.rb
+++ b/users/test/test_helper.rb
@@ -1,10 +1,9 @@
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../../test/dummy/config/environment', __FILE__)
require 'rails/test_help'
-require 'mocha'
+require 'mocha/setup'
Rails.backtrace_cleaner.remove_silencers!
# Load support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
-
diff --git a/users/test/unit/user_test.rb b/users/test/unit/user_test.rb
index 822ef33..cce11c2 100644
--- a/users/test/unit/user_test.rb
+++ b/users/test/unit/user_test.rb
@@ -5,6 +5,7 @@ class UserTest < ActiveSupport::TestCase
include SRP::Util
setup do
@attribs = User.valid_attributes_hash
+ User.find_by_login(@attribs[:login]).try(:destroy)
@user = User.new(@attribs)
end
@@ -19,18 +20,18 @@ class UserTest < ActiveSupport::TestCase
end
test "test require alphanumerical for login" do
- @user.login = "qwär"
+ @user.login = "qw#r"
assert !@user.valid?
end
- test "find_by_param gets User by login" do
+ test "find_by_param gets User by id" do
@user.save
- assert_equal @user, User.find_by_param(@user.login)
+ assert_equal @user, User.find_by_param(@user.id)
@user.destroy
end
- test "to_param gives user login" do
- assert_equal @user.login, @user.to_param
+ test "to_param gives user id" do
+ assert_equal @user.id, @user.to_param
end
test "verifier returns number for the hex in password_verifier" do
diff --git a/users/test/unit/warden_strategy_secure_remote_password_test.rb b/users/test/unit/warden_strategy_secure_remote_password_test.rb
new file mode 100644
index 0000000..319809a
--- /dev/null
+++ b/users/test/unit/warden_strategy_secure_remote_password_test.rb
@@ -0,0 +1,63 @@
+class WardenStrategySecureRemotePasswordTest < ActiveSupport::TestCase
+
+# TODO : turn this into sth. real
+=begin
+ setup do
+ @user = stub :login => "me", :id => 123
+ @client_hex = 'a123'
+ @client_rnd = @client_hex.hex
+ @server_hex = 'b123'
+ @server_rnd = @server_hex.hex
+ @server_rnd_exp = 'e123'.hex
+ @salt = 'stub user salt'
+ @server_handshake = stub :aa => @client_rnd, :bb => @server_rnd, :b => @server_rnd_exp
+ @server_auth = 'adfe'
+ end
+
+
+ test "should perform handshake" do
+ @user.expects(:initialize_auth).
+ with(@client_rnd).
+ returns(@server_handshake)
+ @server_handshake.expects(:to_json).
+ returns({'B' => @server_hex, 'salt' => @salt}.to_json)
+ User.expects(:find_by_param).with(@user.login).returns(@user)
+ assert_equal @server_handshake, session[:handshake]
+ assert_response :success
+ assert_json_response :B => @server_hex, :salt => @salt
+ end
+
+ test "should report user not found" do
+ unknown = "login_that_does_not_exist"
+ User.expects(:find_by_param).with(unknown).raises(RECORD_NOT_FOUND)
+ post :create, :login => unknown
+ assert_response :success
+ assert_json_error "login" => ["unknown user"]
+ end
+
+ test "should authorize" do
+ session[:handshake] = @server_handshake
+ @server_handshake.expects(:authenticate!).
+ with(@client_rnd).
+ returns(@user)
+ @server_handshake.expects(:to_json).
+ returns({:M2 => @server_auth}.to_json)
+ post :update, :id => @user.login, :client_auth => @client_hex
+ assert_nil session[:handshake]
+ assert_json_response :M2 => @server_auth
+ assert_equal @user.id, session[:user_id]
+ end
+
+ test "should report wrong password" do
+ session[:handshake] = @server_handshake
+ @server_handshake.expects(:authenticate!).
+ with(@client_rnd).
+ raises(WRONG_PASSWORD)
+ post :update, :id => @user.login, :client_auth => @client_hex
+ assert_nil session[:handshake]
+ assert_nil session[:user_id]
+ assert_json_error "password" => ["wrong password"]
+ end
+
+=end
+end