├── test
├── dummy
│ ├── log
│ │ └── .gitkeep
│ ├── app
│ │ ├── mailers
│ │ │ └── .gitkeep
│ │ ├── models
│ │ │ └── .gitkeep
│ │ ├── helpers
│ │ │ └── application_helper.rb
│ │ ├── controllers
│ │ │ └── application_controller.rb
│ │ ├── views
│ │ │ └── layouts
│ │ │ │ └── application.html.erb
│ │ └── assets
│ │ │ └── stylesheets
│ │ │ └── application.css
│ ├── lib
│ │ └── assets
│ │ │ └── .gitkeep
│ ├── public
│ │ ├── favicon.ico
│ │ ├── 500.html
│ │ ├── 422.html
│ │ └── 404.html
│ ├── config.ru
│ ├── config
│ │ ├── environment.rb
│ │ ├── routes.rb
│ │ ├── locales
│ │ │ └── en.yml
│ │ ├── initializers
│ │ │ ├── mime_types.rb
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── session_store.rb
│ │ │ ├── secret_token.rb
│ │ │ ├── wrap_parameters.rb
│ │ │ └── inflections.rb
│ │ ├── database.yml
│ │ ├── boot.rb
│ │ ├── environments
│ │ │ ├── development.rb
│ │ │ ├── test.rb
│ │ │ └── production.rb
│ │ ├── application.rb
│ │ └── masq.yml
│ ├── Rakefile
│ ├── script
│ │ └── rails
│ └── db
│ │ └── schema.rb
├── fixtures
│ ├── release_policies.yml
│ ├── sites.yml
│ ├── personas.yml
│ └── accounts.yml
├── support
│ └── active_support
│ │ └── test_case.rb
├── unit
│ └── masq
│ │ ├── release_policy_test.rb
│ │ ├── persona_test.rb
│ │ ├── open_id_request_test.rb
│ │ ├── site_test.rb
│ │ ├── account_mailer_test.rb
│ │ └── signup_test.rb
├── functional
│ └── masq
│ │ ├── yubikey_associations_controller_test.rb
│ │ ├── sites_controller_test.rb
│ │ ├── personas_controller_test.rb
│ │ ├── info_controller_test.rb
│ │ ├── passwords_controller_test.rb
│ │ ├── server_controller_test.rb
│ │ ├── sessions_controller_test.rb
│ │ └── accounts_controller_test.rb
└── test_helper.rb
├── app
├── assets
│ ├── images
│ │ └── masq
│ │ │ ├── .gitkeep
│ │ │ ├── favicon.ico
│ │ │ ├── openid_symbol.png
│ │ │ ├── seatbelt_icon.png
│ │ │ ├── seatbelt_icon_gray.png
│ │ │ └── seatbelt_icon_high.png
│ └── stylesheets
│ │ └── masq
│ │ └── application.css.erb
├── views
│ ├── masq
│ │ ├── consumer
│ │ │ ├── start.html.erb
│ │ │ └── index.html.erb
│ │ ├── account_mailer
│ │ │ ├── forgot_password.text.erb
│ │ │ ├── forgot_password.html.erb
│ │ │ ├── signup_notification.text.erb
│ │ │ └── signup_notification.html.erb
│ │ ├── accounts
│ │ │ ├── show.html.erb
│ │ │ ├── new.html.erb
│ │ │ ├── _hcard.html.erb
│ │ │ ├── show.xrds.builder
│ │ │ └── edit.html.erb
│ │ ├── server
│ │ │ ├── seatbelt_login_state.xml.builder
│ │ │ ├── index.xrds.builder
│ │ │ ├── seatbelt_config.xml.builder
│ │ │ └── decide.html.erb
│ │ ├── info
│ │ │ ├── index.html.erb
│ │ │ ├── help.html.erb
│ │ │ └── safe_login.html.erb
│ │ ├── personas
│ │ │ ├── new.html.erb
│ │ │ ├── edit.html.erb
│ │ │ ├── index.html.erb
│ │ │ └── _form.html.erb
│ │ ├── shared
│ │ │ └── _error_messages.html.erb
│ │ ├── passwords
│ │ │ ├── new.html.erb
│ │ │ └── edit.html.erb
│ │ ├── sites
│ │ │ ├── index.html.erb
│ │ │ └── edit.html.erb
│ │ └── sessions
│ │ │ └── new.html.erb
│ └── layouts
│ │ └── masq
│ │ ├── consumer.html.erb
│ │ └── base.html.erb
├── models
│ └── masq
│ │ ├── release_policy.rb
│ │ ├── open_id_request.rb
│ │ ├── signup.rb
│ │ ├── persona.rb
│ │ ├── site.rb
│ │ └── account.rb
├── helpers
│ └── masq
│ │ ├── server_helper.rb
│ │ ├── personas_helper.rb
│ │ └── application_helper.rb
├── mailers
│ └── masq
│ │ └── account_mailer.rb
└── controllers
│ └── masq
│ ├── info_controller.rb
│ ├── yubikey_associations_controller.rb
│ ├── sites_controller.rb
│ ├── passwords_controller.rb
│ ├── sessions_controller.rb
│ ├── personas_controller.rb
│ ├── base_controller.rb
│ ├── accounts_controller.rb
│ └── consumer_controller.rb
├── lib
├── masq
│ ├── version.rb
│ ├── engine.rb
│ ├── active_record_openid_store
│ │ ├── nonce.rb
│ │ ├── association.rb
│ │ └── openid_ar_store.rb
│ ├── openid_server_system.rb
│ └── authenticated_system.rb
├── masq.rb
└── tasks
│ └── masq_tasks.rake
├── config
├── initializers
│ ├── mime_types.rb
│ ├── configuration.rb
│ └── requires.rb
├── routes.rb
├── masq.example.yml
└── locales
│ └── rails.de.yml
├── .travis.yml
├── .gitignore
├── script
└── rails
├── Gemfile
├── CHANGELOG.md
├── Guardfile
├── masq.gemspec
├── MIT-LICENSE
├── Rakefile
├── Gemfile.lock
├── README.md
└── db
└── migrate
└── 20120312120000_masq_schema.rb
/test/dummy/log/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/masq/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy/app/mailers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy/lib/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/masq/version.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | VERSION = "0.3.2"
3 | end
4 |
--------------------------------------------------------------------------------
/test/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | Mime::Type.register "application/xrds+xml", :xrds
2 |
--------------------------------------------------------------------------------
/app/assets/images/masq/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dennisreimann/masq/HEAD/app/assets/images/masq/favicon.ico
--------------------------------------------------------------------------------
/lib/masq/engine.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class Engine < ::Rails::Engine
3 | isolate_namespace Masq
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/assets/images/masq/openid_symbol.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dennisreimann/masq/HEAD/app/assets/images/masq/openid_symbol.png
--------------------------------------------------------------------------------
/app/assets/images/masq/seatbelt_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dennisreimann/masq/HEAD/app/assets/images/masq/seatbelt_icon.png
--------------------------------------------------------------------------------
/app/assets/images/masq/seatbelt_icon_gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dennisreimann/masq/HEAD/app/assets/images/masq/seatbelt_icon_gray.png
--------------------------------------------------------------------------------
/app/assets/images/masq/seatbelt_icon_high.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dennisreimann/masq/HEAD/app/assets/images/masq/seatbelt_icon_high.png
--------------------------------------------------------------------------------
/test/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery
3 | end
4 |
--------------------------------------------------------------------------------
/app/views/masq/consumer/start.html.erb:
--------------------------------------------------------------------------------
1 | <%= @form_text.html_safe %>
2 | <%= javascript_tag("document.getElementById('checkid_form').submit();") %>
3 |
--------------------------------------------------------------------------------
/config/initializers/configuration.rb:
--------------------------------------------------------------------------------
1 | Masq::Engine.configure do
2 | config.masq = YAML.load(File.read("#{Rails.root}/config/masq.yml"))[Rails.env]
3 | end
4 |
--------------------------------------------------------------------------------
/app/views/masq/account_mailer/forgot_password.text.erb:
--------------------------------------------------------------------------------
1 | <%=t :forgot_password_create_new_one %>:
2 |
3 | <%= reset_password_url(:id => @account.password_reset_code) %>
4 |
--------------------------------------------------------------------------------
/app/views/masq/account_mailer/forgot_password.html.erb:
--------------------------------------------------------------------------------
1 |
<%=t :forgot_password_create_new_one %>:
2 |
3 | <%= reset_password_url(:id => @account.password_reset_code) %>
4 |
--------------------------------------------------------------------------------
/test/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Dummy::Application
5 |
--------------------------------------------------------------------------------
/test/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the rails application
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the rails application
5 | Dummy::Application.initialize!
6 |
--------------------------------------------------------------------------------
/test/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | mount Masq::Engine => "/masq"
3 | get "/*account" => "masq/accounts#show", :as => :identity
4 | root :to => "masq/info#index"
5 | end
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | env:
3 | - 'DB_ADAPTER=mysql2 DB_USERNAME=root'
4 | - 'DB_ADAPTER=sqlite3'
5 | rvm:
6 | - 1.9.2
7 | - 1.9.3
8 | - 2.0.0
9 | script: bundle exec rake app:masq:test:ci
10 |
--------------------------------------------------------------------------------
/lib/masq/active_record_openid_store/nonce.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class Nonce < ActiveRecord::Base
3 | self.table_name = 'masq_open_id_nonces'
4 |
5 | attr_accessible :server_url, :timestamp, :salt
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/views/masq/accounts/show.html.erb:
--------------------------------------------------------------------------------
1 | <%=h @account.login %>
2 | OpenID: <%= identifier(@account) %>
3 |
4 | <%= render :partial => 'hcard', :locals => {:persona => @account.public_persona } if @account.public_persona %>
--------------------------------------------------------------------------------
/test/fixtures/release_policies.yml:
--------------------------------------------------------------------------------
1 | venteria_nickname:
2 | site_id: 1
3 | property: nickname
4 | type_identifier: nickname
5 |
6 | venteria_email:
7 | site_id: 1
8 | property: email
9 | type_identifier: email
10 |
--------------------------------------------------------------------------------
/test/fixtures/sites.yml:
--------------------------------------------------------------------------------
1 | venteria:
2 | id: 1
3 | account_id: 1
4 | persona_id: 1
5 | url: http://venteria.com/
6 |
7 | blog:
8 | id: 2
9 | account_id: 1
10 | persona_id: 2
11 | url: http://blog.dopefreshtight.de/
--------------------------------------------------------------------------------
/app/views/masq/server/seatbelt_login_state.xml.builder:
--------------------------------------------------------------------------------
1 | xml.instruct!
2 | xml.personaConfig(:serverIdentifier => endpoint_url, :version => '1.0') do
3 | xml.persona(identifier(current_account), :displayName => current_account.login) if logged_in?
4 | end
--------------------------------------------------------------------------------
/app/views/masq/account_mailer/signup_notification.text.erb:
--------------------------------------------------------------------------------
1 | <%=t :account_created_but_not_activated_yet %>
2 |
3 | <%=t :please_activate_it_by_clicking_the_following_link %>:
4 |
5 | <%= activate_account_url(:activation_code => @account.activation_code) %>
6 |
--------------------------------------------------------------------------------
/config/initializers/requires.rb:
--------------------------------------------------------------------------------
1 | require 'openid'
2 | require 'openid/consumer/discovery'
3 | require 'openid/extensions/sreg'
4 | require 'openid/extensions/pape'
5 | require 'openid/extensions/ax'
6 | require 'yubikey' if Masq::Engine.config.masq['can_use_yubikey']
--------------------------------------------------------------------------------
/test/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Sample localization file for English. Add more files in this directory for other locales.
2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 |
4 | en:
5 | hello: "Hello world"
6 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/test/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | ---
2 | test:
3 | adapter: mysql2
4 | database: masq_test
5 | username:
6 | password:
7 | port:
8 | socket: /opt/boxen/data/mysql/socket
9 | host: localhost
10 | encoding: utf8
11 | pool: 5
12 | timeout: 5000
13 |
--------------------------------------------------------------------------------
/lib/masq.rb:
--------------------------------------------------------------------------------
1 | require "masq/engine"
2 | require "masq/authenticated_system"
3 | require "masq/openid_server_system"
4 | require "masq/active_record_openid_store/association"
5 | require "masq/active_record_openid_store/nonce"
6 | require "masq/active_record_openid_store/openid_ar_store"
7 |
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= stylesheet_link_tag "application", :media => "all" %>
6 | <%= csrf_meta_tags %>
7 |
8 |
9 |
10 | <%= yield %>
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | gemfile = File.expand_path('../../../../Gemfile', __FILE__)
3 |
4 | if File.exist?(gemfile)
5 | ENV['BUNDLE_GEMFILE'] = gemfile
6 | require 'bundler'
7 | Bundler.setup
8 | end
9 |
10 | $:.unshift File.expand_path('../../../../lib', __FILE__)
--------------------------------------------------------------------------------
/app/views/masq/account_mailer/signup_notification.html.erb:
--------------------------------------------------------------------------------
1 | <%=t :account_created_but_not_activated_yet %>
2 |
3 | <%=t :please_activate_it_by_clicking_the_following_link %>:
4 |
5 | <%= link_to t(:activation_link), activate_account_url(:activation_code => @account.activation_code) %>
6 |
--------------------------------------------------------------------------------
/app/views/masq/info/index.html.erb:
--------------------------------------------------------------------------------
1 | <%=t :get_your_openid %>
2 | <%= simple_format t(:openid_intro) %>
3 | <% unless Masq::Engine.config.masq['disable_registration'] %>
4 | <%= simple_format t(:openid_intro_link, :signup_link => link_to(t(:signup_for_an_openid), new_account_path)) %>
5 | <% end %>
6 |
--------------------------------------------------------------------------------
/test/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | # Add your own tasks in files placed in lib/tasks ending in .rake,
3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4 |
5 | require File.expand_path('../config/application', __FILE__)
6 |
7 | Dummy::Application.load_tasks
8 |
--------------------------------------------------------------------------------
/app/views/masq/personas/new.html.erb:
--------------------------------------------------------------------------------
1 | <%=t :create_a_new_persona_title %>
2 | <%= error_messages_for persona %>
3 |
4 | <%= form_for [:account, persona] do |f| %>
5 | <%= render :partial => 'form', :object => f %>
6 |
7 | <%= f.submit 'create', :class => 'labelspace' %>
8 |
9 | <% end %>
10 |
--------------------------------------------------------------------------------
/test/dummy/script/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3 |
4 | APP_PATH = File.expand_path('../../config/application', __FILE__)
5 | require File.expand_path('../../config/boot', __FILE__)
6 | require 'rails/commands'
7 |
--------------------------------------------------------------------------------
/app/views/masq/shared/_error_messages.html.erb:
--------------------------------------------------------------------------------
1 | <% errors = objects.sum { |object| object.errors.full_messages.map { |msg| msg } } %>
2 | <% if errors.any? %>
3 |
4 |
5 | <% errors.each do |msg| %>
6 | <%= msg %>
7 | <% end %>
8 |
9 |
10 | <% end %>
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle
2 | .DS_Store
3 | .loadpath
4 | .project
5 | .ruby-version
6 | .rvmrc
7 | Capfile
8 | Thumbs.db
9 | db/*.sqlite3
10 | doc/*
11 | log/*.log
12 | pkg/
13 | public/cache/**/*
14 | run_tests.sh
15 | test/dummy/db/*.sqlite3
16 | test/dummy/log/*.log
17 | test/dummy/tmp
18 | test/dummy/.sass-cache
19 | tmp
20 | vendor/ruby
21 |
--------------------------------------------------------------------------------
/app/views/masq/personas/edit.html.erb:
--------------------------------------------------------------------------------
1 | <%=t :edit_your_persona, :title => h(persona.title) %>
2 | <%= error_messages_for persona %>
3 |
4 | <%= form_for [:account, persona] do |f| %>
5 | <%= render :partial => 'form', :object => f %>
6 |
7 | <%= f.submit t(:submit_update), :class => 'labelspace' %>
8 |
9 | <% end %>
10 |
--------------------------------------------------------------------------------
/script/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3 |
4 | ENGINE_ROOT = File.expand_path('../..', __FILE__)
5 | ENGINE_PATH = File.expand_path('../../lib/masq/engine', __FILE__)
6 |
7 | require 'rails/all'
8 | require 'rails/engine/commands'
9 |
--------------------------------------------------------------------------------
/app/models/masq/release_policy.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class ReleasePolicy < ActiveRecord::Base
3 | belongs_to :site
4 |
5 | validates_presence_of :site
6 | validates_presence_of :property
7 | validates_uniqueness_of :property, :scope => [:site_id, :type_identifier]
8 |
9 | attr_accessible :property, :type_identifier
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | group :development, :test do
4 | platforms :ruby, :mswin, :mingw do
5 | gem 'mysql2'
6 | gem 'pg'
7 | gem 'sqlite3'
8 | end
9 | gem 'minitest'
10 | gem 'turn'
11 | gem 'mocha'
12 | gem 'ruby_gntp'
13 | gem 'guard-minitest'
14 | gem 'rb-fsevent', :require => false
15 | end
16 |
17 | gemspec
18 |
--------------------------------------------------------------------------------
/app/views/masq/passwords/new.html.erb:
--------------------------------------------------------------------------------
1 | <%=t :forgot_password_title %>
2 |
3 | <%= form_tag password_path do %>
4 |
5 | <%= content_tag :label, t(:please_enter_email), :for => 'email' %>
6 | <%= text_field_tag :email, params[:email] %>
7 |
8 |
9 | <%= submit_tag t(:submit_send) %>
10 |
11 | <% end %>
12 |
--------------------------------------------------------------------------------
/app/views/masq/info/help.html.erb:
--------------------------------------------------------------------------------
1 | <%= t(:questions_answers) %>
2 | <%=t :here_we_cover_openid_topics %>
3 | <%=t :openid_delegation %>
4 | <%=simple_format t(:delegation_explanation) %>
5 |
6 | <link rel="openid2.provider openid.server" href="<%= endpoint_url %>" />
7 | <link rel="openid2.local_id openid.delegate" href="<%= identifier(logged_in? ? current_account : 'YOUR_LOGIN') %>" />
8 |
--------------------------------------------------------------------------------
/lib/masq/active_record_openid_store/association.rb:
--------------------------------------------------------------------------------
1 | require 'openid/association'
2 |
3 | module Masq
4 | class Association < ActiveRecord::Base
5 | self.table_name = 'masq_open_id_associations'
6 |
7 | attr_accessible :server_url, :handle, :secret, :issued, :lifetime, :assoc_type
8 |
9 | def from_record
10 | OpenID::Association.new(handle, secret, issued, lifetime, assoc_type)
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/helpers/masq/server_helper.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | module ServerHelper
3 | def sreg_request_for_field(field_name)
4 | if sreg_request.required.include?(field_name)
5 | t(:required)
6 | elsif sreg_request.optional.include?(field_name)
7 | t(:optional)
8 | end
9 | end
10 |
11 | def ax_request_for_field(ax_attribute)
12 | ax_attribute.required ? t(:required) : t(:optional)
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
4 |
5 | # Use the database for sessions instead of the cookie-based default,
6 | # which shouldn't be used to store highly confidential information
7 | # (create the session table with "rails generate session_migration")
8 | # Dummy::Application.config.session_store :active_record_store
9 |
--------------------------------------------------------------------------------
/app/views/masq/passwords/edit.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_tag password_path(:id => @reset_code), :method => 'put' do %>
2 |
3 | <%= label_tag :password, t(:password) %>
4 | <%= password_field_tag :password %>
5 |
6 |
7 | <%= label_tag :password_confirmation, t(:password_confirmation) %>
8 | <%= password_field_tag :password_confirmation %>
9 |
10 |
11 | <%= submit_tag t(:reset_password) %>
12 |
13 | <% end %>
--------------------------------------------------------------------------------
/app/helpers/masq/personas_helper.rb:
--------------------------------------------------------------------------------
1 | require 'i18n_data'
2 |
3 | module Masq
4 | module PersonasHelper
5 | # get list of codes and names sorted by country name
6 | def countries_for_select
7 | ::I18nData.countries.map{|pair| pair.reverse}.sort{|x,y| x.first <=> y.first}
8 | end
9 |
10 | # get list of codes and names sorted by language name
11 | def languages_for_select
12 | ::I18nData.languages.map{|pair| pair.reverse}.sort{|x,y| x.first <=> y.first}
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 | # Make sure the secret is at least 30 characters and all random,
6 | # no regular words or you'll be exposed to dictionary attacks.
7 | Dummy::Application.config.secret_token = '2c8b19ba1ad3c45b3ab0e01c5f8a7ceb2f77b9005e89d1832ca3ca559526e0f1f3e5f9650a88010e9e2407e48f30c15b509327ee2f261e8f394844fcbf7faf82'
8 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # Disable root element in JSON by default.
12 | ActiveSupport.on_load(:active_record) do
13 | self.include_root_in_json = false
14 | end
15 |
--------------------------------------------------------------------------------
/app/views/masq/personas/index.html.erb:
--------------------------------------------------------------------------------
1 | <%=t :my_personas_title %>
2 |
3 | <%=t :personas_intro %>
4 |
5 |
6 | <% @personas.each do |persona| %>
7 |
8 | <%=h persona.title%>
9 |
10 | <%= link_to t(:edit), edit_account_persona_path(persona) %>
11 | <%= link_to t(:delete), account_persona_path(persona), :confirm => t(:really_want_to_delete_persona, :title => h(persona.title)), :method => :delete %>
12 |
13 |
14 | <% end %>
15 |
16 |
17 | <%= link_to t(:create_a_new_persona), new_account_persona_path %>
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## v0.3.2
4 |
5 | * Updated dependencies
6 |
7 | ## v0.3.1
8 |
9 | * Updated dependencies
10 |
11 | ## v0.3.0
12 |
13 | * Updated dependencies
14 | * Patch on routing for email identifiers
15 | * Internal improvements for testing
16 |
17 | ## v0.2.8
18 |
19 | * Updated dependencies
20 |
21 | ## v0.2.7
22 |
23 | * Updated dependencies, Gemfile.lock was not correct (crypt19 v1.2.1 does not exist anymore)
24 |
25 | ## v0.2.6
26 |
27 | * [Security] Updated Rails to version 3.2.12 and JSON to 1.7.7
28 |
29 | ## v0.2.5
30 |
31 | * [Security] Updated Rails to version 3.2.11
32 |
--------------------------------------------------------------------------------
/test/fixtures/personas.yml:
--------------------------------------------------------------------------------
1 | public:
2 | id: 1
3 | account_id: 1
4 | title: public
5 | nickname: dennisreimann
6 | email: mail@dennisreimann.de
7 | fullname: Dennis Reimann
8 | postcode: 28199
9 | country: DE
10 | language: DE
11 | timezone: Europe/Berlin
12 | gender: M
13 |
14 | private:
15 | id: 2
16 | account_id: 1
17 | title: private
18 | nickname: Dennis
19 | email: dennis@innovated.de
20 | fullname: Dennis Reimann
21 | postcode: 28199
22 | country: DE
23 | language: DE
24 | timezone: Europe/Berlin
25 | gender: M
26 | dob_day: 10
27 | dob_month: 01
28 | dob_year: 1982
--------------------------------------------------------------------------------
/test/support/active_support/test_case.rb:
--------------------------------------------------------------------------------
1 | class ActiveSupport::TestCase
2 | self.fixture_path = File.expand_path("../../../fixtures", __FILE__)
3 |
4 | set_fixture_class :accounts => Masq::Account
5 | set_fixture_class :personas => Masq::Persona
6 | set_fixture_class :release_policies => Masq::ReleasePolicy
7 | set_fixture_class :sites => Masq::Site
8 |
9 | # Note: You'll currently still have to declare fixtures explicitly in integration tests
10 | # -- they do not yet inherit this setting
11 | fixtures :all
12 |
13 | # Add more helper methods to be used by all tests here...
14 | include Masq::TestHelper
15 | end
--------------------------------------------------------------------------------
/test/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format
4 | # (all these examples are active by default):
5 | # ActiveSupport::Inflector.inflections do |inflect|
6 | # inflect.plural /^(ox)$/i, '\1en'
7 | # inflect.singular /^(ox)en/i, '\1'
8 | # inflect.irregular 'person', 'people'
9 | # inflect.uncountable %w( fish sheep )
10 | # end
11 | #
12 | # These inflection rules are supported but not enabled by default:
13 | # ActiveSupport::Inflector.inflections do |inflect|
14 | # inflect.acronym 'RESTful'
15 | # end
16 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the top of the
9 | * compiled file, but it's generally better to create a new file per style scope.
10 | *
11 | *= require_self
12 | *= require_tree .
13 | */
14 |
--------------------------------------------------------------------------------
/app/views/masq/server/index.xrds.builder:
--------------------------------------------------------------------------------
1 | xml.instruct!
2 | xml.xrds(:XRDS,
3 | 'xmlns:openid' => OpenID::OPENID_1_0_NS,
4 | 'xmlns:xrds' => 'xri://$xrds',
5 | 'xmlns' => 'xri://$xrd*($v*2.0)') do
6 | xml.XRD do
7 | xml.Service(:priority => 1) do
8 | xml.Type OpenID::OPENID_IDP_2_0_TYPE
9 | xml.Type OpenID::SReg::NS_URI_1_1
10 | xml.Type OpenID::SReg::NS_URI_1_0
11 | xml.Type OpenID::AX::AXMessage::NS_URI
12 | xml.Type OpenID::PAPE::AUTH_MULTI_FACTOR if Masq::Engine.config.masq['use_ssl']
13 | xml.Type OpenID::PAPE::AUTH_PHISHING_RESISTANT if Masq::Engine.config.masq['use_ssl']
14 | xml.URI endpoint_url
15 | end
16 | end
17 | end
--------------------------------------------------------------------------------
/app/mailers/masq/account_mailer.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class AccountMailer < ActionMailer::Base
3 | default :from => Masq::Engine.config.masq['email']
4 | default_url_options[:host] = Masq::Engine.config.masq['host']
5 |
6 | def signup_notification(account)
7 | raise "send_activation_mail deactivated" unless Masq::Engine.config.masq['send_activation_mail']
8 | @account = account
9 | mail :to => account.email, :subject => I18n.t(:please_activate_your_account)
10 | end
11 |
12 | def forgot_password(account)
13 | @account = account
14 | mail :to => account.email, :subject => I18n.t(:your_request_for_a_new_password)
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/test/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
We're sorry, but something went wrong.
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/views/masq/sites/index.html.erb:
--------------------------------------------------------------------------------
1 | <%=t :my_trusted_sites %>
2 |
3 | <%=t :identity_request_answered_without_interaction %>
4 | <%=t :alter_release_policies_here %>
5 |
6 | <% unless @sites.empty? %>
7 |
8 | <% @sites.each do |site| %>
9 |
10 | <%=h extract_host(site.url) %> (<%=h site.persona.title %>)
11 |
12 | <%= link_to t(:edit_link), edit_account_site_path(site) %>
13 | <%= link_to t(:delete_link), account_site_path(site), :confirm => t(:really_want_to_delete_trust_for_site, :site => h(extract_host(site.url))), :method => :delete %>
14 |
15 |
16 | <% end %>
17 |
18 | <% else %>
19 | <%=t :no_entries_yet %>
20 | <% end %>
21 |
--------------------------------------------------------------------------------
/app/controllers/masq/info_controller.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class InfoController < BaseController
3 | # The yadis discovery header tells incoming OpenID
4 | # requests where to find the server endpoint.
5 | def index
6 | response.headers['X-XRDS-Location'] = server_url(:format => :xrds, :protocol => scheme)
7 | end
8 |
9 | # This page is to prevent phishing attacks. It should
10 | # not contain any links, the user has to navigate to
11 | # the right login page manually.
12 | def safe_login
13 | if not Masq::Engine.config.masq.include? 'protect_phishing' or Masq::Engine.config.masq['protect_phishing']
14 | render :layout => false
15 | else
16 | redirect_to login_url
17 | end
18 | end
19 |
20 | def help
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/test/unit/masq/release_policy_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class ReleasePolicyTest < ActiveSupport::TestCase
5 | fixtures :release_policies
6 |
7 | def setup
8 | @release_policy = release_policies(:venteria_nickname)
9 | end
10 |
11 | def test_should_require_site
12 | @release_policy.site = nil
13 | assert_invalid @release_policy, :site
14 | end
15 |
16 | def test_should_be_unique_for_property_across_site_and_type_identifier
17 | @other_release_policy = release_policies(:venteria_email)
18 | @other_release_policy.property = @release_policy.property
19 | @other_release_policy.type_identifier = @release_policy.type_identifier
20 | assert_invalid @other_release_policy, :property
21 | end
22 |
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/app/views/masq/accounts/new.html.erb:
--------------------------------------------------------------------------------
1 | <%=t :signup_title %>
2 |
3 | <%= error_messages_for @account %>
4 |
5 | <%= form_for @account, :url => account_path do |f| -%>
6 | <% unless email_as_login? -%>
7 |
8 | <%= f.label :login, t(:login) %>
9 | <%= f.text_field :login %>
10 |
11 | <% end -%>
12 |
13 | <%= f.label :email, t(:email) %>
14 | <%= f.text_field :email %>
15 |
16 |
17 | <%= f.label :password, t(:password) %>
18 | <%= f.password_field :password %>
19 |
20 |
21 | <%= f.label :password_confirmation, t(:password_confirmation) %>
22 | <%= f.password_field :password_confirmation %>
23 |
24 |
25 | <%= f.submit t(:signup) %>
26 |
27 | <% end -%>
28 |
--------------------------------------------------------------------------------
/test/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The change you wanted was rejected.
23 |
Maybe you tried to change something you didn't have access to.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Guardfile:
--------------------------------------------------------------------------------
1 | guard 'minitest' do
2 | # with Minitest::Unit
3 | watch(%r|^test/(.*)\/?(.*)_test\.rb|)
4 | watch(%r|^lib/masq.*\.rb|) { "test" }
5 | watch(%r|^test/test_helper\.rb|) { "test" }
6 |
7 | # Rails
8 | watch(%r|^app/controllers/(.*)/application_controller\.rb|) { |m| "test/functional" }
9 | watch(%r|^app/controllers/(.*)/(.*)\.rb|) { |m| "test/functional/#{m[1]}/#{m[2]}_test.rb" }
10 | watch(%r|^app/helpers/(.*)/(.*)\.rb|) { |m| "test/helpers/#{m[1]}/#{m[2]}_test.rb" }
11 | watch(%r|^app/models/(.*)/(.*)\.rb|) { |m| "test/unit/#{m[1]}/#{m[2]}_test.rb" }
12 | watch(%r|^app/mailers/(.*)/(.*)\.rb|) { |m| "test/unit/#{m[1]}/#{m[2]}_test.rb" }
13 | watch(%r|^app/views/(.*)/(.*)|) { |m| "test/integration" }
14 | watch(%r|^config/routes\.rb|) { |m| "test/integration" }
15 | end
16 |
--------------------------------------------------------------------------------
/app/controllers/masq/yubikey_associations_controller.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class YubikeyAssociationsController < BaseController
3 | before_filter :login_required
4 |
5 | def create
6 | if current_account.associate_with_yubikey(params[:yubico_otp])
7 | flash[:notice] = t(:account_associated_with_yubico_identity)
8 | else
9 | flash[:alert] = t(:sorry_yubico_one_time_password_incorrect)
10 | end
11 | respond_to do |format|
12 | format.html { redirect_to edit_account_path }
13 | end
14 | end
15 |
16 | def destroy
17 | current_account.yubico_identity = nil
18 | current_account.save
19 |
20 | respond_to do |format|
21 | format.html { redirect_to edit_account_path, :notice => t(:account_disassociated_from_yubico_identity) }
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/test/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The page you were looking for doesn't exist.
23 |
You may have mistyped the address or the page may have moved.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/models/masq/open_id_request.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class OpenIdRequest < ActiveRecord::Base
3 | validates_presence_of :token, :parameters
4 |
5 | before_validation :make_token, :on => :create
6 |
7 | attr_accessible :parameters
8 | serialize :parameters, Hash
9 |
10 | def parameters=(params)
11 | self[:parameters] = params.is_a?(Hash) ? params.delete_if { |k,v| k.index('openid.') != 0 } : nil
12 | end
13 |
14 | def from_trusted_domain?
15 | host = URI.parse(parameters['openid.realm'] || parameters['openid.trust_root']).host
16 | unless Masq::Engine.config.masq['trusted_domains'].nil?
17 | Masq::Engine.config.masq['trusted_domains'].find { |domain| host.ends_with? domain }
18 | end
19 | end
20 |
21 | private
22 |
23 | def make_token
24 | self.token = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/test/unit/masq/persona_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class PersonaTest < ActiveSupport::TestCase
5 | fixtures :personas
6 |
7 | def setup
8 | @persona = personas(:public)
9 | end
10 |
11 | def test_should_require_account
12 | @persona.account = nil
13 | assert_invalid @persona, :account
14 | end
15 |
16 | def test_should_require_title
17 | @persona.title = nil
18 | assert_invalid @persona, :title
19 | end
20 |
21 | def test_should_have_unique_title_across_account
22 | @persona.title = personas(:private).title
23 | assert_invalid @persona, :title
24 | end
25 |
26 | def test_should_raise_not_deletable_on_destroy_if_not_flagged_deletable
27 | @persona.update_attribute(:deletable, false)
28 | assert_raises Persona::NotDeletable do
29 | @persona.destroy
30 | end
31 | end
32 |
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/app/views/masq/info/safe_login.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%=h page_title %>
5 |
6 |
7 |
8 | <%= stylesheet_link_tag 'masq/application' %>
9 |
10 |
11 |
12 |
<%= Masq::Engine.config.masq['name'] %>
13 |
14 |
15 |
16 |
<%=t :login_to_proceed %>
17 | <% if checkid_request %>
18 |
<%=t :host_requests_identification_you_need_to_login, :host => extract_host(checkid_request.trust_root) %>
19 | <% end %>
20 | <%=simple_format t(:login_intro, :login_url => login_url(:protocol => scheme)) %>
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/test/functional/masq/yubikey_associations_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class YubikeyAssociationsControllerTest < ActionController::TestCase
5 | include Masq::Engine.routes_url_helpers
6 |
7 | fixtures :accounts
8 |
9 | def test_should_associate_an_account_with_the_given_yubikey
10 | @account = accounts(:standard)
11 | login_as(:standard)
12 | yubico_otp = 'x' * 44
13 | Account.expects(:verify_yubico_otp).with(yubico_otp).returns(true)
14 | post :create, :yubico_otp => yubico_otp
15 | @account.reload
16 | assert_equal 'x' * 12, @account.yubico_identity
17 | assert_redirected_to edit_account_path
18 | end
19 |
20 | def test_should_remove_an_association
21 | @account = accounts(:with_yubico_identity)
22 | login_as(:with_yubico_identity)
23 | delete :destroy
24 | @account.reload
25 | assert_nil @account.yubico_identity
26 | assert_redirected_to edit_account_path
27 | end
28 |
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/app/views/masq/sessions/new.html.erb:
--------------------------------------------------------------------------------
1 | <%=t :login_title %>
2 |
3 | <%= form_tag session_path do %>
4 |
5 | <% if checkid_request && !checkid_request.id_select %>
6 | <%=t :your_openid %>:
7 | <%= checkid_request.identity %>
8 | <%= link_to t(:cancel_this_request), cancel_path %>
9 | <%= hidden_field_tag :login, extract_login_from_identifier(checkid_request.identity) %>
10 | <% else %>
11 | <%= label_tag :login, email_as_login? ? t(:email) : t(:login) %>
12 | <%= text_field_tag :login %>
13 | <% end %>
14 |
15 |
16 | <%= label_tag :password, t(:password) %>
17 | <%= password_field_tag :password %>
18 |
19 |
20 | <%= check_box_tag :remember_me %>
21 | <%= label_tag :remember_me, t(:remember_me), :class => 'check' %>
22 |
23 |
24 | <%= submit_tag t(:login_submit) %>
25 | <%= link_to t(:i_forgot_my_password), forgot_password_path %>
26 |
27 | <% end %>
--------------------------------------------------------------------------------
/test/fixtures/accounts.yml:
--------------------------------------------------------------------------------
1 | standard:
2 | id: 1
3 | enabled: true
4 | login: quentin
5 | email: quentin@example.com
6 | salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
7 | crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
8 | created_at: <%= 5.days.ago.to_s :db %>
9 | activated_at: <%= 5.days.ago.to_s :db %>
10 |
11 | inactive:
12 | id: 2
13 | enabled: true
14 | login: aaron
15 | email: aaron@example.com
16 | salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
17 | crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
18 | created_at: <%= 1.days.ago.to_s :db %>
19 | activation_code: 8f24789ae988411ccf33ab0c30fe9106fab32e9a
20 |
21 | with_yubico_identity:
22 | id: 3
23 | enabled: true
24 | login: yubikey
25 | email: yubikey@example.com
26 | salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
27 | crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
28 | yubico_identity: efveghxdfedg
29 | yubikey_mandatory: true
30 | created_at: <%= 5.days.ago.to_s :db %>
31 | activated_at: <%= 5.days.ago.to_s :db %>
32 |
33 |
--------------------------------------------------------------------------------
/masq.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path("../lib", __FILE__)
2 |
3 | # Maintain your gem's version:
4 | require "masq/version"
5 |
6 | # Describe your gem and declare its dependencies:
7 | Gem::Specification.new do |s|
8 | s.name = "masq"
9 | s.version = Masq::VERSION
10 | s.authors = ["Dennis Reimann"]
11 | s.email = ["mail@dennisreimann.de"]
12 | s.homepage = "https://github.com/dennisreimann/masq"
13 | s.summary = "Mountable Rails engine that provides OpenID server/identity provider functionality"
14 | s.description = "Masq supports the current OpenID specifications (OpenID 2.0) and supports SReg, AX (fetch and store requests) and PAPE as well as some custom additions like multifactor authentication using a yubikey"
15 |
16 | s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"]
17 | s.test_files = Dir["test/**/*"]
18 |
19 | s.add_dependency "rails", "~> 3.2.16"
20 | s.add_dependency "ruby-openid", "~> 2.5.0"
21 | s.add_dependency "ruby-yadis"
22 | s.add_dependency "yubikey"
23 | s.add_dependency "i18n_data"
24 | end
25 |
--------------------------------------------------------------------------------
/app/views/masq/accounts/_hcard.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <% if persona.web_blog and !persona.web_blog.empty? %>
3 |
<%= persona.fullname %>
4 | <% else %>
5 |
<%= persona.fullname %>
6 | <% end %>
7 | <% if persona.company_name%>
8 |
<%= persona.company_name %>
9 | <% end %>
10 | <% if persona.email %>
11 |
<%= persona.email %>
12 | <% end %>
13 | <% if persona.address %>
14 |
15 |
<%= persona.address %>
16 |
<%= persona.city %>
17 | ,
18 |
<%= persona.state %>
19 | ,
20 |
<%= persona.postcode %>
21 |
22 |
<%= persona.country %>
23 |
24 |
25 | <% end %>
26 | <% if persona.phone_mobile%>
27 |
<%= persona.phone_mobile%>
28 | <% end %>
29 |
--------------------------------------------------------------------------------
/test/unit/masq/open_id_request_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class OpenIdRequestTest < ActiveSupport::TestCase
5 |
6 | def setup
7 | @request = OpenIdRequest.create :parameters => checkid_request_params
8 | end
9 |
10 | def test_should_require_token
11 | @request.token = nil
12 | assert_invalid @request, :token
13 | end
14 |
15 | def test_should_generate_token_on_create
16 | @request = OpenIdRequest.new :parameters => checkid_request_params
17 | assert_nil @request.token
18 | assert @request.save
19 | assert_not_nil @request.token
20 | end
21 |
22 | def test_should_require_parameters
23 | @request.parameters = nil
24 | assert_invalid @request, :parameters
25 | end
26 |
27 | def test_should_reject_non_openid_parameters
28 | various_params = checkid_request_params.merge('test' => 1, 'foo' => 'bar')
29 | @request.parameters = various_params
30 | assert !@request.parameters.include?('test')
31 | assert !@request.parameters.include?('bar')
32 | end
33 |
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/app/views/layouts/masq/consumer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%=t :openid_consumer_testsuite_title %>
5 |
6 | <%= stylesheet_link_tag 'masq/application' %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 |
<%=t :relying_party_title %>
12 |
13 |
14 |
15 |
<%=t :openid_consumer_testsuite_title %>
16 | <% if flash[:notice] %>
<%=simple_format h flash[:notice] %>
<% end %>
17 | <% if flash[:alert] %>
<%=simple_format h flash[:alert] %>
<% end %>
18 | <%= yield %>
19 |
20 |
21 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Dennis Reimann (mail@dennisreimann.de)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/test/unit/masq/site_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class SiteTest < ActiveSupport::TestCase
5 | fixtures :sites, :release_policies
6 |
7 | def setup
8 | @site = sites(:venteria)
9 | end
10 |
11 | def test_should_require_account
12 | @site.account = nil
13 | assert_invalid @site, :account
14 | end
15 |
16 | def test_should_require_persona
17 | @site.persona = nil
18 | assert_invalid @site, :persona
19 | end
20 |
21 | def test_should_require_url
22 | @site.url = nil
23 | assert_invalid @site, :url
24 | end
25 |
26 | def test_should_have_unique_url_across_account
27 | @site.account = sites(:blog).account
28 | @site.url = sites(:blog).url
29 | assert_invalid @site, :url
30 | end
31 |
32 | def test_should_delete_associated_release_policies_on_destroy
33 | @release_policy = release_policies(:venteria_nickname)
34 | @release_policy.site.destroy
35 | assert_nil ReleasePolicy.find_by_id(@release_policy.id)
36 | end
37 |
38 | def test_should_set_release_policies_from_given_properties
39 | @site.properties = valid_properties
40 | assert_equal 6, @site.release_policies.size
41 | end
42 |
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/app/views/masq/server/seatbelt_config.xml.builder:
--------------------------------------------------------------------------------
1 | xml.instruct!
2 | xml.opConfig(:version => '1.0', :serverIdentifier => endpoint_url) do
3 | xml.configRevision('2008090801')
4 | xml.title(Masq::Engine.config.masq['name'])
5 | xml.serverIdentifier(endpoint_url)
6 | xml.opDomain(Masq::Engine.config.masq['host'])
7 | xml.opCertCommonName(Masq::Engine.config.masq['ssl_certificate_common_name']) if Masq::Engine.config.masq['use_ssl']
8 | xml.opCertSHA1Hash(Masq::Engine.config.masq['ssl_certificate_sha1']) if Masq::Engine.config.masq['use_ssl']
9 | xml.loginUrl(login_url(:protocol => scheme))
10 | xml.welcomeUrl(root_url(:protocol => scheme))
11 | xml.loginStateUrl(seatbelt_state_url(:protocol => scheme, :format => :xml))
12 | xml.settingsIconUrl("#{root_url(:protocol => scheme)}images/seatbelt_icon.png")
13 | xml.toolbarGrayIconUrl("#{root_url(:protocol => scheme)}images/seatbelt_icon_gray.png")
14 | xml.toolbarHighIconUrl("#{root_url(:protocol => scheme)}images/seatbelt_icon_high.png")
15 | xml.toolbarGrayBackground('#EBEBEB')
16 | xml.toolbarGrayBorder('#666666')
17 | xml.toolbarGrayText('#666666')
18 | xml.toolbarLoginBackground('#EBEBEB')
19 | xml.toolbarLoginBorder('#2B802B')
20 | xml.toolbarLoginText('#2B802B')
21 | xml.toolbarHighBackground('#EBEBEB')
22 | xml.toolbarHighBorder('#F50012')
23 | xml.toolbarHighText('#F50012')
24 | end
25 |
--------------------------------------------------------------------------------
/test/unit/masq/account_mailer_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class AccountMailerTest < ActiveSupport::TestCase
5 |
6 | def test_should_send_signup_notification_if_send_notification_mail_option_is_enabled
7 | Masq::Engine.config.masq['send_activation_mail'] = true
8 | account = Account.new valid_account_attributes
9 | account.activation_code = 'openid123'
10 | response = AccountMailer.signup_notification(account)
11 | assert_equal account.email, response.to[0]
12 | response.parts.each { |part| assert part.body.match('openid123') }
13 | end
14 |
15 | def test_should_not_send_signup_notification_if_send_notification_mail_option_is_disabled
16 | Masq::Engine.config.masq['send_activation_mail'] = false
17 | account = Account.new valid_account_attributes
18 | assert_raise RuntimeError, "send_activation_mail deactivated" do
19 | AccountMailer.signup_notification(account)
20 | end
21 | end
22 |
23 | def test_should_send_forgot_password
24 | account = Account.new valid_account_attributes
25 | account.password_reset_code = 'openid123'
26 | response = AccountMailer.forgot_password(account)
27 | assert_equal account.email, response.to[0]
28 | response.parts.each { |part| assert part.body.match('openid123') }
29 | end
30 |
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/app/models/masq/signup.rb:
--------------------------------------------------------------------------------
1 | require 'digest/sha1'
2 |
3 | module Masq
4 | class Signup
5 | attr_accessor :account
6 |
7 | def self.create_account!(attrs = {})
8 | signup = Signup.new attrs
9 | signup.send(:create_account!)
10 | signup
11 | end
12 |
13 | def succeeded?
14 | !account.new_record?
15 | end
16 |
17 | def send_activation_email?
18 | Masq::Engine.config.masq['send_activation_mail']
19 | end
20 |
21 | protected
22 |
23 | def initialize(attrs = {})
24 | self.account = Account.new(attrs)
25 | end
26 |
27 | def create_account!
28 | return false unless account.valid?
29 | make_activation_code if send_activation_email?
30 | account.save!
31 | make_default_persona
32 | if send_activation_email?
33 | AccountMailer.signup_notification(account).deliver
34 | else
35 | account.activate!
36 | end
37 | account
38 | end
39 |
40 | def make_activation_code
41 | account.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
42 | end
43 |
44 | def make_default_persona
45 | account.public_persona = account.personas.build(:title => "Standard", :email => account.email)
46 | account.public_persona.deletable = false
47 | account.public_persona.save!
48 | end
49 |
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/app/views/masq/consumer/index.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_tag consumer_start_path do %>
2 |
3 | <%= label_tag :openid_identifier, t(:identifier) + ':', :class => 'check' %>
4 | <%= text_field_tag :openid_identifier, Masq::Engine.config.masq['host'] %>
5 | <%= submit_tag t(:verify) %>
6 |
7 |
8 | <%= check_box_tag :immediate %>
9 | <%= label_tag :immediate, t(:use_immediate_mode), :class => 'check' %>
10 |
11 |
12 | <%= check_box_tag :use_sreg, params[:did_sreg] %>
13 | <%= label_tag :use_sreg, t(:request_registration_data), :class => 'check' %>
14 |
15 |
16 | <%= check_box_tag :use_ax_fetch, params[:did_ax_fetch] %>
17 | <%= label_tag :use_ax_fetch, t(:request_attribute_exchange_data), :class => 'check' %>
18 |
19 |
20 | <%= check_box_tag :use_ax_store, params[:did_ax_store] %>
21 | <%= label_tag :use_ax_store, t(:store_attribute_exchange_data), :class => 'check' %>
22 |
23 |
24 | <%= check_box_tag :use_pape, params[:did_pape] %>
25 | <%= label_tag :use_pape, t(:request_pape), :class => 'check' %>
26 |
27 |
28 | <%= check_box_tag :force_post, params[:force_post] %>
29 | <%= label_tag :force_post, t(:force_post), :class => 'check' %>
30 |
31 | <% end %>
32 |
--------------------------------------------------------------------------------
/app/controllers/masq/sites_controller.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class SitesController < BaseController
3 | before_filter :login_required
4 | before_filter :find_personas, :only => [:create, :edit, :update]
5 |
6 | helper_method :site, :persona
7 |
8 | def index
9 | @sites = current_account.sites.includes(:persona).order(:url)
10 |
11 | respond_to do |format|
12 | format.html
13 | end
14 | end
15 |
16 | def edit
17 | site.persona = current_account.personas.find(params[:persona_id]) if params[:persona_id]
18 | end
19 |
20 | def update
21 | respond_to do |format|
22 | if site.update_attributes(params[:site])
23 | flash[:notice] = t(:release_policy_for_site_updated)
24 | format.html { redirect_to edit_account_site_path(site) }
25 | else
26 | format.html { render :action => 'edit' }
27 | end
28 | end
29 | end
30 |
31 | def destroy
32 | site.destroy
33 |
34 | respond_to do |format|
35 | format.html { redirect_to account_sites_path }
36 | end
37 | end
38 |
39 | private
40 |
41 | def site
42 | @site ||= current_account.sites.find(params[:id])
43 | end
44 |
45 | def persona
46 | @persona ||= site.persona
47 | end
48 |
49 | def find_personas
50 | @personas = current_account.personas.order(:title)
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Log error messages when you accidentally call methods on nil.
10 | config.whiny_nils = true
11 |
12 | # Show full error reports and disable caching
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger
20 | config.active_support.deprecation = :log
21 |
22 | # Only use best-standards-support built into browsers
23 | config.action_dispatch.best_standards_support = :builtin
24 |
25 | # Raise exception on mass assignment protection for Active Record models
26 | config.active_record.mass_assignment_sanitizer = :strict
27 |
28 | # Log the query plan for queries taking more than this (works
29 | # with SQLite, MySQL, and PostgreSQL)
30 | config.active_record.auto_explain_threshold_in_seconds = 0.5
31 |
32 | # Do not compress assets
33 | config.assets.compress = false
34 |
35 | # Expands the lines which load the assets
36 | config.assets.debug = true
37 | end
38 |
--------------------------------------------------------------------------------
/app/models/masq/persona.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class Persona < ActiveRecord::Base
3 | belongs_to :account
4 | has_many :sites, :dependent => :destroy
5 |
6 | validates_presence_of :account
7 | validates_presence_of :title
8 | validates_uniqueness_of :title, :scope => :account_id
9 |
10 | before_destroy :check_deletable!
11 |
12 | attr_protected :account_id, :deletable
13 |
14 | class NotDeletable < StandardError; end
15 |
16 | def self.properties
17 | Persona.mappings.keys
18 | end
19 |
20 | def self.attribute_name_for_type_uri(type_uri)
21 | prop = mappings.detect { |i| i[1].include?(type_uri) }
22 | prop ? prop[0] : nil
23 | end
24 |
25 | # Returns the personas attribute for the given SReg name or AX Type URI
26 | def property(type)
27 | prop = Persona.mappings.detect { |i| i[1].include?(type) }
28 | prop ? self.send(prop[0]).to_s : nil
29 | end
30 |
31 | def date_of_birth
32 | "#{dob_year? ? dob_year : '0000'}-#{dob_month? ? dob_month.to_s.rjust(2, '0') : '00'}-#{dob_day? ? dob_day.to_s.rjust(2, '0') : '00'}"
33 | end
34 |
35 | def date_of_birth=(dob)
36 | res = dob.split("-")
37 | self.dob_year = res[0]
38 | self.dob_month = res[1]
39 | self.dob_day = res[2]
40 | dob
41 | end
42 |
43 | protected
44 |
45 | def check_deletable!
46 | raise NotDeletable unless deletable
47 | end
48 |
49 | private
50 |
51 | # Mappings for SReg names and AX Type URIs to attributes
52 | def self.mappings
53 | Masq::Engine.config.masq['attribute_mappings']
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/app/controllers/masq/passwords_controller.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class PasswordsController < BaseController
3 | before_filter :check_can_change_password, :only => [:create, :edit, :update]
4 | before_filter :find_account_by_reset_code, :only => [:edit, :update]
5 |
6 | # Forgot password
7 | def create
8 | if account = Account.where(:email => params[:email], :activation_code => nil).first
9 | account.forgot_password!
10 | redirect_to login_path, :notice => t(:password_reset_link_has_been_sent)
11 | else
12 | flash[:alert] = t(:could_not_find_user_with_email_address)
13 | render :action => 'new'
14 | end
15 | end
16 |
17 | # Reset password
18 | def update
19 | unless params[:password].blank?
20 | if @account.update_attributes(:password => params[:password], :password_confirmation => params[:password_confirmation])
21 | redirect_to login_path, :notice => t(:password_reset)
22 | else
23 | flash[:alert] = t(:password_mismatch)
24 | render :action => 'edit'
25 | end
26 | else
27 | flash[:alert] = t(:password_cannot_be_blank)
28 | render :action => 'edit'
29 | end
30 | end
31 |
32 | private
33 |
34 | def find_account_by_reset_code
35 | @reset_code = params[:id]
36 | @account = Account.find_by_password_reset_code(@reset_code) unless @reset_code.blank?
37 | redirect_to(forgot_password_path, :alert => t(:reset_code_invalid_try_again)) unless @account
38 | end
39 |
40 | def check_can_change_password
41 | render_404 unless Masq::Engine.config.masq['can_change_password']
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Configure static asset server for tests with Cache-Control for performance
11 | config.serve_static_assets = true
12 | config.static_cache_control = "public, max-age=3600"
13 |
14 | # Log error messages when you accidentally call methods on nil
15 | config.whiny_nils = true
16 |
17 | # Show full error reports and disable caching
18 | config.consider_all_requests_local = true
19 | config.action_controller.perform_caching = false
20 |
21 | # Raise exceptions instead of rendering exception templates
22 | config.action_dispatch.show_exceptions = false
23 |
24 | # Disable request forgery protection in test environment
25 | config.action_controller.allow_forgery_protection = false
26 |
27 | # Tell Action Mailer not to deliver emails to the real world.
28 | # The :test delivery method accumulates sent emails in the
29 | # ActionMailer::Base.deliveries array.
30 | config.action_mailer.delivery_method = :test
31 |
32 | # Raise exception on mass assignment protection for Active Record models
33 | config.active_record.mass_assignment_sanitizer = :strict
34 |
35 | # Print deprecation notices to the stderr
36 | config.active_support.deprecation = :stderr
37 | end
38 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | begin
3 | require 'bundler'
4 | require 'bundler/setup'
5 | Bundler::GemHelper.install_tasks
6 | rescue LoadError
7 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
8 | end
9 |
10 | begin
11 | require 'rdoc/task'
12 | rescue LoadError
13 | require 'rdoc/rdoc'
14 | require 'rake/rdoctask'
15 | RDoc::Task = Rake::RDocTask
16 | end
17 |
18 | RDoc::Task.new(:rdoc) do |rdoc|
19 | rdoc.rdoc_dir = 'rdoc'
20 | rdoc.title = 'Masq'
21 | rdoc.options << '--line-numbers'
22 | rdoc.rdoc_files.include('lib/**/*.rb')
23 | end
24 |
25 | APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
26 | load 'rails/tasks/engine.rake'
27 |
28 | Bundler::GemHelper.install_tasks
29 |
30 | require 'rake/testtask'
31 |
32 | namespace :test do |ns|
33 | desc "Prepare tests"
34 | task :prepare do
35 | Rails.env = 'test'
36 | Rake::Task['db:setup'].invoke
37 | end
38 |
39 | tests = %w(unit functional integration)
40 |
41 | tests.each do |type|
42 | desc "Run #{type} tests"
43 | Rake::TestTask.new(type) do |t|
44 | t.libs << 'lib'
45 | t.libs << 'test'
46 | t.test_files = FileList["test/#{type}/**/*_test.rb"]
47 | t.verbose = false
48 | end
49 | end
50 |
51 | desc "Run all tests"
52 | Rake::TestTask.new('all') do |t|
53 | files = []
54 | tests.each { |type| files += FileList["test/#{type}/**/*_test.rb"] }
55 |
56 | t.libs << 'lib'
57 | t.libs << 'test'
58 | t.test_files = files
59 | t.verbose = false
60 | end
61 |
62 | end
63 |
64 | Rake::Task['test'].clear
65 | desc "Run tests"
66 | task :test => %w[test:prepare test:all]
67 |
68 | task :default => :test
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Masq::Engine.routes.draw do
2 | resource :account do
3 | get :activate
4 | get :password
5 | put :change_password
6 |
7 | resources :personas
8 | resources :sites
9 | resource :yubikey_association, :only => [:create, :destroy]
10 | end
11 |
12 | resource :password
13 | resource :session, :only => [:new, :create, :destroy]
14 |
15 | get "/help" => "info#help", :as => :help
16 | get "/safe-login" => "info#safe_login", :as => :safe_login
17 |
18 | get "/forgot_password" => "passwords#new", :as => :forgot_password
19 | get "/reset_password/:id" => "passwords#edit", :as => :reset_password
20 |
21 | get "/login" => "sessions#new", :as => :login
22 | get "/logout" => "sessions#destroy", :as => :logout
23 | post '/resend_activation_email/*account' => 'accounts#resend_activation_email', :as => :resend_activation_email
24 |
25 | match "/server" => "server#index", :as => :server
26 | match "/server/decide" => "server#decide", :as => :decide
27 | match "/server/proceed" => "server#proceed", :as => :proceed
28 | match "/server/complete" => "server#complete", :as => :complete
29 | match "/server/cancel" => "server#cancel", :as => :cancel
30 | get "/server/seatbelt/config.:format" => "server#seatbelt_config", :as => :seatbelt_config
31 | get "/server/seatbelt/state.:format" => "server#seatbelt_login_state", :as => :seatbelt_state
32 |
33 | get "/consumer" => "consumer#index", :as => :consumer
34 | post "/consumer/start" => "consumer#start", :as => :consumer_start
35 | match "/consumer/complete" => "consumer#complete", :as => :consumer_complete
36 |
37 | get "/*account" => "accounts#show", :as => :identity, :constraints => {:format => /\.xrds/}
38 |
39 | root :to => "info#index"
40 | end
41 |
--------------------------------------------------------------------------------
/app/controllers/masq/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class SessionsController < BaseController
3 | before_filter :login_required, :only => :destroy
4 | after_filter :set_login_cookie, :only => :create
5 |
6 | def new
7 | redirect_after_login if logged_in?
8 | end
9 |
10 | def create
11 | self.current_account = Account.authenticate(params[:login], params[:password])
12 | if logged_in?
13 | flash[:notice] = t(:you_are_logged_in)
14 | redirect_after_login
15 | else
16 | a = Account.find_by_login(params[:login])
17 | if a.nil?
18 | redirect_to login_path, :alert => t(:login_incorrect)
19 | elsif a.active? && a.enabled?
20 | redirect_to login_path, :alert => t(:password_incorrect)
21 | elsif not a.enabled?
22 | redirect_to login_path, :alert => t(:account_disabled)
23 | else
24 | redirect_to login_path(:resend_activation_for => params[:login]), :alert => t(:account_not_yet_activated)
25 | end
26 | end
27 | end
28 |
29 | def destroy
30 | current_account.forget_me
31 | cookies.delete :auth_token
32 | reset_session
33 | redirect_to root_path, :notice => t(:you_are_now_logged_out)
34 | end
35 |
36 | private
37 |
38 | def set_login_cookie
39 | if logged_in? and params[:remember_me] == '1'
40 | current_account.remember_me
41 | cookies[:auth_token] = {
42 | :value => current_account.remember_token,
43 | :expires => current_account.remember_token_expires_at }
44 | end
45 | end
46 |
47 | def redirect_after_login
48 | if return_to = session[:return_to]
49 | session[:return_to] = nil
50 | redirect_to return_to
51 | else
52 | redirect_to identifier(current_account)
53 | end
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/tasks/masq_tasks.rake:
--------------------------------------------------------------------------------
1 | namespace :masq do
2 |
3 | namespace :install do
4 | desc "Install configuration and migrations"
5 | task :all do
6 | %w(config migrations).each { |t| Rake::Task["masq:install:#{t}"].invoke }
7 | end
8 |
9 | desc "Copy configuration file from masq to application"
10 | task :config do
11 | target = Rails.root.join("config/masq.yml")
12 | unless File.exists?(target)
13 | require 'fileutils'
14 | source = File.expand_path('../../../config/masq.example.yml', __FILE__)
15 | FileUtils.cp source, target
16 | puts "Created config/masq.yml"
17 | end
18 | end
19 | end
20 |
21 | namespace :openid do
22 | desc 'Cleanup OpenID store'
23 | task :cleanup_store => :environment do
24 | Masq::ActiveRecordStore.new.cleanup
25 | end
26 | end
27 |
28 | namespace :test do
29 | desc "Prepare CI build task"
30 | task :prepare_ci do
31 | adapter = ENV["DB_ADAPTER"] || "sqlite3"
32 | database = ENV["DB_DATABASE"] || ("sqlite3" == adapter ? "db/test.sqlite3" : "masq_test")
33 |
34 | config = {
35 | "test" => {
36 | "adapter" => adapter,
37 | "database" => database,
38 | "username" => ENV["DB_USERNAME"],
39 | "password" => ENV["DB_PASSWORD"],
40 | "port" => ENV["DB_PORT"] ? ENV["DB_PORT"].to_i : nil,
41 | "socket" => ENV["DB_SOCKET"] ? ENV["DB_SOCKET"] : nil,
42 | "host" => "localhost",
43 | "encoding" => "utf8",
44 | "pool" => 5,
45 | "timeout" => 5000
46 | }
47 | }
48 |
49 | File.open(Rails.root.join("config/database.yml"), "w") do |f|
50 | f.write(config.to_yaml)
51 | end
52 | end
53 |
54 | desc "Run CI build task"
55 | task :ci => [:prepare_ci] do
56 | Rake::Task['test'].invoke
57 | end
58 | end
59 |
60 | end
--------------------------------------------------------------------------------
/app/views/masq/sites/edit.html.erb:
--------------------------------------------------------------------------------
1 | <%=t :your_release_policy_for_site, :site => h(extract_host(site.url)) %>
2 |
3 | <%= error_messages_for site %>
4 |
5 | <%= form_for site, :url => account_site_path(site), :method => :put do |f| %>
6 |
7 | <%= f.label :persona_id, t(:persona_label) %>
8 | <%= f.select :persona_id, current_account.personas.all.collect { |p| [ p.title, p.id ] } %>
9 | <%= submit_tag t(:choose_persona_submit) %>
10 |
11 |
12 |
13 | <%=h site.persona.title %>
14 |
15 | <%= link_to t(:edit_persona_link), edit_account_persona_path(site.persona, :return => params[:persona_id] ? url_for(:persona_id => params[:persona_id]) : nil) %> <%=t :or %>
16 | <%= link_to t(:create_new_persona_link), new_account_persona_path(:return => request.url) %>
17 |
18 |
19 |
20 |
21 |
22 | <%=t :property %>
23 | <%=t :value %>
24 | <%=t :disclosure %>
25 |
26 | <% site.release_policies.each do |release_policy| %>
27 | <% property, type, value = release_policy.property, release_policy.type_identifier, site.persona.property(release_policy.type_identifier) %>
28 |
29 | <%= label_tag "site_properties_#{property}", property_label_text(property) %>
30 | <%= label_tag "site_properties_#{property}", value unless value.blank? %>
31 |
32 | <%= check_box_tag "site[properties][#{property}][value]", value, true, :id => "site_properties_#{property}" %>
33 | <%= hidden_field_tag "site[properties][#{property}][type]", type, :id => "site_properties_type_#{property}" %>
34 |
35 |
36 | <% end %>
37 |
38 |
39 |
40 | <%= submit_tag t(:update_release_policy_submit) %>
41 |
42 | <% end %>
43 |
--------------------------------------------------------------------------------
/app/views/masq/accounts/show.xrds.builder:
--------------------------------------------------------------------------------
1 | xml.instruct!
2 | xml.xrds(:XRDS,
3 | 'xmlns:openid' => OpenID::OPENID_1_0_NS,
4 | 'xmlns:xrds' => 'xri://$xrds',
5 | 'xmlns' => 'xri://$xrd*($v*2.0)') do
6 | xml.XRD do
7 | xml.Service(:priority => 1) do
8 | xml.Type OpenID::OPENID_2_0_TYPE
9 | xml.Type OpenID::SReg::NS_URI_1_1
10 | xml.Type OpenID::SReg::NS_URI_1_0
11 | xml.Type OpenID::AX::AXMessage::NS_URI
12 | xml.Type OpenID::PAPE::AUTH_MULTI_FACTOR if Masq::Engine.config.masq['use_ssl'] && @account.has_otp_device?
13 | xml.Type OpenID::PAPE::AUTH_PHISHING_RESISTANT if Masq::Engine.config.masq['use_ssl'] && @account.has_otp_device?
14 | xml.URI endpoint_url
15 | xml.LocalID identity_url(:account => @account, :protocol => scheme)
16 | end
17 | xml.Service(:priority => 2) do
18 | xml.Type OpenID::OPENID_1_1_TYPE
19 | xml.Type OpenID::SReg::NS_URI_1_1
20 | xml.Type OpenID::SReg::NS_URI_1_0
21 | xml.Type OpenID::AX::AXMessage::NS_URI
22 | xml.Type OpenID::PAPE::AUTH_MULTI_FACTOR if Masq::Engine.config.masq['use_ssl'] && @account.has_otp_device?
23 | xml.Type OpenID::PAPE::AUTH_PHISHING_RESISTANT if Masq::Engine.config.masq['use_ssl'] && @account.has_otp_device?
24 | xml.URI endpoint_url
25 | xml.tag!('openid:Delegate', identity_url(:account => @account, :protocol => scheme))
26 | end
27 | xml.Service(:priority => 3) do
28 | xml.Type OpenID::OPENID_1_0_TYPE
29 | xml.Type OpenID::SReg::NS_URI_1_1
30 | xml.Type OpenID::SReg::NS_URI_1_0
31 | xml.Type OpenID::AX::AXMessage::NS_URI
32 | xml.Type OpenID::PAPE::AUTH_MULTI_FACTOR if Masq::Engine.config.masq['use_ssl'] && @account.has_otp_device?
33 | xml.Type OpenID::PAPE::AUTH_PHISHING_RESISTANT if Masq::Engine.config.masq['use_ssl'] && @account.has_otp_device?
34 | xml.URI endpoint_url
35 | xml.tag!('openid:Delegate', identity_url(:account => @account, :protocol => scheme))
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/app/controllers/masq/personas_controller.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class PersonasController < BaseController
3 | before_filter :login_required
4 | before_filter :store_return_url, :only => [:new, :edit]
5 |
6 | helper_method :persona
7 |
8 | def index
9 | @personas = current_account.personas
10 |
11 | respond_to do |format|
12 | format.html
13 | end
14 | end
15 |
16 | def create
17 | respond_to do |format|
18 | if persona.save!
19 | flash[:notice] = t(:persona_successfully_created)
20 | format.html { redirect_back_or_default account_personas_path }
21 | else
22 | format.html { render :action => "new" }
23 | end
24 | end
25 | end
26 |
27 | def update
28 | respond_to do |format|
29 | if persona.update_attributes(params[:persona])
30 | flash[:notice] = t(:persona_updated)
31 | format.html { redirect_back_or_default account_personas_path }
32 | else
33 | format.html { render :action => "edit" }
34 | end
35 | end
36 | end
37 |
38 | def destroy
39 | respond_to do |format|
40 | begin
41 | persona.destroy
42 | rescue Persona::NotDeletable
43 | flash[:alert] = t(:persona_cannot_be_deleted)
44 | end
45 | format.html { redirect_to account_personas_path }
46 | end
47 | end
48 |
49 | protected
50 |
51 | def persona
52 | @persona ||= params[:id].present? ?
53 | current_account.personas.find(params[:id]) :
54 | current_account.personas.new(params[:persona])
55 | end
56 |
57 | def redirect_back_or_default(default)
58 | case session[:return_to]
59 | when decide_path then redirect_to decide_path(:persona_id => persona.id)
60 | else super(default)
61 | end
62 | end
63 |
64 | def store_return_url
65 | store_location(params[:return]) unless params[:return].blank?
66 | end
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/test/functional/masq/sites_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class SitesControllerTest < ActionController::TestCase
5 | include Masq::Engine.routes_url_helpers
6 |
7 | fixtures :accounts, :sites, :personas
8 |
9 | def test_should_require_login_for_index
10 | get :index
11 | assert_login_required
12 | end
13 |
14 | def test_should_have_list_of_sites_on_index
15 | login_as(:standard)
16 | get :index
17 | assert_response :success
18 | assert_not_nil assigns(:sites)
19 | end
20 |
21 | def test_should_require_login_for_edit
22 | get :edit, :id => sites(:venteria).id
23 | assert_login_required
24 | end
25 |
26 | def test_should_get_edit
27 | login_as(:standard)
28 | get :edit, :id => sites(:venteria).id
29 | assert_response :success
30 | end
31 |
32 | def test_should_require_login_for_update
33 | put :update, :id => sites(:venteria).id, :site => valid_site_attributes
34 | assert_login_required
35 | end
36 |
37 | def test_should_update_site
38 | login_as(:standard)
39 | @site = sites(:venteria)
40 | put :update, :id => @site.id, :site => valid_site_attributes
41 | assert_redirected_to edit_account_site_path(@site)
42 | end
43 |
44 | def test_should_update_release_policies_on_site_update
45 | login_as(:standard)
46 | @site = sites(:venteria)
47 | put :update, :id => @site.id,
48 | :site => valid_site_attributes.merge(:properties => valid_properties)
49 | assert_equal 6, @site.release_policies.size
50 | end
51 |
52 | def test_should_require_login_for_destroy
53 | delete :destroy, :id => sites(:venteria).id
54 | assert_login_required
55 | end
56 |
57 | def test_should_destroy_site
58 | login_as(:standard)
59 | assert_difference('Site.count', -1) do
60 | delete :destroy, :id => sites(:venteria).id
61 | end
62 | assert_redirected_to account_sites_path
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/lib/masq/active_record_openid_store/openid_ar_store.rb:
--------------------------------------------------------------------------------
1 | require 'openid/store/interface'
2 |
3 | module Masq
4 | # not in OpenID module to avoid namespace conflict
5 | class ActiveRecordStore < OpenID::Store::Interface
6 | def store_association(server_url, assoc)
7 | remove_association(server_url, assoc.handle)
8 | Association.create(:server_url => server_url,
9 | :handle => assoc.handle,
10 | :secret => assoc.secret,
11 | :issued => assoc.issued,
12 | :lifetime => assoc.lifetime,
13 | :assoc_type => assoc.assoc_type)
14 | end
15 |
16 | def get_association(server_url, handle=nil)
17 | assocs = if handle.blank?
18 | Association.find_all_by_server_url(server_url)
19 | else
20 | Association.find_all_by_server_url_and_handle(server_url, handle)
21 | end
22 |
23 | assocs.reverse.each do |assoc|
24 | a = assoc.from_record
25 | if a.expires_in == 0
26 | assoc.destroy
27 | else
28 | return a
29 | end
30 | end if assocs.any?
31 |
32 | return nil
33 | end
34 |
35 | def remove_association(server_url, handle)
36 | Association.delete_all(['server_url = ? AND handle = ?', server_url, handle]) > 0
37 | end
38 |
39 | def use_nonce(server_url, timestamp, salt)
40 | return false if Nonce.find_by_server_url_and_timestamp_and_salt(server_url, timestamp, salt)
41 | return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
42 | Nonce.create(:server_url => server_url, :timestamp => timestamp, :salt => salt)
43 | return true
44 | end
45 |
46 | def cleanup_nonces
47 | now = Time.now.to_i
48 | Nonce.delete_all(["timestamp > ? OR timestamp < ?", now + OpenID::Nonce.skew, now - OpenID::Nonce.skew])
49 | end
50 |
51 | def cleanup_associations
52 | now = Time.now.to_i
53 | Association.delete_all(['issued + lifetime > ?',now])
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/test/unit/masq/signup_test.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'test_helper'
3 |
4 | module Masq
5 | class SignupTest < ActiveSupport::TestCase
6 |
7 | def test_signup_with_valid_account
8 | signup = Signup.create_account! valid_account_attributes
9 | assert signup.succeeded?
10 | assert_kind_of Account, signup.account
11 | end
12 |
13 | def test_signup_with_invalid_account
14 | signup = Signup.create_account!
15 | assert !signup.succeeded?
16 | assert_kind_of Account, signup.account
17 | end
18 |
19 | def test_should_assign_activation_code_on_create_if_send_activation_mail_is_enabled
20 | Masq::Engine.config.masq['send_activation_mail'] = true
21 | signup = Signup.create_account! valid_account_attributes
22 | assert_not_nil signup.account.activation_code
23 | end
24 |
25 | def test_should_send_activation_mail_on_create_if_send_activation_mail_is_enabled
26 | Masq::Engine.config.masq['send_activation_mail'] = true
27 | mail = mock()
28 | mail.expects(:deliver)
29 | AccountMailer.expects(:signup_notification).returns(mail)
30 | Signup.create_account! valid_account_attributes
31 | end
32 |
33 | def test_should_not_assign_activation_code_on_create_if_send_activation_mail_is_disabled
34 | Masq::Engine.config.masq['send_activation_mail'] = false
35 | signup = Signup.create_account! valid_account_attributes
36 | assert_nil signup.account.activation_code
37 | end
38 |
39 | def test_should_not_send_activation_mail_on_create_if_send_activation_mail_is_disabled
40 | Masq::Engine.config.masq['send_activation_mail'] = false
41 | AccountMailer.expects(:signup_notification).never
42 | Signup.create_account! valid_account_attributes
43 | end
44 |
45 | def test_should_create_default_personas_on_create
46 | signup = Signup.create_account! valid_account_attributes
47 | account = signup.account
48 | persona = account.personas.first
49 | assert_not_nil persona
50 | assert_equal 'Standard', persona.title
51 | assert_equal account.email, persona.email
52 | assert_equal 1, account.personas.size
53 | end
54 |
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/app/controllers/masq/base_controller.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class BaseController < ActionController::Base
3 | include OpenidServerSystem
4 | include AuthenticatedSystem
5 |
6 | protect_from_forgery
7 |
8 | rescue_from ::ActiveRecord::RecordNotFound, :with => :render_404
9 | rescue_from ::ActionController::InvalidAuthenticityToken, :with => :render_422
10 |
11 | helper_method :extract_host, :extract_login_from_identifier, :checkid_request,
12 | :identifier, :endpoint_url, :scheme, :email_as_login?
13 |
14 | protected
15 |
16 | def endpoint_url
17 | server_url(:protocol => scheme)
18 | end
19 |
20 | # Returns the OpenID identifier for an account
21 | def identifier(account)
22 | identity_url(:account => account, :protocol => scheme)
23 | end
24 |
25 | # Extracts the hostname from the given url, which is used to
26 | # display the name of the requesting website to the user
27 | def extract_host(u)
28 | URI.split(u).compact[1]
29 | end
30 |
31 | def extract_login_from_identifier(openid_url)
32 | openid_url.gsub(/^https?:\/\/.*\//, '')
33 | end
34 |
35 | def checkid_request
36 | unless @checkid_request
37 | req = openid_server.decode_request(current_openid_request.parameters) if current_openid_request
38 | @checkid_request = req.is_a?(OpenID::Server::CheckIDRequest) ? req : false
39 | end
40 | @checkid_request
41 | end
42 |
43 | def current_openid_request
44 | @current_openid_request ||= OpenIdRequest.find_by_token(session[:request_token]) if session[:request_token]
45 | end
46 |
47 | def render_404
48 | render_error(404)
49 | end
50 |
51 | def render_422
52 | render_error(422)
53 | end
54 |
55 | def render_500
56 | render_error(500)
57 | end
58 |
59 | def render_error(status_code)
60 | render :file => "#{Rails.root}/public/#{status_code}", :formats => [:html], :status => status_code, :layout => false
61 | end
62 |
63 | private
64 |
65 | def scheme
66 | Masq::Engine.config.masq['use_ssl'] ? 'https' : 'http'
67 | end
68 |
69 | def email_as_login?
70 | Masq::Engine.config.masq['email_as_login']
71 | end
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/app/helpers/masq/application_helper.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 |
3 | module ApplicationHelper
4 | def page_title
5 | @page_title ? "#{@page_title} | #{Masq::Engine.config.masq['name']}" : Masq::Engine.config.masq['name']
6 | end
7 |
8 | def label_tag(field, text = nil, options = {})
9 | content_tag :label, text ? text : field.to_s.humanize, options.reverse_merge(:for => field.to_s)
10 | end
11 |
12 | def error_messages_for(*objects)
13 | render "masq/shared/error_messages", :objects => objects.flatten
14 | end
15 |
16 | # Is the current page an identity page? This is used to display
17 | # further information (like the endoint url) in the
18 | def identity_page?
19 | active_page? 'accounts' => ['show']
20 | end
21 |
22 | # Is the current page the home page? This is used to display
23 | # further information (like the endoint url) in the
24 | def home_page?
25 | active_page? 'info' => ['index']
26 | end
27 |
28 | # Custom label names for request properties (like SReg data)
29 | def property_label_text(property)
30 | case property.to_sym
31 | when :image_default then t(:image_url)
32 | when :web_default then t(:website_url)
33 | when :web_blog then t(:blog_url)
34 | else t(property.to_sym)
35 | end
36 | end
37 |
38 | def property_label_text_for_type_uri(type_uri)
39 | property = Persona.attribute_name_for_type_uri(type_uri)
40 | property ? property_label_text(property) : type_uri
41 | end
42 |
43 | # Renders a navigation element and marks it as active where
44 | # appropriate. See active_page? for details
45 | def nav(name, url, pages = nil, active = false)
46 | content_tag :li, link_to(name, url), :class => (active || (pages && active_page?(pages)) ? 'act' : nil)
47 | end
48 |
49 | # Takes a hash with pages and tells whether the current page is among them.
50 | # The keys must be controller names and their value must be an array of
51 | # action names. If the array is empty, every action is supposed to be valid.
52 | def active_page?(pages = {})
53 | is_active = pages.include?(params[:controller])
54 | is_active = pages[params[:controller]].include?(params[:action]) if is_active && !pages[params[:controller]].empty?
55 | is_active
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/test/functional/masq/personas_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class PersonasControllerTest < ActionController::TestCase
5 | include Masq::Engine.routes_url_helpers
6 |
7 | fixtures :accounts, :personas
8 |
9 | def test_should_require_login_for_index
10 | get :index
11 | assert_login_required
12 | end
13 |
14 | def test_should_have_list_of_personas_on_index
15 | login_as(:standard)
16 | get :index
17 | assert_response :success
18 | assert_not_nil assigns(:personas)
19 | end
20 |
21 | def test_should_require_login_for_new
22 | get :new
23 | assert_login_required
24 | end
25 |
26 | def test_should_get_new
27 | login_as(:standard)
28 | get :new
29 | assert_response :success
30 | end
31 |
32 | def test_should_require_login_for_create
33 | post :create, :persona => valid_persona_attributes
34 | assert_login_required
35 | end
36 |
37 | def test_should_create_persona
38 | login_as(:standard)
39 | assert_difference('Persona.count', 1) do
40 | post :create, :persona => valid_persona_attributes
41 | end
42 | assert_redirected_to account_personas_path
43 | end
44 |
45 | def test_should_require_login_for_edit
46 | get :edit, :id => personas(:public).id
47 | assert_login_required
48 | end
49 |
50 | def test_should_get_edit
51 | login_as(:standard)
52 | get :edit, :id => personas(:public).id
53 | assert_response :success
54 | end
55 |
56 | def test_should_require_login_for_update
57 | put :update, :id => personas(:public).id, :persona => valid_persona_attributes
58 | assert_login_required
59 | end
60 |
61 | def test_should_update_persona
62 | login_as(:standard)
63 | put :update, :id => personas(:public).id, :persona => valid_persona_attributes
64 | assert_redirected_to account_personas_path
65 | end
66 |
67 | def test_should_require_login_for_destroy
68 | delete :destroy, :id => personas(:public).id
69 | assert_login_required
70 | end
71 |
72 | def test_should_destroy_persona
73 | login_as(:standard)
74 | assert_difference('Persona.count', -1) do
75 | delete :destroy, :id => personas(:public).id
76 | end
77 | assert_redirected_to account_personas_path
78 | end
79 |
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/test/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require 'rails/all'
4 |
5 | Bundler.require
6 | require "masq"
7 |
8 | module Dummy
9 | class Application < Rails::Application
10 | # Settings in config/environments/* take precedence over those specified here.
11 | # Application configuration should go into files in config/initializers
12 | # -- all .rb files in that directory are automatically loaded.
13 |
14 | # Custom directories with classes and modules you want to be autoloadable.
15 | # config.autoload_paths += %W(#{config.root}/extras)
16 |
17 | # Only load the plugins named here, in the order given (default is alphabetical).
18 | # :all can be used as a placeholder for all plugins not explicitly named.
19 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
20 |
21 | # Activate observers that should always be running.
22 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
23 |
24 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
25 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
26 | # config.time_zone = 'Central Time (US & Canada)'
27 |
28 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
29 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
30 | # config.i18n.default_locale = :de
31 |
32 | # Configure the default encoding used in templates for Ruby 1.9.
33 | config.encoding = "utf-8"
34 |
35 | # Configure sensitive parameters which will be filtered from the log file.
36 | config.filter_parameters += [:password]
37 |
38 | # Use SQL instead of Active Record's schema dumper when creating the database.
39 | # This is necessary if your schema can't be completely dumped by the schema dumper,
40 | # like if you have constraints or database-specific column types
41 | # config.active_record.schema_format = :sql
42 |
43 | # Enforce whitelist mode for mass assignment.
44 | # This will create an empty whitelist of attributes available for mass-assignment for all models
45 | # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
46 | # parameters by using an attr_accessible or attr_protected declaration.
47 | config.active_record.whitelist_attributes = true
48 |
49 | # Enable the asset pipeline
50 | config.assets.enabled = true
51 |
52 | # Version of your assets, change this if you want to expire all your assets
53 | config.assets.version = '1.0'
54 | end
55 | end
56 |
57 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # Code is not reloaded between requests
5 | config.cache_classes = true
6 |
7 | # Full error reports are disabled and caching is turned on
8 | config.consider_all_requests_local = false
9 | config.action_controller.perform_caching = true
10 |
11 | # Disable Rails's static asset server (Apache or nginx will already do this)
12 | config.serve_static_assets = false
13 |
14 | # Compress JavaScripts and CSS
15 | config.assets.compress = true
16 |
17 | # Don't fallback to assets pipeline if a precompiled asset is missed
18 | config.assets.compile = false
19 |
20 | # Generate digests for assets URLs
21 | config.assets.digest = true
22 |
23 | # Defaults to Rails.root.join("public/assets")
24 | # config.assets.manifest = YOUR_PATH
25 |
26 | # Specifies the header that your server uses for sending files
27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
29 |
30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
31 | # config.force_ssl = true
32 |
33 | # See everything in the log (default is :info)
34 | # config.log_level = :debug
35 |
36 | # Prepend all log lines with the following tags
37 | # config.log_tags = [ :subdomain, :uuid ]
38 |
39 | # Use a different logger for distributed setups
40 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
41 |
42 | # Use a different cache store in production
43 | # config.cache_store = :mem_cache_store
44 |
45 | # Enable serving of images, stylesheets, and JavaScripts from an asset server
46 | # config.action_controller.asset_host = "http://assets.example.com"
47 |
48 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
49 | # config.assets.precompile += %w( search.js )
50 |
51 | # Disable delivery errors, bad email addresses will be ignored
52 | # config.action_mailer.raise_delivery_errors = false
53 |
54 | # Enable threaded mode
55 | # config.threadsafe!
56 |
57 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
58 | # the I18n.default_locale when a translation can not be found)
59 | config.i18n.fallbacks = true
60 |
61 | # Send deprecation notices to registered listeners
62 | config.active_support.deprecation = :notify
63 |
64 | # Log the query plan for queries taking more than this (works
65 | # with SQLite, MySQL, and PostgreSQL)
66 | # config.active_record.auto_explain_threshold_in_seconds = 0.5
67 | end
68 |
--------------------------------------------------------------------------------
/app/models/masq/site.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class Site < ActiveRecord::Base
3 | belongs_to :account
4 | belongs_to :persona
5 | has_many :release_policies, :dependent => :destroy
6 |
7 | validates_presence_of :url, :persona, :account
8 | validates_uniqueness_of :url, :scope => :account_id
9 | attr_accessible :url, :persona_id, :properties, :ax_fetch, :sreg
10 |
11 | # Sets the release policies by first deleting the old ones and
12 | # then appending a new one for every given sreg and ax property.
13 | # This setter is used to set the attributes recieved from the
14 | # update site form, so it gets passed AX and SReg properties.
15 | # To be backwards compatible (SReg seems to be obsolete now that
16 | # there is AX), SReg properties get a type_identifier matching
17 | # their property name so that they can be distinguished from AX
18 | # properties (see the sreg_properties and ax_properties getters).
19 | def properties=(props)
20 | release_policies.destroy_all
21 | props.each_pair do |property, details|
22 | release_policies.build(:property => property, :type_identifier => details['type']) if details['value']
23 | end
24 | end
25 |
26 | # Generates a release policy for each property that has a value.
27 | # This setter is used in the server controllers complete action
28 | # to set the attributes recieved from the decision form.
29 | def ax_fetch=(props)
30 | props.each_pair do |property, details|
31 | release_policies.build(:property => property, :type_identifier => details['type']) if details['value']
32 | end
33 | end
34 |
35 | # Generates a release policy for each SReg property.
36 | # This setter is used in the server controllers complete action
37 | # to set the attributes recieved from the decision form.
38 | def sreg=(props)
39 | props.each_key do |property|
40 | release_policies.build(:property => property, :type_identifier => property)
41 | end
42 | end
43 |
44 | # Returns a hash with all released SReg properties. SReg properties
45 | # have a type_identifier matching their property name
46 | def sreg_properties
47 | props = {}
48 | release_policies.each do |rp|
49 | is_sreg = (rp.property == rp.type_identifier)
50 | props[rp.property] = persona.property(rp.property) if is_sreg
51 | end
52 | props
53 | end
54 |
55 | # Returns a hash with all released AX properties.
56 | # AX properties have an URL as type_identifier.
57 | def ax_properties
58 | props = {}
59 | release_policies.each do |rp|
60 | if rp.type_identifier.match("://")
61 | props["type.#{rp.property}"] = rp.type_identifier
62 | props["value.#{rp.property}"] = persona.property(rp.type_identifier )
63 | end
64 | end
65 | props
66 | end
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/test/functional/masq/info_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class InfoControllerTest < ActionController::TestCase
5 | include Masq::Engine.routes_url_helpers
6 |
7 | def test_should_set_yadis_header_on_homepage
8 | get :index
9 | assert_match server_url(:format => :xrds, :host => Masq::Engine.config.masq['host']), @response.headers['X-XRDS-Location']
10 | end
11 |
12 | def test_should_show_registration_link_if_enabled
13 | Masq::Engine.config.masq['disable_registration'] = false
14 | get :index
15 | assert_select "ul#navi li a", {:text => I18n.t(:signup_link), :count => 1}
16 | end
17 |
18 | def test_should_not_show_registration_link_if_disabled
19 | Masq::Engine.config.masq['disable_registration'] = true
20 | get :index
21 | assert_select "ul#navi li a", {:text => I18n.t(:signup_link), :count => 0}
22 | end
23 |
24 | def test_should_not_show_registration_link_on_index_if_disable_registration_is_enabled
25 | Masq::Engine.config.masq['disable_registration'] = false
26 | get :index
27 | text = I18n.t(:openid_intro_link, :signup_link => I18n.t(:signup_for_an_openid))
28 | text = text[3..-5] # cut and
-- ugly :(
29 | assert_select "p:nth-child(3)", {:text => text, :count => 1}
30 | end
31 |
32 | def test_should_show_registration_link_on_index_if_disable_registration_is_disabled
33 | Masq::Engine.config.masq['disable_registration'] = true
34 | get :index
35 | assert_select "p:nth-child(3)", {:text => I18n.t(:openid_intro_link, :signup_link => I18n.t(:signup_for_an_openid)), :count => 0}
36 | end
37 |
38 | def test_should_show_logout_link_after_cookie_login
39 | accounts(:standard).remember_me
40 | @request.cookies["auth_token"] = accounts(:standard).remember_token
41 | get :index
42 | assert @controller.send(:logged_in?)
43 | assert_select "ul#navi li a", {:text => I18n.t(:logout), :count => 1}
44 | end
45 |
46 | def test_should_show_logout_link_after_session_login
47 | login_as :standard
48 | get :index
49 | assert @controller.send(:logged_in?)
50 | assert_select "ul#navi li a", {:text => I18n.t(:logout), :count => 1}
51 | end
52 |
53 | def test_should_not_show_logout_link_after_basic_login
54 | @request.env['HTTP_AUTHORIZATION'] = encode_credentials(accounts(:standard).login, 'test')
55 | get :index
56 | assert @controller.send(:logged_in?)
57 | assert @controller.send(:auth_type_used) == :basic
58 | assert_select "ul#navi li a", {:text => I18n.t(:logout), :count => 0}
59 | end
60 |
61 | def test_phishing_protection_enabled
62 | Masq::Engine.config.masq['protect_phishing'] = true
63 | get :safe_login
64 | assert_select 'a[href=?]', login_path, false
65 | end
66 |
67 | def test_phishing_protection_disabled
68 | Masq::Engine.config.masq['protect_phishing'] = false
69 | get :safe_login
70 | assert_redirected_to login_path
71 | end
72 |
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/app/views/layouts/masq/base.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%=h page_title %>
5 |
6 | <% if identity_page? %>
7 |
8 |
9 | <% elsif home_page? %>
10 |
11 | <% end %>
12 |
13 |
14 |
15 | <%= stylesheet_link_tag 'masq/application' %>
16 | <%= csrf_meta_tags %>
17 |
18 |
19 |
20 |
21 |
<%= link_to Masq::Engine.config.masq['name'], root_path %>
22 |
23 | <% if logged_in? %>
24 | <% unless checkid_request %>
25 | <%= nav t(:my_identity), identity_path(current_account), 'accounts' => ['show'] %>
26 | <%= nav t(:my_profile), edit_account_path, 'accounts' => ['edit', 'update'] %>
27 | <%= nav t(:my_personas), account_personas_path, 'personas' => [] %>
28 | <%= nav t(:my_trusted_sites), account_sites_path, 'sites' => [] %>
29 | <% if not auth_type_used == :basic %>
30 | <%= nav t(:logout), logout_path %>
31 | <% end %>
32 | <% else %>
33 | <%= nav t(:current_verification_request), proceed_path, 'server' => [] %>
34 | <% end %>
35 | <% else %>
36 | <%= nav t(:login_link), login_path, 'sessions' => ['new', 'create'] %>
37 | <% unless Masq::Engine.config.masq['disable_registration'] %>
38 | <%= nav t(:signup_link), new_account_path, 'accounts' => ['new', 'create'] %>
39 | <% end %>
40 | <%= nav t(:help), help_path, 'info' => ['help'] %>
41 | <% end %>
42 |
43 |
44 |
45 |
46 |
47 | <% if flash[:notice] %>
<%=simple_format h(flash[:notice]) %>
<% end %>
48 | <% if flash[:alert] %>
49 |
50 | <%=simple_format h(flash[:alert]) %>
51 |
52 | <% unless params[:resend_activation_for].blank? -%>
53 | <%= button_to t(:resend_activation_email), resend_activation_email_path(:account => params[:resend_activation_for]) -%>
54 | <%- end %>
55 |
56 | <% end %>
57 | <%= yield %>
58 |
59 |
60 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | masq (0.3.2)
5 | i18n_data
6 | rails (~> 3.2.16)
7 | ruby-openid (~> 2.5.0)
8 | ruby-yadis
9 | yubikey
10 |
11 | GEM
12 | remote: http://rubygems.org/
13 | specs:
14 | actionmailer (3.2.16)
15 | actionpack (= 3.2.16)
16 | mail (~> 2.5.4)
17 | actionpack (3.2.16)
18 | activemodel (= 3.2.16)
19 | activesupport (= 3.2.16)
20 | builder (~> 3.0.0)
21 | erubis (~> 2.7.0)
22 | journey (~> 1.0.4)
23 | rack (~> 1.4.5)
24 | rack-cache (~> 1.2)
25 | rack-test (~> 0.6.1)
26 | sprockets (~> 2.2.1)
27 | activemodel (3.2.16)
28 | activesupport (= 3.2.16)
29 | builder (~> 3.0.0)
30 | activerecord (3.2.16)
31 | activemodel (= 3.2.16)
32 | activesupport (= 3.2.16)
33 | arel (~> 3.0.2)
34 | tzinfo (~> 0.3.29)
35 | activeresource (3.2.16)
36 | activemodel (= 3.2.16)
37 | activesupport (= 3.2.16)
38 | activesupport (3.2.16)
39 | i18n (~> 0.6, >= 0.6.4)
40 | multi_json (~> 1.0)
41 | ansi (1.4.3)
42 | arel (3.0.3)
43 | builder (3.0.4)
44 | coderay (1.0.9)
45 | erubis (2.7.0)
46 | formatador (0.2.4)
47 | guard (1.7.0)
48 | formatador (>= 0.2.4)
49 | listen (>= 0.6.0)
50 | lumberjack (>= 1.0.2)
51 | pry (>= 0.9.10)
52 | thor (>= 0.14.6)
53 | guard-minitest (0.5.0)
54 | guard (>= 0.4)
55 | hike (1.2.3)
56 | i18n (0.6.9)
57 | i18n_data (0.4.0)
58 | journey (1.0.4)
59 | json (1.8.1)
60 | listen (0.7.3)
61 | lumberjack (1.0.3)
62 | mail (2.5.4)
63 | mime-types (~> 1.16)
64 | treetop (~> 1.4.8)
65 | metaclass (0.0.1)
66 | method_source (0.8.1)
67 | mime-types (1.25.1)
68 | minitest (4.7.0)
69 | mocha (0.13.3)
70 | metaclass (~> 0.0.1)
71 | multi_json (1.8.4)
72 | mysql2 (0.3.11)
73 | pg (0.16.0)
74 | polyglot (0.3.3)
75 | pry (0.9.12)
76 | coderay (~> 1.0.5)
77 | method_source (~> 0.8)
78 | slop (~> 3.4)
79 | rack (1.4.5)
80 | rack-cache (1.2)
81 | rack (>= 0.4)
82 | rack-ssl (1.3.3)
83 | rack
84 | rack-test (0.6.2)
85 | rack (>= 1.0)
86 | rails (3.2.16)
87 | actionmailer (= 3.2.16)
88 | actionpack (= 3.2.16)
89 | activerecord (= 3.2.16)
90 | activeresource (= 3.2.16)
91 | activesupport (= 3.2.16)
92 | bundler (~> 1.0)
93 | railties (= 3.2.16)
94 | railties (3.2.16)
95 | actionpack (= 3.2.16)
96 | activesupport (= 3.2.16)
97 | rack-ssl (~> 1.3.2)
98 | rake (>= 0.8.7)
99 | rdoc (~> 3.4)
100 | thor (>= 0.14.6, < 2.0)
101 | rake (10.1.1)
102 | rb-fsevent (0.9.3)
103 | rdoc (3.12.2)
104 | json (~> 1.4)
105 | ruby-openid (2.5.0)
106 | ruby-yadis (0.3.4)
107 | ruby_gntp (0.3.4)
108 | slop (3.4.4)
109 | sprockets (2.2.2)
110 | hike (~> 1.2)
111 | multi_json (~> 1.0)
112 | rack (~> 1.0)
113 | tilt (~> 1.1, != 1.3.0)
114 | sqlite3 (1.3.7)
115 | thor (0.18.1)
116 | tilt (1.4.1)
117 | treetop (1.4.15)
118 | polyglot
119 | polyglot (>= 0.3.1)
120 | turn (0.9.6)
121 | ansi
122 | tzinfo (0.3.38)
123 | yubikey (1.3.1)
124 |
125 | PLATFORMS
126 | ruby
127 |
128 | DEPENDENCIES
129 | guard-minitest
130 | masq!
131 | minitest
132 | mocha
133 | mysql2
134 | pg
135 | rb-fsevent
136 | ruby_gntp
137 | sqlite3
138 | turn
139 |
--------------------------------------------------------------------------------
/app/views/masq/accounts/edit.html.erb:
--------------------------------------------------------------------------------
1 | <%=t :my_profile %>
2 |
3 | <%= error_messages_for current_account %>
4 |
5 | <%= form_for current_account, :url => account_path, :html => { :method => :put } do |f| %>
6 | <% unless email_as_login? -%>
7 |
8 | <%= f.label :email, t(:email) %>
9 | <%= f.text_field :email %>
10 |
11 | <% end -%>
12 |
13 |
14 | <%= f.label :public_persona_id, t(:public_persona) %>
15 | <%= f.select :public_persona_id, current_account.personas.map{ |p| [ p.title, p.id ]}, :include_blank => true %>
16 |
17 |
18 |
19 | <%= f.submit t(:submit_update) %>
20 |
21 | <% end %>
22 |
23 | <% if Masq::Engine.config.masq['can_change_password'] %>
24 | <%=t :my_password %>
25 | <%= form_tag change_password_account_path, :method => :put do %>
26 |
27 | <%= label_tag :old_password, t(:old_password) %>
28 | <%= password_field_tag :old_password %>
29 |
30 |
31 | <%= label_tag :password, t(:new_password_minimum_6_characters).html_safe %>
32 | <%= password_field_tag :password %>
33 |
34 |
35 | <%= label_tag :password_confirmation, t(:password_confirmation) %>
36 | <%= password_field_tag :password_confirmation %>
37 |
38 |
39 | <%= submit_tag t(:submit_update) %>
40 |
41 | <% end %>
42 | <% end %>
43 |
44 | <% if Masq::Engine.config.masq['can_use_yubikey'] %>
45 | <%=t :my_yubikey %>
46 | <% if current_account.yubico_identity? %>
47 | <%= form_tag account_yubikey_association_path, :method => :delete do %>
48 |
49 |
<%=t :your_account_is_associated_with_the_yubico_identity %> <%= current_account.yubico_identity %>
50 |
<%=t :yubikey_how_to_use %>
51 |
52 |
53 | <%= submit_tag t(:remove_association) %>
54 |
55 | <% end %>
56 |
57 | <%= form_for :account, :url => account_path, :html => { :method => :put } do |f| %>
58 |
59 |
60 | <% if current_account.yubikey_mandatory? %>
61 | <%=t :your_yubikey_is_mandatory_for_login %>
62 | <% else %>
63 | <%=t :your_yubikey_is_optional_for_login %>
64 | <% end %>
65 |
66 |
67 | <%= f.hidden_field :yubikey_mandatory, :value => (current_account.yubikey_mandatory ? 0 : 1) %>
68 | <%= submit_tag( current_account.yubikey_mandatory ? t(:make_my_yubikey_optional) : t(:make_my_yubikey_mandatory) ) %>
69 |
70 |
71 | <% end %>
72 |
73 | <% else %>
74 | <%= form_tag account_yubikey_association_path do %>
75 |
76 | <%= label_tag :yubico_otp, t(:your_yubikey_a_one_time_password).html_safe %>
77 | <%= password_field_tag :yubico_otp %>
78 |
79 |
80 | <%= submit_tag t(:associate_account_with_yubikey) %>
81 |
82 | <% end %>
83 | <% end %>
84 | <% end %>
85 |
86 | <% if Masq::Engine.config.masq['can_disable_account'] %>
87 | <%=t :disable_my_account %>
88 |
89 | <%= form_tag account_path, :method => :delete do %>
90 | <%=t :wont_be_possible_to_reclaim_identifier %> <%= identifier(current_account) %>
91 |
92 | <%= label_tag :confirmation_password, t(:confirm_by_entering_password) %>
93 | <%= password_field_tag :confirmation_password, '' %>
94 |
95 |
96 | <%= submit_tag t(:delete_my_account_and_data) %>
97 |
98 | <% end %>
99 | <% end %>
100 |
101 |
--------------------------------------------------------------------------------
/test/functional/masq/passwords_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class PasswordsControllerTest < ActionController::TestCase
5 | include Masq::Engine.routes_url_helpers
6 |
7 | fixtures :accounts
8 |
9 | def setup
10 | Masq::Engine.config.masq['can_change_password'] = true
11 | end
12 |
13 | def test_should_get_new
14 | get :new
15 | assert_response :success
16 | end
17 |
18 | def test_should_display_error_when_email_could_not_be_found
19 | post :create, :email => 'doesnotexist@somewhere.com'
20 | assert flash[:alert]
21 | assert_template 'new'
22 | end
23 |
24 | def test_should_reset_password_when_email_could_be_found
25 | @account = accounts(:standard)
26 | post :create, :email => @account.email
27 | assert_not_nil @account.reload.password_reset_code
28 | assert_redirected_to login_path
29 | assert flash[:notice]
30 | end
31 |
32 | def test_should_return404_on_reset_password_when_can_change_password_is_disabled
33 | Masq::Engine.config.masq['can_change_password'] = false
34 | @account = accounts(:standard)
35 | post :create, :email => @account.email
36 | assert_nil @account.reload.password_reset_code
37 | assert_response :not_found
38 | end
39 |
40 | def test_should_redirect_to_new_if_code_is_missing
41 | get :edit
42 | assert_redirected_to forgot_password_path
43 | assert flash[:alert]
44 | end
45 |
46 | def test_should_redirect_to_new_if_code_is_invalid
47 | get :edit, :id => 'doesnotexist'
48 | assert_redirected_to forgot_password_path
49 | assert flash[:alert]
50 | end
51 |
52 | def test_should_reset_the_password_when_it_matches_confirmation
53 | @account = accounts(:standard)
54 | old_crypted_password = @account.crypted_password
55 | @account.forgot_password!
56 | put :update, :id => @account.password_reset_code,
57 | :password => 'v4l1d_n3w_pa$$w0rD',
58 | :password_confirmation => 'v4l1d_n3w_pa$$w0rD'
59 | assert_not_equal old_crypted_password, @account.reload.crypted_password
60 | assert_redirected_to login_path
61 | assert flash[:notice]
62 | end
63 |
64 | def test_should_not_reset_the_password_when_can_change_password_is_disabled
65 | Masq::Engine.config.masq['can_change_password'] = false
66 | @account = accounts(:standard)
67 | old_crypted_password = @account.crypted_password
68 | @account.forgot_password!
69 | put :update, :id => @account.password_reset_code,
70 | :password => 'v4l1d_n3w_pa$$w0rD',
71 | :password_confirmation => 'v4l1d_n3w_pa$$w0rD'
72 | assert_equal old_crypted_password, @account.reload.crypted_password
73 | assert_response :not_found
74 | end
75 |
76 | def test_should_not_reset_the_password_if_it_is_blank
77 | @account = accounts(:standard)
78 | old_crypted_password = @account.crypted_password
79 | @account.forgot_password!
80 | new_password = ''
81 | put :update, :id => @account.password_reset_code,
82 | :password => new_password,
83 | :password_confirmation => new_password
84 | assert_equal old_crypted_password, @account.reload.crypted_password
85 | assert flash[:alert]
86 | assert_template 'edit'
87 | end
88 |
89 | def test_should_not_reset_the_password_if_it_does_not_match_confirmation
90 | @account = accounts(:standard)
91 | old_crypted_password = @account.crypted_password
92 | @account.forgot_password!
93 | put :update, :id => @account.password_reset_code,
94 | :password => 'v4l1d_n3w_pa$$w0rD',
95 | :password_confirmation => 'other_password'
96 | assert_equal old_crypted_password, @account.reload.crypted_password
97 | assert flash[:alert]
98 | assert_template 'edit'
99 | end
100 | end
101 | end
102 |
--------------------------------------------------------------------------------
/config/masq.example.yml:
--------------------------------------------------------------------------------
1 | ---
2 | default: &default
3 | send_activation_mail: true
4 | trust_basic_auth: false
5 | disable_registration: false
6 | can_change_password: true
7 | can_disable_account: true
8 | can_use_yubikey: true
9 | create_auth_ondemand:
10 | enabled: false
11 | default_mail_domain: example.com
12 | random_password: true
13 | protect_phishing: true
14 | name: masq
15 | host: localhost:3000
16 | email: info@your.domain.com
17 | use_ssl: false
18 | email_as_login: false
19 | yubico:
20 | id: 99
21 | api_key: youryubicoapikey
22 | attribute_mappings:
23 | nickname:
24 | - nickname
25 | - http://axschema.org/namePerson/friendly
26 | email:
27 | - email
28 | - http://axschema.org/contact/email
29 | fullname:
30 | - fullname
31 | - http://axschema.org/namePerson
32 | postcode:
33 | - postcode
34 | - http://axschema.org/contact/postalCode/home
35 | country:
36 | - country
37 | - http://axschema.org/contact/country/home
38 | language:
39 | - language
40 | - http://axschema.org/pref/language
41 | timezone:
42 | - timezone
43 | - http://axschema.org/pref/timezone
44 | gender:
45 | - gender
46 | - http://axschema.org/person/gender
47 | date_of_birth:
48 | - dob
49 | - http://axschema.org/birthDate
50 | dob_day:
51 | - dob_day
52 | - http://axschema.org/birthDate/birthday
53 | dob_month:
54 | - dob_month
55 | - http://axschema.org/birthDate/birthMonth
56 | dob_year:
57 | - dob_year
58 | - http://axschema.org/birthDate/birthYear
59 | address:
60 | - http://axschema.org/contact/postalAddress/home
61 | address_additional:
62 | - http://axschema.org/contact/postalAddressAdditional/home
63 | city:
64 | - http://axschema.org/contact/city/home
65 | state:
66 | - http://axschema.org/contact/state/home
67 | company_name:
68 | - http://axschema.org/company/name
69 | job_title:
70 | - http://axschema.org/company/title
71 | address_business:
72 | - http://axschema.org/contact/postalAddress/business
73 | address_additional_business:
74 | - http://axschema.org/contact/postalAddressAdditional/business
75 | postcode_business:
76 | - http://axschema.org/contact/postalCode/business
77 | city_business:
78 | - http://axschema.org/contact/city/business
79 | state_business:
80 | - http://axschema.org/contact/state/business
81 | country_business:
82 | - http://axschema.org/contact/country/business
83 | phone_home:
84 | - http://axschema.org/contact/phone/home
85 | phone_mobile:
86 | - http://axschema.org/contact/phone/cell
87 | phone_work:
88 | - http://axschema.org/contact/phone/business
89 | phone_fax:
90 | - http://axschema.org/contact/phone/fax
91 | im_aim:
92 | - http://axschema.org/contact/IM/AIM
93 | im_icq:
94 | - http://axschema.org/contact/IM/ICQ
95 | im_msn:
96 | - http://axschema.org/contact/IM/MSN
97 | im_yahoo:
98 | - http://axschema.org/contact/IM/Yahoo
99 | im_jabber:
100 | - http://axschema.org/contact/IM/Jabber
101 | im_skype:
102 | - http://axschema.org/contact/IM/Skype
103 | image_default:
104 | - http://axschema.org/media/image/default
105 | biography:
106 | - http://axschema.org/media/biography
107 | web_default:
108 | - http://axschema.org/contact/web/default
109 | web_blog:
110 | - http://axschema.org/contact/web/blog
111 | trusted_domains:
112 |
113 | development:
114 | <<: *default
115 |
116 | test:
117 | <<: *default
118 | trusted_domains:
119 | - trusted-domain.com
120 |
121 | production:
122 | <<: *default
123 | use_ssl: true
124 | ssl_certificate_common_name: your.domain.com
125 | ssl_certificate_sha1: D2:1B:D8:C4:39:B7:EE:10:DA:E2:4E:0A:65:98:8E:27:C9:32:4B:F0
126 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/masq/application.css.erb:
--------------------------------------------------------------------------------
1 | * { margin: 0; padding: 0; }
2 | body { font: normal 100% Helvetica, Arial, sans-serif; line-height: 1.7em; color: #222; }
3 | .wrap { width: 44em; }
4 | #head { position: relative; height: 4em; padding: 0 3em; background: #222; color: #FFF; }
5 | #head h1 { position: absolute; bottom: 0.2em; font-size: 2em; font-weight: normal; letter-spacing: -0.05em; }
6 | #head h1 a { color: #FFF; }
7 | #navi { position: absolute; bottom: 0.1em; left: 15em; width: 32em; list-style: none; margin: 0; }
8 | #navi li { float: left; margin-right: 0.4em; }
9 | #navi a { color: #FFF; padding: 0.6em 0.5em 0.5em 0.5em; }
10 | #navi a:focus,
11 | #navi a:hover { color: #FF6200; }
12 | #navi li.act a { color: #222; background: #FFF; border-top: 2px solid #FF6200; }
13 | #main { padding: 2em 3em; background: #FFF; color: #000; }
14 | #main li { margin: 0; border-bottom: 1px dotted #CCC; padding: 0.5em 0; }
15 | #main ul { list-style: none; }
16 | #foot { padding: 0.3em 3em 1em 3em; text-align: right; }
17 | #foot a { text-decoration: none; }
18 | h2 { margin: 0.2em 0 0.5em 0; font-size: 1.4em; font-weight: bold; color: #FF6200; }
19 | h3 { margin: 0; font-size: 1.1em; font-weight: bold; }
20 | h3 .note { margin-left: 0.3em; }
21 | form h3 { margin: 0 0 0.5em 0; }
22 | p { margin-bottom: 0.7em; }
23 | ul { margin: 0 0 1.7em 0; }
24 | a { color: #FF6200; text-decoration: none; }
25 | img { border: 0; }
26 | form { margin: 0 0 1.7em 0; }
27 | small { font-size: 0.8em; font-weight: normal; }
28 | table { margin: 0 0 1.7em 0; }
29 | th { font-weight: normal; padding: 0.2em 2em 0.2em 0; text-align: left; color: #AAA; }
30 | td { padding: 0.2em 2em 0.2em 0; }
31 | form { margin: 1em 0 1.7em 0; border: 1px dotted #CCC; border-top: 2px solid #AAA; padding: 1em; }
32 | input { padding: 0.2em; }
33 | select { width: 24em; }
34 | input[type=text],
35 | input[type=password] { width: 24em; }
36 | input[type=checkbox],
37 | input[type=radio] { position: relative; top: -0.2em; }
38 | input.space { margin-right: 0.7em; }
39 | select#persona_dob_day { width: 6em; }
40 | select#persona_dob_month { width: 11em; }
41 | select#persona_dob_year { width: 6em; }
42 | div.row { margin: 0 0 0.7em 0; clear: both; }
43 | div.space { margin: 0 0 1.7em 0; }
44 | label { display: block; }
45 | label.check { display: inline; margin: 0 2em 0 0.2em; }
46 | label.note { display: inline; margin: 0 0 0 0.4em; }
47 | div.inline label { float: left; width: 8.5em; }
48 | .labelspace { margin-left: 10.5em; }
49 | .options a { margin-right: 0.7em; }
50 | .note { font-size: 0.9em; color: #AAA; font-weight: normal; }
51 | .note a,
52 | a#forgot_password { color: #AAA; margin: 0 0.3em; text-decoration: underline; font-weight: normal; }
53 | #openid_identifier { background: url(<%= image_path('masq/openid_symbol.png') %>) 2px 40% no-repeat; padding-left: 25px; }
54 | .clear { clear: both; }
55 | .notice { margin: 0 0 1.7em 0; border: 1px solid #60964f; padding: 0.5em; background: #b3dca7; }
56 | .fieldWithErrors label { color: #FF6200; }
57 | .error,
58 | #errorExplanation { margin: 0 0 1.7em 0; padding: 0.5em 1em; background: #FF6200; color: #222; }
59 | #errorExplanation h2 { font-size: 1.1em; color: #222; }
60 | #errorExplanation ul { margin: 0 0 0 1.2em; list-style: square; }
61 | #errorExplanation ul li { padding: 0; border: 0; }
--------------------------------------------------------------------------------
/test/dummy/config/masq.yml:
--------------------------------------------------------------------------------
1 | ---
2 | default: &default
3 | send_activation_mail: true
4 | trust_basic_auth: false
5 | disable_registration: false
6 | can_change_password: true
7 | can_disable_account: true
8 | can_use_yubikey: true
9 | create_auth_ondemand:
10 | enabled: false
11 | default_mail_domain: example.com
12 | random_password: true
13 | name: masq
14 | host: localhost:3000
15 | email: info@your.domain.com
16 | use_ssl: false
17 | email_as_login: false
18 | session:
19 | key: _masq_session
20 | secret: a5c8d013da6da10ade0465aa2d62241a710a9b52a723cbc153d949a10f4805152b1a95566c17c865f3c68ed00de6271b2221a63928dd4ce0f94bb6e83a4bcf17
21 | mailer:
22 | address: localhost
23 | domain: your.domain.com
24 | from: info@your.domain.com
25 | yubico:
26 | id: 99
27 | api_key: yourapikey
28 | attribute_mappings:
29 | not_supported:
30 | - http://openid.tzi.de/spec/schema/uid
31 | nickname:
32 | - nickname
33 | - http://axschema.org/namePerson/friendly
34 | email:
35 | - email
36 | - http://axschema.org/contact/email
37 | fullname:
38 | - fullname
39 | - http://axschema.org/namePerson
40 | postcode:
41 | - postcode
42 | - http://axschema.org/contact/postalCode/home
43 | country:
44 | - country
45 | - http://axschema.org/contact/country/home
46 | language:
47 | - language
48 | - http://axschema.org/pref/language
49 | timezone:
50 | - timezone
51 | - http://axschema.org/pref/timezone
52 | gender:
53 | - gender
54 | - http://axschema.org/person/gender
55 | date_of_birth:
56 | - dob
57 | - http://axschema.org/birthDate
58 | dob_day:
59 | - dob_day
60 | - http://axschema.org/birthDate/birthday
61 | dob_month:
62 | - dob_month
63 | - http://axschema.org/birthDate/birthMonth
64 | dob_year:
65 | - dob_year
66 | - http://axschema.org/birthDate/birthYear
67 | address:
68 | - http://axschema.org/contact/postalAddress/home
69 | address_additional:
70 | - http://axschema.org/contact/postalAddressAdditional/home
71 | city:
72 | - http://axschema.org/contact/city/home
73 | state:
74 | - http://axschema.org/contact/state/home
75 | company_name:
76 | - http://axschema.org/company/name
77 | job_title:
78 | - http://axschema.org/company/title
79 | address_business:
80 | - http://axschema.org/contact/postalAddress/business
81 | address_additional_business:
82 | - http://axschema.org/contact/postalAddressAdditional/business
83 | postcode_business:
84 | - http://axschema.org/contact/postalCode/business
85 | city_business:
86 | - http://axschema.org/contact/city/business
87 | state_business:
88 | - http://axschema.org/contact/state/business
89 | country_business:
90 | - http://axschema.org/contact/country/business
91 | phone_home:
92 | - http://axschema.org/contact/phone/home
93 | phone_mobile:
94 | - http://axschema.org/contact/phone/cell
95 | phone_work:
96 | - http://axschema.org/contact/phone/business
97 | phone_fax:
98 | - http://axschema.org/contact/phone/fax
99 | im_aim:
100 | - http://axschema.org/contact/IM/AIM
101 | im_icq:
102 | - http://axschema.org/contact/IM/ICQ
103 | im_msn:
104 | - http://axschema.org/contact/IM/MSN
105 | im_yahoo:
106 | - http://axschema.org/contact/IM/Yahoo
107 | im_jabber:
108 | - http://axschema.org/contact/IM/Jabber
109 | im_skype:
110 | - http://axschema.org/contact/IM/Skype
111 | image_default:
112 | - http://axschema.org/media/image/default
113 | biography:
114 | - http://axschema.org/media/biography
115 | web_default:
116 | - http://axschema.org/contact/web/default
117 | web_blog:
118 | - http://axschema.org/contact/web/blog
119 | trusted_domains:
120 | - trusted-domain.com
121 |
122 | development:
123 | <<: *default
124 |
125 | test:
126 | <<: *default
127 | host: "test.host"
128 |
129 | production:
130 | <<: *default
131 | host: "myrealhost.com"
132 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Masq OpenID Server
2 |
3 | [](http://badge.fury.io/rb/masq)
4 | [](http://travis-ci.org/dennisreimann/masq)
5 |
6 | Masq is a mountable Rails engine that provides OpenID server/identity provider functionality.
7 | It is the successor of the stand-alone Rails application [masquerade](http://github.com/dennisreimann/masquerade/).
8 |
9 | The project is released under the MIT-License and its source code is available at [GitHub](http://github.com/dennisreimann/masquerade/).
10 | Feel free to fork and submit patches :)
11 |
12 | ## Installation
13 |
14 | _If you want to upgrade from masquerade, please skip this part and see the Upgrading section below_
15 |
16 | 0. In case you want to run masq as a standalone application (not integrated into an existing app), you will have to generate a barebone Rails app first:
17 | * `rails new my_openid_provider`
18 |
19 | 1. Add masq to your Gemfile and install it:
20 | * `gem 'masq'`
21 | * `bundle install`
22 |
23 | 2. Copy the configuration and edit it:
24 | * `bundle exec rake masq:install:config`
25 | * `$EDITOR config/masq.yml`
26 |
27 | 3. Copy the migrations and migrate:
28 | * `bundle exec rake masq:install:migrations`
29 | * `bundle exec rake db:migrate`
30 |
31 | 4. Configure the routes by mounting the masq engine:
32 | * For integration into an existing app, mount it in a subdirectory, like:
33 | * `mount Masq::Engine => "/masq"` or
34 | * `mount Masq::Engine => "/openid"`
35 | * Standalone installation, mount it at the root:
36 | * `mount Masq::Engine => "/"`
37 |
38 | ## Upgrading from masquerade
39 |
40 | 1. Generate a barebone Rails app:
41 | * `rails new my_openid_provider`
42 |
43 | 2. Add masq to your Gemfile and install it:
44 | * `gem 'masq'`
45 | * `bundle install`
46 |
47 | 3. Copy your existing masquerade config file from `config/app_config.yml` to the new apps `config/masq.yml`
48 |
49 | 4. Copy the migrations and migrate:
50 | * PLEASE BACKUP YOUR DATABASE FIRST!
51 | * `bundle exec rake masq:install:migrations`
52 | * `bundle exec rake db:migrate`
53 |
54 | 5. Configure the routes by mounting the masq engine:
55 |
56 | Rails.application.routes.draw do
57 | mount Masq::Engine => "/"
58 | end
59 |
60 | ## Testing the installation
61 |
62 | You can test the functionality in your local environment starting two instances: One as
63 | your Identity Provider/OpenID Server and another one as Relying Party.
64 |
65 | * `rails server`
66 | * `rails server -p 3001`
67 |
68 | Open your browser with these urls (assumes you mounted the engine at */masq*):
69 |
70 | * [http://localhost:3000/masq](http://localhost:3000/masq) (Identity Provider)
71 | * [http://localhost:3001/masq/consumer](http://localhost:3001/masq/consumer) (Relying Party testsuite)
72 |
73 | First you have to create an account at the Identity Provider, after that you will be able
74 | to use the issued OpenID URL (`http://localhost:3000/masq/YOUR_LOGIN`) to send requests from the
75 | Relying Party to the server.
76 |
77 | Use the options provided by the OpenID verification form to test several aspects of the
78 | client-server communication (like requesting simple registration data).
79 |
80 | ## Development
81 |
82 | ### Introduction
83 |
84 | The main functionality is in the server controller, which is the endpoint for incoming
85 | OpenID requests. The server controller is supposed to only interact with relying parties
86 | a.k.a. consumer websites. It includes the OpenidServerSystem module, which provides some
87 | handy methods to access and answer OpenID requests.
88 |
89 | ### Testing
90 |
91 | You can run the tests with Rake:
92 | * `DB_ADAPTER=sqlite3 bundle exec rake app:masq:test:ci`
93 | * `DB_ADAPTER=mysql2 bundle exec rake app:masq:test:ci`
94 | * `DB_ADAPTER=postgresql bundle exec rake app:masq:test:ci`
95 |
96 | The Rake task configures the database.yml for the chosen adapter.
97 |
98 | In case you prefer running the tests continuously, use Guard:
99 | * `bundle exec guard`
100 |
101 | ## Contact
102 |
103 | Dennis Reimann: [mail@dennisreimann.de](mailto:mail@dennisreimann.de)
104 |
--------------------------------------------------------------------------------
/lib/masq/openid_server_system.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | # This module is mainly a wrapper for the OpenID::Server functionality provided
3 | # by the ruby-openid gem. Included in your server controller it gives you some
4 | # helpful methods to access and answer OpenID requests.
5 | module OpenidServerSystem
6 | protected
7 |
8 | # OpenID store reader, used inside this module
9 | # to procide access to the storage machanism
10 | def openid_store
11 | @openid_store ||= ActiveRecordStore.new
12 | end
13 |
14 | # OpenID server reader, use this to access the server
15 | # functionality from inside your server controller
16 | def openid_server
17 | @openid_server ||= OpenID::Server::Server.new(openid_store, endpoint_url)
18 | end
19 |
20 | # OpenID parameter reader, use this to access only OpenID
21 | # request parameters from inside your server controller
22 | def openid_params
23 | @openid_params ||= params.clone.delete_if { |k,v| k.index('openid.') != 0 }
24 | end
25 |
26 | # OpenID request accessor
27 | def openid_request
28 | @openid_request ||= openid_server.decode_request(openid_params)
29 | end
30 |
31 | # Sets the current OpenID request and resets all dependent requests
32 | def openid_request=(req)
33 | @openid_request, @sreg_request, @ax_fetch_request, @ax_store_request = req, nil, nil, nil
34 | end
35 |
36 | # SReg request reader
37 | def sreg_request
38 | @sreg_request ||= OpenID::SReg::Request.from_openid_request(openid_request)
39 | end
40 |
41 | # Attribute Exchange fetch request reader
42 | def ax_fetch_request
43 | @ax_fetch_request ||= OpenID::AX::FetchRequest.from_openid_request(openid_request)
44 | end
45 |
46 | # Attribute Exchange store request reader
47 | def ax_store_request
48 | @ax_store_request ||= OpenID::AX::StoreRequest.from_openid_request(openid_request)
49 | end
50 |
51 | # PAPE request reader
52 | def pape_request
53 | @pape_request ||= OpenID::PAPE::Request.from_openid_request(openid_request)
54 | end
55 |
56 | # Adds SReg data (Hash) to an OpenID response.
57 | def add_sreg(resp, data)
58 | sreg_resp = OpenID::SReg::Response.extract_response(sreg_request, data)
59 | resp.add_extension(sreg_resp)
60 | resp
61 | end
62 |
63 | # Adds Attribute Exchange data (Hash) to an OpenID response. See:
64 | # http://rakuto.blogspot.com/2008/03/ruby-fetch-and-store-some-attributes.html
65 | def add_ax(resp, data)
66 | ax_resp = OpenID::AX::FetchResponse.new
67 | ax_args = data.reverse_merge('mode' => 'fetch_response')
68 | ax_resp.parse_extension_args(ax_args)
69 | resp.add_extension(ax_resp)
70 | resp
71 | end
72 |
73 | # Adds PAPE information for your server to an OpenID response.
74 | def add_pape(resp, policies = [], nist_auth_level = 0, auth_time = nil)
75 | if papereq = OpenID::PAPE::Request.from_openid_request(openid_request)
76 | paperesp = OpenID::PAPE::Response.new
77 | policies.each { |p| paperesp.add_policy_uri(p) }
78 | paperesp.nist_auth_level = nist_auth_level
79 | paperesp.auth_time = auth_time.utc.iso8601
80 | resp.add_extension(paperesp)
81 | end
82 | resp
83 | end
84 |
85 | # Answers check auth and associate requests.
86 | def handle_non_checkid_request
87 | resp = openid_server.handle_request(openid_request)
88 | render_openid_response(resp)
89 | end
90 |
91 | # Renders the final response output
92 | def render_openid_response(resp)
93 | signed_response = openid_server.signatory.sign(resp) if resp.needs_signing
94 | web_response = openid_server.encode_response(resp)
95 | case web_response.code
96 | when OpenID::Server::HTTP_OK then render(:text => web_response.body, :status => 200)
97 | when OpenID::Server::HTTP_REDIRECT then redirect_to(web_response.headers['location'])
98 | else render(:text => web_response.body, :status => 400)
99 | end
100 | end
101 |
102 | # If the request contains a max_auth_age, the last authentication date
103 | # must meet this requirement, otherwise the user has to reauthenticate:
104 | # http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-02.html#anchor9
105 | def pape_requirements_met?(auth_time)
106 | return true unless pape_request && pape_request.max_auth_age
107 | (Time.now - auth_time).to_i <= pape_request.max_auth_age
108 | end
109 | end
110 | end
111 |
--------------------------------------------------------------------------------
/test/functional/masq/server_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class ServerControllerTest < ActionController::TestCase
5 | include Masq::Engine.routes_url_helpers
6 |
7 | fixtures :accounts, :personas
8 |
9 | def test_should_redirect_to_safe_login_page_if_untrusted_domain
10 | login_as(:standard)
11 | post :index, checkid_request_params
12 | assert_redirected_to safe_login_path
13 | assert_not_nil request.session[:return_to]
14 | assert_not_nil request.session[:request_token]
15 | end
16 |
17 | def test_should_redirect_to_login_page_if_trusted_domain
18 | login_as(:standard)
19 | domain = Masq::Engine.config.masq['trusted_domains'].first
20 | post :index, checkid_request_params.merge('openid.trust_root' => "http://#{domain}/", 'openid.realm' => "http://#{domain}/", 'openid.return_to' => "http://#{domain}/return")
21 | assert_redirected_to login_path
22 | assert_not_nil request.session[:return_to]
23 | assert_not_nil request.session[:request_token]
24 | end
25 |
26 | def test_should_save_site_if_user_chose_to_trust_always
27 | fake_checkid_request(:standard)
28 | assert_difference('Site.count', 1) do
29 | post :complete, :always => 1,
30 | :site => {
31 | :persona_id => personas(:public).id,
32 | :url => checkid_request_params['openid.trust_root'],
33 | :properties => valid_properties }
34 | end
35 | assert_response :redirect
36 | assert_match(checkid_request_params['openid.return_to'], response.redirect_url)
37 | assert_match(/mode=id_res/, response.redirect_url)
38 | end
39 |
40 | def test_should_not_save_site_if_user_chose_to_trust_temporary
41 | fake_checkid_request(:standard)
42 | assert_no_difference('Site.count') do
43 | post :complete, :temporary => 1,
44 | :site => valid_site_attributes.merge(:properties => valid_properties)
45 | end
46 | assert_response :redirect
47 | assert_match checkid_request_params['openid.return_to'], response.redirect_url
48 | assert_match /mode=id_res/, response.redirect_url
49 | end
50 |
51 | def test_should_redirect_to_openid_cancel_url_if_user_chose_to_cancel
52 | fake_checkid_request(:standard)
53 | post :complete, :cancel => 1
54 | assert_response :redirect
55 | assert_match(checkid_request_params['openid.return_to'], response.redirect_url)
56 | assert_match(/mode=cancel/, response.redirect_url)
57 | end
58 |
59 | def test_should_ask_user_to_login_if_claimed_id_does_not_belong_to_current_account
60 | login_as(:standard)
61 | id_url = "http://notmine.com"
62 | post :index, checkid_request_params.merge('openid.identity' => id_url, 'openid.claimed_id' => id_url)
63 | assert_redirected_to safe_login_path
64 | assert_not_nil request.session[:return_to]
65 | assert_not_nil request.session[:request_token]
66 | end
67 |
68 | def test_should_clear_old_request_when_recieving_a_new_one
69 | fake_checkid_request(:standard)
70 | token_for_first_request = request.session[:request_token]
71 | assert token_for_first_request
72 | post :index
73 | assert_not_equal request.session[:request_token], token_for_first_request
74 | assert_nil OpenIdRequest.find_by_token(token_for_first_request)
75 | end
76 |
77 | def test_should_directly_answer_incoming_associate_requests
78 | post :index, associate_request_params
79 | assert_response :success
80 | assert_match 'assoc_handle', response.body
81 | assert_match 'assoc_type', response.body
82 | assert_match 'session_type', response.body
83 | assert_match 'expires_in', response.body
84 | end
85 |
86 | def test_should_require_login_for_proceed
87 | get :proceed
88 | assert_login_required
89 | end
90 |
91 | def test_should_require_login_for_decide
92 | get :decide
93 | assert_login_required
94 | end
95 |
96 | def test_should_require_login_for_complete
97 | get :complete
98 | assert_login_required
99 | end
100 |
101 | private
102 |
103 | # Takes the name of an account fixture for which to fake the request
104 | def fake_checkid_request(account)
105 | login_as account
106 | id_url = identity_url(accounts(account), :host => Masq::Engine.config.masq['host'])
107 | openid_params = checkid_request_params.merge('openid.identity' => id_url, 'openid.claimed_id' => id_url)
108 | request.session[:request_token] = OpenIdRequest.create(:parameters => openid_params).token
109 | end
110 |
111 | end
112 | end
113 |
--------------------------------------------------------------------------------
/app/controllers/masq/accounts_controller.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class AccountsController < BaseController
3 | before_filter :check_disabled_registration, :only => [:new, :create]
4 | before_filter :login_required, :except => [:show, :new, :create, :activate, :resend_activation_email]
5 | before_filter :detect_xrds, :only => :show
6 |
7 | def show
8 | @account = Account.where(:login => params[:account], :enabled => true).first
9 | raise ActiveRecord::RecordNotFound if @account.nil?
10 |
11 | respond_to do |format|
12 | format.html do
13 | response.headers['X-XRDS-Location'] = identity_url(:account => @account, :format => :xrds, :protocol => scheme)
14 | end
15 | format.xrds
16 | end
17 | end
18 |
19 | def new
20 | @account = Account.new
21 | end
22 |
23 | def create
24 | cookies.delete :auth_token
25 | attrs = params[:account]
26 | attrs[:login] = attrs[:email] if email_as_login?
27 | signup = Signup.create_account!(attrs)
28 | if signup.succeeded?
29 | redirect_to login_path, :notice => signup.send_activation_email? ?
30 | t(:thanks_for_signing_up_activation_link) :
31 | t(:thanks_for_signing_up)
32 | else
33 | @account = signup.account
34 | render :action => 'new'
35 | end
36 | end
37 |
38 | def update
39 | attrs = params[:account]
40 | attrs.delete(:email) if email_as_login?
41 | attrs.delete(:login)
42 |
43 | if current_account.update_attributes(attrs)
44 | redirect_to edit_account_path(:account => current_account), :notice => t(:profile_updated)
45 | else
46 | render :action => 'edit'
47 | end
48 | end
49 |
50 | def destroy
51 | return render_404 unless Masq::Engine.config.masq['can_disable_account']
52 |
53 | if current_account.authenticated?(params[:confirmation_password])
54 | current_account.disable!
55 | current_account.forget_me
56 | cookies.delete :auth_token
57 | reset_session
58 | redirect_to root_path, :notice => t(:account_disabled)
59 | else
60 | redirect_to edit_account_path, :alert => t(:entered_password_is_wrong)
61 | end
62 | end
63 |
64 | def activate
65 | return render_404 unless Masq::Engine.config.masq['send_activation_mail']
66 |
67 | begin
68 | account = Account.find_and_activate!(params[:activation_code])
69 | redirect_to login_path, :notice => t(:account_activated_login_now)
70 | rescue ArgumentError, Account::ActivationCodeNotFound
71 | redirect_to new_account_path, :alert => t(:couldnt_find_account_with_code_create_new_one)
72 | rescue Account::AlreadyActivated
73 | redirect_to login_path, :alert => t(:account_already_activated_please_login)
74 | end
75 | end
76 |
77 | def change_password
78 | return render_404 unless Masq::Engine.config.masq['can_change_password']
79 |
80 | if Account.authenticate(current_account.login, params[:old_password])
81 | if ((params[:password] == params[:password_confirmation]) && !params[:password_confirmation].blank?)
82 | current_account.password_confirmation = params[:password_confirmation]
83 | current_account.password = params[:password]
84 | if current_account.save
85 | redirect_to edit_account_path(:account => current_account), :notice => t(:password_has_been_changed)
86 | else
87 | redirect_to edit_account_path, :alert => t(:sorry_password_couldnt_be_changed)
88 | end
89 | else
90 | @old_password = params[:old_password]
91 | redirect_to edit_account_path, :alert => t(:confirmation_of_new_password_invalid)
92 | end
93 | else
94 | redirect_to edit_account_path, :alert => t(:old_password_incorrect)
95 | end
96 | end
97 |
98 | def resend_activation_email
99 | account = Account.find_by_login(params[:account])
100 |
101 | if account && !account.active?
102 | AccountMailer.signup_notification(account).deliver
103 | flash[:notice] = t(:activation_link_resent)
104 | else
105 | flash[:alert] = t(:account_already_activated_or_missing)
106 | end
107 |
108 | redirect_to login_path
109 | end
110 |
111 | protected
112 |
113 | def check_disabled_registration
114 | render_404 if Masq::Engine.config.masq['disable_registration']
115 | end
116 |
117 | def detect_xrds
118 | if params[:account] =~ /\A(.+)\.xrds\z/
119 | request.format = :xrds
120 | params[:account] = $1
121 | end
122 | end
123 | end
124 | end
125 |
--------------------------------------------------------------------------------
/lib/masq/authenticated_system.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | module AuthenticatedSystem
3 | protected
4 | # Returns true or false if the account is logged in.
5 | # Preloads @current_account with the account model if they're logged in.
6 | def logged_in?
7 | current_account != :false
8 | end
9 |
10 | # Accesses the current account from the session. Set it to :false if login fails
11 | # so that future calls do not hit the database.
12 | def current_account
13 | @current_account ||= (login_from_session || login_from_basic_auth || login_from_cookie || :false)
14 | end
15 |
16 | # Store the given account id in the session.
17 | def current_account=(new_account)
18 | if self.auth_type_used != :basic
19 | session[:account_id] = (new_account.nil? || new_account.is_a?(Symbol)) ? nil : new_account.id
20 | end
21 | @current_account = new_account || :false
22 | end
23 |
24 | # Check if the account is authorized
25 | #
26 | # Override this method in your controllers if you want to restrict access
27 | # to only a few actions or if you want to check if the account
28 | # has the correct rights.
29 | #
30 | # Example:
31 | #
32 | # # only allow nonbobs
33 | # def authorized?
34 | # current_account.login != "bob"
35 | # end
36 | def authorized?
37 | logged_in?
38 | end
39 |
40 | # Filter method to enforce a login requirement.
41 | #
42 | # To require logins for all actions, use this in your controllers:
43 | #
44 | # before_filter :login_required
45 | #
46 | # To require logins for specific actions, use this in your controllers:
47 | #
48 | # before_filter :login_required, :only => [ :edit, :update ]
49 | #
50 | # To skip this in a subclassed controller:
51 | #
52 | # skip_before_filter :login_required
53 | #
54 | def login_required
55 | authorized? || access_denied
56 | end
57 |
58 | # Redirect as appropriate when an access request fails.
59 | #
60 | # The default action is to redirect to the login screen.
61 | #
62 | # Override this method in your controllers if you want to have special
63 | # behavior in case the account is not authorized
64 | # to access the requested action. For example, a popup window might
65 | # simply close itself.
66 | def access_denied
67 | respond_to do |format|
68 | format.html do
69 | store_location
70 | redirect_to login_path
71 | end
72 | format.any do
73 | request_http_basic_authentication 'Web Password'
74 | end
75 | end
76 | end
77 |
78 | # Store the URI of the current request in the session.
79 | #
80 | # We can return to this location by calling #redirect_back_or_default.
81 | def store_location(url = request.fullpath)
82 | session[:return_to] = url
83 | end
84 |
85 | # Redirect to the URI stored by the most recent store_location call or
86 | # to the passed default.
87 | def redirect_back_or_default(default)
88 | redirect_to(session[:return_to] || default)
89 | session[:return_to] = nil
90 | end
91 |
92 | # Inclusion hook to make #current_account and #logged_in?
93 | # available as ActionView helper methods.
94 | def self.included(base)
95 | base.send :helper_method, :current_account, :logged_in?, :auth_type_used
96 | end
97 |
98 | # Called from #current_account. First attempt to login by the account id stored in the session.
99 | def login_from_session
100 | account = Account.find(session[:account_id]) if session[:account_id]
101 | self.auth_type_used = :session if not account.nil?
102 | self.current_account = account
103 | end
104 |
105 | def auth_type_used
106 | @auth_type_used
107 | end
108 |
109 | def auth_type_used= t
110 | @auth_type_used = t
111 | end
112 |
113 | # Called from #current_account. Now, attempt to login by basic authentication information.
114 | def login_from_basic_auth
115 | authenticate_with_http_basic do |accountname, password|
116 | account = Account.authenticate(accountname, password, true)
117 | self.auth_type_used = :basic if not account.nil?
118 | self.current_account = account
119 | account
120 | end
121 | end
122 |
123 | # Called from #current_account. Finaly, attempt to login by an expiring token in the cookie.
124 | def login_from_cookie
125 | account = cookies[:auth_token] && Account.find_by_remember_token(cookies[:auth_token])
126 | if account && account.remember_token?
127 | account.remember_me
128 | cookies[:auth_token] = { :value => account.remember_token, :expires => account.remember_token_expires_at }
129 | self.auth_type_used = :cookie if not account.nil?
130 | self.current_account = account
131 | account
132 | end
133 | end
134 | end
135 | end
136 |
--------------------------------------------------------------------------------
/test/functional/masq/sessions_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class SessionsControllerTest < ActionController::TestCase
5 | include Masq::Engine.routes_url_helpers
6 |
7 | fixtures :accounts
8 |
9 | def test_should_save_account_id_in_session_after_successful_login
10 | post :create, :login => accounts(:standard).login, :password => 'test'
11 | assert session[:account_id]
12 | end
13 |
14 | def test_should_redirect_to_users_identity_page_after_successful_login
15 | account = accounts(:standard)
16 | post :create, :login => account.login, :password => 'test'
17 | assert_redirected_to identity_url(account, :host => Masq::Engine.config.masq['host'])
18 | end
19 |
20 | def test_should_redirect_to_users_on_login_if_allready_logged_in
21 | login_as :standard
22 | account = accounts(:standard)
23 | get :new
24 | assert_redirected_to identity_url(account, :host => Masq::Engine.config.masq['host'])
25 | end
26 |
27 | def test_should_set_cookie_with_auth_token_if_user_chose_to_be_remembered
28 | post :create, :login => accounts(:standard).login, :password => 'test', :remember_me => "1"
29 | assert_not_nil @response.cookies["auth_token"]
30 | end
31 |
32 | def test_should_not_set_cookie_with_auth_token_if_user_did_not_chose_to_be_remembered
33 | post :create, :login => accounts(:standard).login, :password => 'test', :remember_me => "0"
34 | assert_nil @response.cookies["auth_token"]
35 | end
36 |
37 | def test_should_not_save_account_id_in_session_after_failed_login
38 | post :create, :login => accounts(:standard).login, :password => 'bad password'
39 | assert_nil session[:account_id]
40 | end
41 |
42 | def test_should_not_save_account_id_in_session_after_basic_login
43 | @request.env['HTTP_AUTHORIZATION'] = encode_credentials(accounts(:standard).login, 'test')
44 | post :new
45 | assert_nil session[:account_id]
46 | assert @controller.send(:logged_in?)
47 | assert @controller.send(:auth_type_used) == :basic
48 | end
49 |
50 | def test_should_set_correct_auth_type_on_basic_login
51 | @request.env['HTTP_AUTHORIZATION'] = encode_credentials(accounts(:standard).login, 'test')
52 | get :new
53 | assert @controller.send(:auth_type_used) == :basic
54 | end
55 |
56 | def test_should_set_correct_auth_type_on_login_with_session
57 | login_as :standard
58 | get :new
59 | assert @controller.send(:auth_type_used) == :session
60 | end
61 |
62 | def test_should_set_correct_auth_type_on_login_with_cookie
63 | accounts(:standard).remember_me
64 | @request.cookies["auth_token"] = cookie_for(:standard)
65 | get :new
66 | assert @controller.send(:auth_type_used) == :cookie
67 | end
68 |
69 | def test_should_redirect_to_login_after_failed_login
70 | post :create, :login => accounts(:standard).login, :password => 'bad password'
71 | assert_redirected_to login_path
72 | assert flash.any?
73 | end
74 |
75 | def test_should_reset_session_on_logout
76 | login_as :standard
77 | get :destroy
78 | assert_nil session[:account_id]
79 | end
80 |
81 | def test_should_redirect_to_homepage_after_logout
82 | login_as :standard
83 | get :destroy
84 | assert_redirected_to root_path
85 | end
86 |
87 | def test_should_delete_token_on_logout
88 | login_as :standard
89 | get :destroy
90 | assert_nil @response.cookies["auth_token"]
91 | end
92 |
93 | def test_should_automatically_login_users_with_valid_auth_token_cookie
94 | accounts(:standard).remember_me
95 | @request.cookies["auth_token"] = cookie_for(:standard)
96 | get :new
97 | assert @controller.send(:logged_in?)
98 | end
99 |
100 | def test_should_fail_to_login_users_with_expired_auth_token_cookie
101 | accounts(:standard).remember_me
102 | accounts(:standard).update_attribute :remember_token_expires_at, 5.minutes.ago
103 | @request.cookies["auth_token"] = cookie_for(:standard)
104 | get :new
105 | assert !@controller.send(:logged_in?)
106 | end
107 |
108 | def test_should_fail_to_login_users_with_invalid_auth_token_cookie
109 | accounts(:standard).remember_me
110 | @request.cookies["auth_token"] = 'invalid_auth_token'
111 | get :new
112 | assert !@controller.send(:logged_in?)
113 | end
114 |
115 | def test_should_set_authentication_attributes_after_successful_login
116 | @account = accounts(:standard)
117 | post :create, :login => @account.login, :password => 'test'
118 | @account.reload
119 | assert_not_nil @account.last_authenticated_at
120 | assert !@account.last_authenticated_with_yubikey
121 | end
122 |
123 | def test_should_authenticate_with_password_and_yubico_otp
124 | @account = accounts(:with_yubico_identity)
125 | yubico_otp = @account.yubico_identity + 'x' * 32
126 | Account.expects(:verify_yubico_otp).with(yubico_otp).returns(true)
127 | post :create, :login => @account.login, :password => 'test' + yubico_otp
128 | @account.reload
129 | assert_not_nil @account.last_authenticated_at
130 | assert @account.last_authenticated_with_yubikey
131 | end
132 |
133 | def test_should_disallow_password_only_login_when_yubikey_is_mandatory
134 | account = accounts(:with_yubico_identity)
135 | post :create, :login => account.login, :password => 'test'
136 | assert_redirected_to login_path
137 | assert flash.any?
138 | end
139 |
140 | protected
141 |
142 | def cookie_for(account)
143 | accounts(account).remember_token
144 | end
145 |
146 | end
147 | end
148 |
--------------------------------------------------------------------------------
/test/dummy/db/schema.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | # This file is auto-generated from the current state of the database. Instead
3 | # of editing this file, please use the migrations feature of Active Record to
4 | # incrementally modify your database, and then regenerate this schema definition.
5 | #
6 | # Note that this schema.rb definition is the authoritative source for your
7 | # database schema. If you need to create the application database on another
8 | # system, you should be using db:schema:load, not running all the migrations
9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 | # you'll amass, the slower it'll run and the greater likelihood for issues).
11 | #
12 | # It's strongly recommended to check this file into your version control system.
13 |
14 | ActiveRecord::Schema.define(:version => 20120312120000) do
15 |
16 | create_table "masq_accounts", :force => true do |t|
17 | t.boolean "enabled", :default => true
18 | t.string "login", :null => false
19 | t.string "email", :null => false
20 | t.string "crypted_password", :limit => 40, :null => false
21 | t.string "salt", :limit => 40, :null => false
22 | t.string "remember_token"
23 | t.string "password_reset_code", :limit => 40
24 | t.string "activation_code", :limit => 40
25 | t.string "yubico_identity", :limit => 12
26 | t.integer "public_persona_id"
27 | t.datetime "last_authenticated_at"
28 | t.boolean "last_authenticated_with_yubikey"
29 | t.boolean "yubikey_mandatory", :default => false, :null => false
30 | t.datetime "remember_token_expires_at"
31 | t.datetime "activated_at"
32 | t.datetime "created_at"
33 | t.datetime "updated_at"
34 | end
35 |
36 | add_index "masq_accounts", ["email"], :name => "index_masq_accounts_on_email", :unique => true
37 | add_index "masq_accounts", ["login"], :name => "index_masq_accounts_on_login", :unique => true
38 |
39 | create_table "masq_open_id_associations", :force => true do |t|
40 | t.binary "server_url"
41 | t.binary "secret"
42 | t.string "handle"
43 | t.string "assoc_type"
44 | t.integer "issued"
45 | t.integer "lifetime"
46 | end
47 |
48 | create_table "masq_open_id_nonces", :force => true do |t|
49 | t.string "server_url", :null => false
50 | t.string "salt", :null => false
51 | t.integer "timestamp", :null => false
52 | end
53 |
54 | create_table "masq_open_id_requests", :force => true do |t|
55 | t.string "token", :limit => 40
56 | t.text "parameters"
57 | t.datetime "created_at"
58 | t.datetime "updated_at"
59 | end
60 |
61 | add_index "masq_open_id_requests", ["token"], :name => "index_masq_open_id_requests_on_token", :unique => true
62 |
63 | create_table "masq_personas", :force => true do |t|
64 | t.integer "account_id", :null => false
65 | t.string "title", :null => false
66 | t.string "nickname"
67 | t.string "email"
68 | t.string "fullname"
69 | t.string "postcode"
70 | t.string "country"
71 | t.string "language"
72 | t.string "timezone"
73 | t.string "gender", :limit => 1
74 | t.string "address"
75 | t.string "address_additional"
76 | t.string "city"
77 | t.string "state"
78 | t.string "company_name"
79 | t.string "job_title"
80 | t.string "address_business"
81 | t.string "address_additional_business"
82 | t.string "postcode_business"
83 | t.string "city_business"
84 | t.string "state_business"
85 | t.string "country_business"
86 | t.string "phone_home"
87 | t.string "phone_mobile"
88 | t.string "phone_work"
89 | t.string "phone_fax"
90 | t.string "im_aim"
91 | t.string "im_icq"
92 | t.string "im_msn"
93 | t.string "im_yahoo"
94 | t.string "im_jabber"
95 | t.string "im_skype"
96 | t.string "image_default"
97 | t.string "biography"
98 | t.string "web_default"
99 | t.string "web_blog"
100 | t.integer "dob_day", :limit => 2
101 | t.integer "dob_month", :limit => 2
102 | t.integer "dob_year"
103 | t.boolean "deletable", :default => true, :null => false
104 | t.datetime "created_at"
105 | t.datetime "updated_at"
106 | end
107 |
108 | add_index "masq_personas", ["account_id", "title"], :name => "index_masq_personas_on_account_id_and_title", :unique => true
109 |
110 | create_table "masq_release_policies", :force => true do |t|
111 | t.integer "site_id", :null => false
112 | t.string "property", :null => false
113 | t.string "type_identifier"
114 | end
115 |
116 | add_index "masq_release_policies", ["site_id", "property", "type_identifier"], :name => "index_masq_release_policies", :unique => true
117 |
118 | create_table "masq_sites", :force => true do |t|
119 | t.integer "account_id", :null => false
120 | t.integer "persona_id", :null => false
121 | t.string "url", :null => false
122 | t.datetime "created_at"
123 | t.datetime "updated_at"
124 | end
125 |
126 | add_index "masq_sites", ["account_id", "url"], :name => "index_masq_sites_on_account_id_and_url", :unique => true
127 |
128 | create_table "masq_timezones", :force => true do |t|
129 | t.string "name", :limit => 60, :null => false
130 | end
131 |
132 | add_index "masq_timezones", ["name"], :name => "index_masq_timezones_on_name", :unique => true
133 |
134 | end
135 |
--------------------------------------------------------------------------------
/db/migrate/20120312120000_masq_schema.rb:
--------------------------------------------------------------------------------
1 | class MasqSchema < ActiveRecord::Migration
2 | def change
3 | # Check for existing masquerade tables. In case the tables already exist,
4 | # upgrade the database by renaming the tables - otherwise create them.
5 |
6 | # Accounts: Also check for columns, as account is a pretty generic model name
7 | # and we don't want to conflict with an existing account tables that's not
8 | # from an existing masquerade installation
9 | if table_exists?(:accounts) && column_exists?(:accounts, :public_persona_id) &&
10 | column_exists?(:accounts, :yubico_identity)
11 | rename_table :accounts, :masq_accounts
12 | else
13 | create_table :masq_accounts, :force => true do |t|
14 | t.boolean :enabled, :default => true
15 | t.string :login, :null => false
16 | t.string :email, :null => false
17 | t.string :crypted_password, :limit => 40, :null => false
18 | t.string :salt, :limit => 40, :null => false
19 | t.string :remember_token
20 | t.string :password_reset_code, :limit => 40
21 | t.string :activation_code, :limit => 40
22 | t.string :yubico_identity, :limit => 12
23 | t.integer :public_persona_id
24 | t.datetime :last_authenticated_at
25 | t.boolean :last_authenticated_with_yubikey
26 | t.boolean :yubikey_mandatory, :default => false, :null => false
27 | t.datetime :remember_token_expires_at
28 | t.datetime :activated_at
29 | t.datetime :created_at
30 | t.datetime :updated_at
31 | end
32 |
33 | add_index :masq_accounts, [:email], :unique => true
34 | add_index :masq_accounts, [:login], :unique => true
35 | end
36 |
37 | # OpenID Associations
38 | if table_exists?(:open_id_associations)
39 | rename_table :open_id_associations, :masq_open_id_associations
40 | else
41 | create_table :masq_open_id_associations, :force => true do |t|
42 | t.binary :server_url
43 | t.binary :secret
44 | t.string :handle
45 | t.string :assoc_type
46 | t.integer :issued
47 | t.integer :lifetime
48 | end
49 | end
50 |
51 | # OpenID Nonces
52 | if table_exists?(:open_id_nonces)
53 | rename_table :open_id_nonces, :masq_open_id_nonces
54 | else
55 | create_table :masq_open_id_nonces, :force => true do |t|
56 | t.string :server_url, :null => false
57 | t.string :salt, :null => false
58 | t.integer :timestamp, :null => false
59 | end
60 | end
61 |
62 | # OpenID Requests
63 | if table_exists?(:open_id_requests)
64 | rename_table :open_id_requests, :masq_open_id_requests
65 | else
66 | create_table :masq_open_id_requests, :force => true do |t|
67 | t.string :token, :limit => 40
68 | t.text :parameters
69 | t.datetime :created_at
70 | t.datetime :updated_at
71 | end
72 | add_index :masq_open_id_requests, [:token], :unique => true
73 | end
74 |
75 | # Personas
76 | if table_exists?(:personas)
77 | rename_table :personas, :masq_personas
78 | else
79 | create_table :masq_personas, :force => true do |t|
80 | t.integer :account_id, :null => false
81 | t.string :title, :null => false
82 | t.string :nickname
83 | t.string :email
84 | t.string :fullname
85 | t.string :postcode
86 | t.string :country
87 | t.string :language
88 | t.string :timezone
89 | t.string :gender, :limit => 1
90 | t.string :address
91 | t.string :address_additional
92 | t.string :city
93 | t.string :state
94 | t.string :company_name
95 | t.string :job_title
96 | t.string :address_business
97 | t.string :address_additional_business
98 | t.string :postcode_business
99 | t.string :city_business
100 | t.string :state_business
101 | t.string :country_business
102 | t.string :phone_home
103 | t.string :phone_mobile
104 | t.string :phone_work
105 | t.string :phone_fax
106 | t.string :im_aim
107 | t.string :im_icq
108 | t.string :im_msn
109 | t.string :im_yahoo
110 | t.string :im_jabber
111 | t.string :im_skype
112 | t.string :image_default
113 | t.string :biography
114 | t.string :web_default
115 | t.string :web_blog
116 | t.integer :dob_day, :limit => 2
117 | t.integer :dob_month, :limit => 2
118 | t.integer :dob_year
119 | t.boolean :deletable, :default => true, :null => false
120 | t.datetime :created_at
121 | t.datetime :updated_at
122 | end
123 | add_index :masq_personas, [:account_id, :title], :unique => true
124 | end
125 |
126 | # Release Policies
127 | if table_exists?(:release_policies)
128 | rename_table :release_policies, :masq_release_policies
129 | else
130 | create_table :masq_release_policies, :force => true do |t|
131 | t.integer :site_id, :null => false
132 | t.string :property, :null => false
133 | t.string :type_identifier
134 | end
135 | add_index :masq_release_policies, [:site_id, :property, :type_identifier], :name => :index_masq_release_policies, :unique => true
136 | end
137 |
138 | # Sites
139 | if table_exists?(:sites)
140 | rename_table :sites, :masq_sites
141 | else
142 | create_table :masq_sites, :force => true do |t|
143 | t.integer :account_id, :null => false
144 | t.integer :persona_id, :null => false
145 | t.string :url, :null => false
146 | t.datetime :created_at
147 | t.datetime :updated_at
148 | end
149 | add_index :masq_sites, [:account_id, :url], :unique => true
150 | end
151 | end
152 | end
153 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | ENV["RAILS_ENV"] = "test"
3 |
4 | require File.expand_path("../dummy/config/environment.rb", __FILE__)
5 | require "rails/test_help"
6 |
7 | begin
8 | require 'turn/autorun'
9 | Turn.config.format = :dot
10 | rescue LoadError
11 | end
12 |
13 | Rails.backtrace_cleaner.remove_silencers!
14 |
15 | if ActionDispatch::IntegrationTest.method_defined?(:fixture_path=)
16 | ActionDispatch::IntegrationTest.fixture_path = File.expand_path("../fixtures", __FILE__)
17 | end
18 |
19 | module Masq
20 | class ActionController::TestCase
21 | setup do
22 | @routes = Engine.routes
23 | end
24 | end
25 |
26 | module TestHelper
27 |
28 | private
29 |
30 | def valid_account_attributes
31 | { :login => 'dennisreimann',
32 | :email => 'mail@dennisreimann.de',
33 | :password => '123456',
34 | :password_confirmation => '123456' }
35 | end
36 |
37 | def valid_persona_attributes
38 | { :title => 'official',
39 | :nickname => 'dennisreimann',
40 | :email => 'mail@dennisreimann.de',
41 | :fullname => 'Dennis Reimann',
42 | :postcode => '28199',
43 | :country => 'DE',
44 | :language => 'DE',
45 | :timezone => 'Europe/Berlin',
46 | :gender => 'M',
47 | :dob_day => '10',
48 | :dob_month => '01',
49 | :dob_year => '1982' }
50 | end
51 |
52 | def valid_properties
53 | { 'nickname' => { 'value' => 'dennisreimann', 'type' => 'nickname' },
54 | 'email' => { 'value' => 'mail@dennisreimann.de', 'type' => 'email' },
55 | 'gender' => { 'value' => 'M', 'type' => 'gender' },
56 | 'dob' => { 'value' => '1982-01-10', 'type' => 'dob' },
57 | 'login' => { 'value' => 'dennisreimann', 'type'=> 'http://axschema.org/namePerson/friendly' },
58 | 'email_address' => { 'value' => 'mail@dennisreimann.de', 'type' => 'http://axschema.org/contact/email' } }
59 | end
60 |
61 | def valid_site_attributes
62 | { :url => "http://dennisreimann.de/" }
63 | end
64 |
65 | def checkid_request_params
66 | { 'openid.ns' => OpenID::OPENID2_NS,
67 | 'openid.mode' => 'checkid_setup',
68 | 'openid.realm' => 'http://test.com/',
69 | 'openid.trust_root' => 'http://test.com/',
70 | 'openid.return_to' => 'http://test.com/return',
71 | 'openid.claimed_id' => 'http://dennisreimann.de/',
72 | 'openid.identity' => 'http://openid.innovated.de/dennisreimann' }
73 | end
74 |
75 | def associate_request_params
76 | { 'openid.ns' => OpenID::OPENID2_NS,
77 | 'openid.mode' => 'associate',
78 | 'openid.assoc_type' => 'HMAC-SHA1',
79 | 'openid.session_type' => 'DH-SHA1',
80 | 'openid.dh_consumer_public' => 'MgKzyEozjQH6uDumfyCGfDGWW2RM5QRfLi+Yu+h7SuW7l+jxk54/s9mWG+0ZR2J4LmhUO9Cw/sPqynxwqWGQLnxr0wYHxSsBIctUgxp67L/6qB+9GKM6URpv1mPkifv5k1M8hIJTQhzYXxHe+/7MM8BD47vBp0nihjaDr0XAe6w=' }
81 | end
82 |
83 | def sreg_request_params
84 | { 'openid.ns.sreg' => OpenID::SReg::NS_URI,
85 | 'openid.sreg.required' => 'nickname,email',
86 | 'openid.sreg.optional' => 'fullname,dob',
87 | 'openid.sreg.policy_url' => 'http://test.com/policy.html' }
88 | end
89 |
90 | def ax_fetch_request_params
91 | { 'openid.ns.ax' => OpenID::AX::AXMessage::NS_URI,
92 | 'openid.ax.mode' => OpenID::AX::FetchRequest::MODE,
93 | 'openid.ax.type.nickname' => 'http://axschema.org/namePerson/friendly',
94 | 'openid.ax.type.gender' => 'http://axschema.org/person/gender',
95 | 'openid.ax.required' => 'nickname',
96 | 'openid.ax.if_available' => 'gender',
97 | 'openid.ax.update_url' => 'http://test.com/update' }
98 | end
99 |
100 | def ax_store_request_params
101 | { 'openid.ns.ax' => OpenID::AX::AXMessage::NS_URI,
102 | 'openid.ax.mode' => OpenID::AX::StoreRequest::MODE,
103 | 'openid.ax.count.fullname' => 1,
104 | 'openid.ax.type.fullname' => 'http://axschema.org/namePerson',
105 | 'openid.ax.value.fullname.1' => 'Bob "AX Storer" Smith',
106 | 'openid.ax.count.email' => 1,
107 | 'openid.ax.type.email' => 'http://axschema.org/contact/email',
108 | 'openid.ax.value.email.1' => 'new@axstore.com' }
109 | end
110 |
111 | def pape_request_params
112 | { 'openid.ns.pape' => OpenID::PAPE::NS_URI,
113 | 'openid.pape.max_auth_age' => 3600,
114 | 'openid.pape.preferred_auth_policies' => [
115 | OpenID::PAPE::AUTH_MULTI_FACTOR_PHYSICAL,
116 | OpenID::PAPE::AUTH_MULTI_FACTOR,
117 | OpenID::PAPE::AUTH_PHISHING_RESISTANT].join(' ') }
118 | end
119 |
120 | def assert_valid(object) # just for work with Rails 2.3.4.
121 | assert object.valid?
122 | end
123 |
124 | def assert_invalid(object, attribute, message = "")
125 | assert_equal false, object.valid?
126 | assert object.errors[attribute], message
127 | end
128 |
129 | def assert_login_required
130 | assert_redirected_to login_path
131 | assert_not_nil request.session[:return_to]
132 | end
133 |
134 | # verbatim, from ActiveController's own unit tests
135 | # stolen from http://stackoverflow.com/questions/1165478/testing-http-basic-auth-in-rails-2-2/1258046#1258046
136 | def encode_credentials(username, password)
137 | "Basic #{Base64.encode64("#{username}:#{password}")}"
138 | end
139 |
140 | # Sets the current user in the session from the user fixtures.
141 | def login_as(account)
142 | request.session[:account_id] = account ? accounts(account).id : nil
143 | end
144 |
145 | def authorize_as(account)
146 | if @request.env["HTTP_AUTHORIZATION"] = account
147 | ActionController::HttpAuthentication::Basic.encode_credentials(accounts(account).login, 'test')
148 | else
149 | nil
150 | end
151 | end
152 | end
153 | end
154 |
155 | # Load support files
156 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
157 |
--------------------------------------------------------------------------------
/app/views/masq/server/decide.html.erb:
--------------------------------------------------------------------------------
1 | <%= t(:identity_request_from_host, :host => h(extract_host(@site.url))).html_safe %>
2 |
3 | <% if ax_store_request %>
4 | <%= t(:trust_root_sends_some_personal_data, :trust_root => "#{h checkid_request.trust_root} ").html_safe %>
5 | <% if @site.persona %>
6 | <%= t(:select_information_to_accept) %>
7 | <%= t(:attributes_will_be_added_to_persona, :persona => "#{h @site.persona.title} ",
8 | :choose_link => link_to_function(t(:choose_other_persona), 'Element.toggle("persona_select")')).html_safe %>
9 | <% else %>
10 | <%= t(:identity_request_missing_persona, :create_link => link_to(t(:create_persona_link), new_account_persona_path(:return => decide_path))).html_safe %>
11 | <% end %>
12 |
13 | <% elsif sreg_request || ax_fetch_request %>
14 | <%= t(:trust_root_requests_some_personal_data, :trust_root => "#{h checkid_request.trust_root} ").html_safe %>
15 | <% if @site.persona %>
16 | <%= t(:select_information_to_submit) %>
17 | <%= t(:attributes_are_shown_from_persona, :persona => "#{h @site.persona.title} ").html_safe %>
18 | <%= t(:to_submit_other_values_you_can_edit_or_choose,
19 | :edit_link => link_to(t(:edit_persona_link), edit_account_persona_path(@site.persona, :return => decide_path(:persona_id => @site.persona.id))),
20 | :choose_link => link_to_function(t(:choose_other_persona), 'Element.toggle("persona_select")')).html_safe %>
21 | <% else %>
22 | <%= t(:identity_request_missing_persona, :create_link => link_to(t(:create_persona_link), new_account_persona_path(:return => decide_path))).html_safe %>
23 | <% end %>
24 | <% else %>
25 | <%= t(:trust_root_requires_authentication, :trust_root => "#{h checkid_request.trust_root} ").html_safe %>
26 | <% end %>
27 |
28 | <%= form_tag decide_path, :method => :get, :id => 'persona_select', :style => 'display:none;' do %>
29 |
30 | <%= t(:choose_persona_title) %>
31 | <% unless current_account.personas.empty? %>
32 |
33 |
34 | <%= options_from_collection_for_select(current_account.personas, :id, :title, @site.persona.id) %>
35 |
36 |
37 |
38 | <%= submit_tag t(:choose_persona_submit) %> <%= t(:or) %>
39 | <%= link_to t(:create_new_persona_link), new_account_persona_path(:return => decide_path) %>
40 |
41 | <% end %>
42 |
43 | <% end if sreg_request || ax_fetch_request || ax_store_request %>
44 |
45 | <%= form_for @site, :url => complete_path do |f| %>
46 |
47 | <% if sreg_request || ax_fetch_request %>
48 |
49 |
50 | <%= t(:requested_information_title) %>
51 | <%= t(:disclosure) %>
52 |
53 | <% (sreg_request.required + sreg_request.optional).each do |property| %>
54 |
55 | <%= label_tag "site_sreg_#{property}", property_label_text(property) %>
56 | <%= label_tag "site_sreg_#{property}", @site.persona.property(property) %>
57 |
58 | <%= check_box_tag "site[sreg][#{property}]", @site.persona.property(property), sreg_request.required.include?(property), :id => "site_sreg_#{property}", :class => sreg_request_for_field(property) %>
59 | <%= label_tag "site_sreg_#{property}", sreg_request_for_field(property), :class => 'note' %>
60 |
61 |
62 | <% end if sreg_request %>
63 | <% ax_fetch_request.attributes.each do |property| %>
64 | <% supported = !Masq::Persona.attribute_name_for_type_uri(property.type_uri).nil? %>
65 |
66 | <%= label_tag "site_ax_fetch_#{property.ns_alias}", property_label_text(property.ns_alias) %>
67 | <%= label_tag "site_ax_fetch_#{property.ns_alias}", supported ? @site.persona.property(property.type_uri) : t(:not_supported), :class => supported ? nil : 'note' %>
68 |
69 | <%= check_box_tag "site[ax_fetch][#{property.ns_alias}][value]", @site.persona.property(property.type_uri), property.required, :id => "site_ax_fetch_#{property.ns_alias}", :class => ax_request_for_field(property), :disabled => !supported %>
70 | <%= hidden_field_tag "site[ax_fetch][#{property.ns_alias}][type]", property.type_uri, :id => "site_ax_fetch_type_#{property.ns_alias}" %>
71 | <%= label_tag "site_ax_fetch_#{property.ns_alias}", ax_request_for_field(property), :class => 'note' %>
72 |
73 |
74 | <% end if ax_fetch_request %>
75 |
76 | <% end %>
77 |
78 | <% if ax_store_request %>
79 |
80 |
81 | <%= t(:sent_information_title) %>
82 | <%= t(:current) %>
83 | <%= t(:accept) %>
84 |
85 | <% ax_store_request.data.each do |type_uri, value| %>
86 | <% if attribute = Masq::Persona.attribute_name_for_type_uri(type_uri) %>
87 |
88 | <%= label_tag "site_ax_store_#{attribute}", property_label_text_for_type_uri(type_uri) %>
89 | <%= label_tag "site_ax_store_#{attribute}", value %>
90 | <%= label_tag "site_ax_store_#{attribute}", @site.persona.property(type_uri) %>
91 |
92 | <%= check_box_tag "site[ax_store][#{attribute}][value]", value, false, :id => "site_ax_store_#{attribute}" %>
93 | <%= hidden_field_tag "site[ax_store][#{attribute}][type]", type_uri, :id => "site_ax_store_type_#{attribute}" %>
94 |
95 |
96 | <% end %>
97 | <% end %>
98 |
99 | <% end %>
100 |
101 |
102 | <% if sreg_request || ax_fetch_request || ax_store_request %>
103 | <%= f.hidden_field :persona_id %>
104 | <%= f.hidden_field :url %>
105 | <% end %>
106 |
107 | <% if sreg_request || ax_fetch_request %>
108 | <%= submit_tag t(:trust_site_only_this_time), :name => 'temporary', :class => 'space' %>
109 | <%= submit_tag t(:always_trust_site), :name => 'always', :class => 'space' %>
110 | <% else %>
111 | <%= submit_tag t(:approve_request), :name => 'temporary', :class => 'space' %>
112 | <% end %>
113 | <%= submit_tag t(:cancel_request), :name => 'cancel' %>
114 |
115 |
116 | <% end unless current_account.personas.empty? %>
117 |
118 |
--------------------------------------------------------------------------------
/test/functional/masq/accounts_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | module Masq
4 | class AccountsControllerTest < ActionController::TestCase
5 | include Masq::Engine.routes_url_helpers
6 |
7 | fixtures :accounts
8 |
9 | def test_should_allow_signup_if_enabled
10 | Masq::Engine.config.masq['disable_registration'] = false
11 | assert_difference 'Account.count' do
12 | post :create, :account => valid_account_attributes
13 | end
14 | assert_redirected_to login_path
15 | end
16 |
17 | def test_should_return404_on_signup_if_disabled
18 | Masq::Engine.config.masq['disable_registration'] = true
19 | get :new
20 | assert_response :not_found
21 | post :create, :account => valid_account_attributes
22 | assert_response :not_found
23 | end
24 |
25 | def test_should_show_correct_message_after_signup_if_send_activation_mail_is_disabled
26 | Masq::Engine.config.masq['disable_registration'] = false # doesn't make sense if registration is disabled
27 | Masq::Engine.config.masq['send_activation_mail'] = true
28 | post :create, :account => valid_account_attributes
29 | assert_equal I18n.t(:thanks_for_signing_up_activation_link), flash[:notice]
30 | end
31 |
32 | def test_should_show_correct_message_after_signup_if_send_activation_mail_is_enabled
33 | Masq::Engine.config.masq['disable_registration'] = false # doesn't make sense if registration is disabled
34 | Masq::Engine.config.masq['send_activation_mail'] = false
35 | post :create, :account => valid_account_attributes
36 | assert_equal I18n.t(:thanks_for_signing_up), flash[:notice]
37 | end
38 |
39 | def test_should_allow_activate_if_send_activation_mail_is_enabled
40 | Masq::Engine.config.masq['send_activation_mail'] = true
41 | get :activate, :account => valid_account_attributes
42 | assert_response :found
43 | end
44 |
45 | def test_should_return404_activate_if_send_activation_mail_is_disabled
46 | Masq::Engine.config.masq['send_activation_mail'] = false
47 | get :activate, :account => valid_account_attributes
48 | assert_response :not_found
49 | end
50 |
51 | def test_should_require_login_for_edit
52 | get :edit
53 | assert_login_required
54 | end
55 |
56 | def test_should_require_login_for_update
57 | put :update
58 | assert_login_required
59 | end
60 |
61 | def test_should_require_login_for_destroy
62 | delete :destroy
63 | assert_login_required
64 | end
65 |
66 | def test_should_display_current_account_on_edit
67 | login_as(:standard)
68 | get :edit
69 | assert_select "input#account_email[value='quentin@example.com']"
70 | end
71 |
72 | def test_should_require_login_for_change_password_and_change_password_is_enabled
73 | Masq::Engine.config.masq['can_change_password'] = true
74 | put :change_password
75 | assert_login_required
76 | end
77 |
78 | def test_should_return404_on_change_password_if_change_password_is_disabled
79 | Masq::Engine.config.masq['can_change_password'] = false
80 | login_as(:standard)
81 | put :change_password
82 | assert_response :not_found
83 | end
84 |
85 | def test_should_change_password_if_change_password_is_enabled
86 | Masq::Engine.config.masq['can_change_password'] = true
87 | login_as(:standard)
88 | put :change_password, :old_password => 'test', :password => 'testtest', :password_confirmation => 'testtest'
89 | assert flash[:notice] == I18n.t(:password_has_been_changed)
90 | end
91 |
92 | def test_should_disable_account_if_confirmation_password_matches_and_can_disable_account_is_enabled
93 | Masq::Engine.config.masq['can_disable_account'] = true
94 | login_as(:standard)
95 | delete :destroy, :confirmation_password => 'test'
96 | assert !accounts(:standard).reload.enabled
97 | assert_redirected_to root_path
98 | end
99 |
100 | def test_should_get_404_on_disable_account_if_confirmation_password_matches_and_can_disable_account_is_disabled
101 | Masq::Engine.config.masq['can_disable_account'] = false
102 | login_as(:standard)
103 | delete :destroy, :confirmation_password => 'test'
104 | assert_response :not_found
105 | end
106 |
107 | def test_should_not_disable_account_if_confirmation_password_does_not_match
108 | Masq::Engine.config.masq['can_disable_account'] = true # doesn't make sense if registration is disabled
109 | login_as(:standard)
110 | delete :destroy, :confirmation_password => 'lksdajflsaf'
111 | assert accounts(:standard).reload.enabled
112 | assert_redirected_to edit_account_path
113 | end
114 |
115 | def test_should_show_change_password_if_can_can_change_password_is_enabled
116 | Masq::Engine.config.masq['can_change_password'] = true
117 | login_as(:standard)
118 | get :edit
119 | assert_select "h2:nth-of-type(2)", I18n.t(:my_password)
120 | end
121 |
122 | def test_should_not_show_change_password_if_can_change_password_is_disabled
123 | Masq::Engine.config.masq['can_change_password'] = false
124 | login_as(:standard)
125 | get :edit
126 | assert_select "h2:nth-of-type(2)", {:text => I18n.t(:my_password), :count => 0}
127 | end
128 |
129 | def test_should_show_disable_account_if_can_disable_account_is_enabled
130 | Masq::Engine.config.masq['can_change_password'] = true # required for h2 count in selector
131 | Masq::Engine.config.masq['can_disable_account'] = true
132 | login_as(:standard)
133 | get :edit
134 | assert_select "h2:nth-of-type(4)", I18n.t(:disable_my_account)
135 | end
136 |
137 | def test_should_not_show_disable_account_if_can_disable_account_is_disabled
138 | Masq::Engine.config.masq['can_change_password'] = true # required for h2 count in selector
139 | Masq::Engine.config.masq['can_disable_account'] = false
140 | login_as(:standard)
141 | get :edit
142 | assert_select "h2:nth-of-type(4)", {:text => I18n.t(:disable_my_account), :count => 0}
143 | end
144 |
145 | def test_should_set_yadis_header_on_identity_page
146 | account = accounts(:standard).login
147 | get :show, :account => account
148 | assert_match identity_path(account, :format => :xrds), @response.headers['X-XRDS-Location']
149 | end
150 |
151 | end
152 | end
153 |
--------------------------------------------------------------------------------
/config/locales/rails.de.yml:
--------------------------------------------------------------------------------
1 | de:
2 | date:
3 | formats:
4 | default: "%d.%m.%Y"
5 | short: "%e. %b"
6 | long: "%e. %B %Y"
7 | only_day: "%e"
8 |
9 | day_names:
10 | - Sonntag
11 | - Montag
12 | - Dienstag
13 | - Mittwoch
14 | - Donnerstag
15 | - Freitag
16 | - Samstag
17 | abbr_day_names:
18 | - So
19 | - Mo
20 | - Di
21 | - Mi
22 | - Do
23 | - Fr
24 | - Sa
25 | month_names:
26 | - ~
27 | - Januar
28 | - Februar
29 | - März
30 | - April
31 | - Mai
32 | - Juni
33 | - Juli
34 | - August
35 | - September
36 | - Oktober
37 | - November
38 | - Dezember
39 | abbr_month_names:
40 | - ~
41 | - Jan
42 | - Feb
43 | - Mär
44 | - Apr
45 | - Mai
46 | - Jun
47 | - Jul
48 | - Aug
49 | - Sep
50 | - Okt
51 | - Nov
52 | - Dez
53 | order:
54 | - :day
55 | - :month
56 | - :year
57 |
58 | time:
59 | formats:
60 | default: "%A, %d. %B %Y, %H:%M Uhr"
61 | short: "%d. %B, %H:%M Uhr"
62 | long: "%A, %d. %B %Y, %H:%M Uhr"
63 | time: "%H:%M"
64 |
65 | am: "vormittags"
66 | pm: "nachmittags"
67 |
68 | datetime:
69 | distance_in_words:
70 | half_a_minute: 'eine halbe Minute'
71 | less_than_x_seconds:
72 | one: 'weniger als eine Sekunde'
73 | other: 'weniger als %{count} Sekunden'
74 | x_seconds:
75 | one: 'eine Sekunde'
76 | other: '%{count} Sekunden'
77 | less_than_x_minutes:
78 | one: 'weniger als eine Minute'
79 | other: 'weniger als %{count} Minuten'
80 | x_minutes:
81 | one: 'eine Minute'
82 | other: '%{count} Minuten'
83 | about_x_hours:
84 | one: 'etwa eine Stunde'
85 | other: 'etwa %{count} Stunden'
86 | x_days:
87 | one: 'ein Tag'
88 | other: '%{count} Tage'
89 | about_x_months:
90 | one: 'etwa ein Monat'
91 | other: 'etwa %{count} Monate'
92 | x_months:
93 | one: 'ein Monat'
94 | other: '%{count} Monate'
95 | almost_x_years:
96 | one: 'fast ein Jahr'
97 | other: 'fast %{count} Jahre'
98 | about_x_years:
99 | one: 'etwa ein Jahr'
100 | other: 'etwa %{count} Jahre'
101 | over_x_years:
102 | one: 'mehr als ein Jahr'
103 | other: 'mehr als %{count} Jahre'
104 | prompts:
105 | second: "Sekunden"
106 | minute: "Minuten"
107 | hour: "Stunden"
108 | day: "Tag"
109 | month: "Monat"
110 | year: "Jahr"
111 |
112 | number:
113 | format:
114 | precision: 2
115 | separator: ','
116 | delimiter: '.'
117 | significant: false
118 | strip_insignificant_zeros: false
119 | currency:
120 | format:
121 | unit: '€'
122 | format: '%n %u'
123 | separator: ","
124 | delimiter: "."
125 | precision: 2
126 | significant: false
127 | strip_insignificant_zeros: false
128 | percentage:
129 | format:
130 | delimiter: ""
131 | precision:
132 | format:
133 | delimiter: ""
134 | human:
135 | format:
136 | delimiter: ""
137 | precision: 1
138 | significant: true
139 | strip_insignificant_zeros: true
140 | storage_units:
141 | # Storage units output formatting.
142 | # %u is the storage unit, %n is the number (default: 2 MB)
143 | format: "%n %u"
144 | units:
145 | byte:
146 | one: "Byte"
147 | other: "Bytes"
148 | kb: "KB"
149 | mb: "MB"
150 | gb: "GB"
151 | tb: "TB"
152 | decimal_units:
153 | format: "%n %u"
154 | units:
155 | unit: ""
156 | thousand: Tausend
157 | million: Millionen
158 | billion:
159 | one: Milliarde
160 | other: Milliarden
161 | trillion: Billionen
162 | quadrillion:
163 | one: Billiarde
164 | other: Billiarden
165 |
166 | support:
167 | array:
168 | words_connector: ", "
169 | two_words_connector: " und "
170 | last_word_connector: " und "
171 | select:
172 | prompt: "Bitte wählen:"
173 |
174 | helpers:
175 | select:
176 | prompt: "Bitte wählen"
177 |
178 | submit:
179 | create: '%{model} erstellen'
180 | update: '%{model} aktualisieren'
181 | submit: '%{model} speichern'
182 |
183 | errors:
184 | format: "%{attribute} %{message}"
185 |
186 | messages: &errors_messages
187 | inclusion: "ist kein gültiger Wert"
188 | exclusion: "ist nicht verfügbar"
189 | invalid: "ist nicht gültig"
190 | confirmation: "stimmt nicht mit der Bestätigung überein"
191 | accepted: "muss akzeptiert werden"
192 | empty: "muss ausgefüllt werden"
193 | blank: "muss ausgefüllt werden"
194 | too_long: "ist zu lang (nicht mehr als %{count} Zeichen)"
195 | too_short: "ist zu kurz (nicht weniger als %{count} Zeichen)"
196 | wrong_length: "hat die falsche Länge (muss genau %{count} Zeichen haben)"
197 | not_a_number: "ist keine Zahl"
198 | greater_than: "muss größer als %{count} sein"
199 | greater_than_or_equal_to: "muss größer oder gleich %{count} sein"
200 | equal_to: "muss genau %{count} sein"
201 | less_than: "muss kleiner als %{count} sein"
202 | less_than_or_equal_to: "muss kleiner oder gleich %{count} sein"
203 | odd: "muss ungerade sein"
204 | even: "muss gerade sein"
205 | not_an_integer: "muss ganzzahlig sein"
206 | not_saved:
207 | one: "Speichern nicht möglich: ein Fehler."
208 | other: "Speichern nicht möglich: %{count} Fehler."
209 | template: &errors_template
210 | header:
211 | one: "Konnte %{model} nicht speichern: ein Fehler."
212 | other: "Konnte %{model} nicht speichern: %{count} Fehler."
213 | body: "Bitte überprüfen Sie die folgenden Felder:"
214 |
215 | activerecord:
216 | errors:
217 | messages:
218 | taken: "ist bereits vergeben"
219 | record_invalid: "Gültigkeitsprüfung ist fehlgeschlagen: %{errors}"
220 | <<: *errors_messages
221 | template:
222 | <<: *errors_template
223 |
224 | full_messages:
225 | format: "%{attribute} %{message}"
226 |
--------------------------------------------------------------------------------
/app/views/masq/personas/_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form.label :title, t(:title) %>
3 | <%= form.text_field :title %>
4 |
5 |
6 | <%=t :personal_information %>
7 |
8 | <%= form.label :nickname, property_label_text(:nickname) %>
9 | <%= form.text_field :nickname %>
10 |
11 |
12 | <%= form.label :fullname, property_label_text(:fullname) %>
13 | <%= form.text_field :fullname %>
14 |
15 |
16 | <%= form.label :email, property_label_text(:email) %>
17 | <%= form.text_field :email %>
18 |
19 |
20 | <%= form.label :address, property_label_text(:address) %>
21 | <%= form.text_field :address %>
22 |
23 |
24 | <%= form.label :address_additional, property_label_text(:address_additional) %>
25 | <%= form.text_field :address_additional %>
26 |
27 |
28 | <%= form.label :postcode, property_label_text(:postcode) %>
29 | <%= form.text_field :postcode %>
30 |
31 |
32 | <%= form.label :city, property_label_text(:city) %>
33 | <%= form.text_field :city %>
34 |
35 |
36 | <%= form.label :state, property_label_text(:state) %>
37 | <%= form.text_field :state %>
38 |
39 |
40 | <%= form.label :country, property_label_text(:country) %>
41 | <%= form.select :country, countries_for_select, { :include_blank => true } %>
42 |
43 |
44 | <%= form.label :language, property_label_text(:language) %>
45 | <%= form.select :language, languages_for_select, { :include_blank => true } %>
46 |
47 |
48 | <%= form.label :timezone, property_label_text(:timezone) %>
49 | <%= form.select :timezone, ActiveSupport::TimeZone::MAPPING.values.sort, { :include_blank => true } %>
50 |
51 |
52 | <%= form.label :gender, property_label_text(:gender) %>
53 | <%= form.select :gender, [['Male', 'M'], ['Female', 'F']], { :include_blank => true } %>
54 |
55 |
56 | <%= form.label :dob_day, property_label_text(:dob) %>
57 | <%= select_day persona.dob_day, :prefix => 'persona', :field_name => 'dob_day', :include_blank => true %>
58 | <%= select_month persona.dob_month, :prefix => 'persona', :field_name => 'dob_month', :include_blank => true %>
59 | <%= select_year persona.dob_year, :prefix => 'persona', :field_name => 'dob_year', :include_blank => true, :start_year => (Date.today.year - 100), :end_year => Date.today.year %>
60 |
61 |
62 | <%=t :professional_information %>
63 |
64 | <%= form.label :company_name, property_label_text(:company_name) %>
65 | <%= form.text_field :company_name %>
66 |
67 |
68 | <%= form.label :job_title, property_label_text(:job_title) %>
69 | <%= form.text_field :job_title %>
70 |
71 |
72 | <%= form.label :address_business, property_label_text(:address_business) %>
73 | <%= form.text_field :address_business %>
74 |
75 |
76 | <%= form.label :address_additional_business, property_label_text(:address_additional_business) %>
77 | <%= form.text_field :address_additional_business %>
78 |
79 |
80 | <%= form.label :postcode_business, property_label_text(:postcode_business) %>
81 | <%= form.text_field :postcode_business %>
82 |
83 |
84 | <%= form.label :city_business, property_label_text(:city_business) %>
85 | <%= form.text_field :city_business %>
86 |
87 |
88 | <%= form.label :state_business, property_label_text(:state_business) %>
89 | <%= form.text_field :state_business %>
90 |
91 |
92 | <%= form.label :country_business, property_label_text(:country_business) %>
93 | <%= form.select :country_business, countries_for_select, { :include_blank => true } %>
94 |
95 |
96 | <%=t :phone %>
97 |
98 | <%= form.label :phone_home, property_label_text(:phone_home) %>
99 | <%= form.text_field :phone_home %>
100 |
101 |
102 | <%= form.label :phone_mobile, property_label_text(:phone_mobile) %>
103 | <%= form.text_field :phone_mobile %>
104 |
105 |
106 | <%= form.label :phone_fax, property_label_text(:phone_fax) %>
107 | <%= form.text_field :phone_fax %>
108 |
109 |
110 | <%= form.label :phone_work, property_label_text(:phone_work) %>
111 | <%= form.text_field :phone_work %>
112 |
113 |
114 | <%=t :instant_messaging %>
115 |
116 | <%= form.label :im_aim, property_label_text(:im_aim) %>
117 | <%= form.text_field :im_aim %>
118 |
119 |
120 | <%= form.label :im_icq, property_label_text(:im_icq) %>
121 | <%= form.text_field :im_icq %>
122 |
123 |
124 | <%= form.label :im_msn, property_label_text(:im_msn) %>
125 | <%= form.text_field :im_msn %>
126 |
127 |
128 | <%= form.label :im_yahoo, property_label_text(:im_yahoo) %>
129 | <%= form.text_field :im_yahoo %>
130 |
131 |
132 | <%= form.label :im_jabber, property_label_text(:im_jabber) %>
133 | <%= form.text_field :im_jabber %>
134 |
135 |
136 | <%= form.label :im_skype, property_label_text(:im_skype) %>
137 | <%= form.text_field :im_skype %>
138 |
139 |
140 | <%=t :other_information %>
141 |
142 | <%= form.label :biography, property_label_text(:biography) %>
143 | <%= form.text_field :biography %>
144 |
145 |
146 | <%= form.label :image_default, property_label_text(:image_default) %>
147 | <%= form.text_field :image_default %>
148 |
149 |
150 | <%= form.label :web_default, property_label_text(:web_default) %>
151 | <%= form.text_field :web_default %>
152 |
153 |
154 | <%= form.label :web_blog, property_label_text(:web_blog) %>
155 | <%= form.text_field :web_blog %>
156 |
157 |
--------------------------------------------------------------------------------
/app/controllers/masq/consumer_controller.rb:
--------------------------------------------------------------------------------
1 | module Masq
2 | class ConsumerController < BaseController
3 |
4 | skip_before_filter :verify_authenticity_token
5 |
6 | def start
7 | begin
8 | oidreq = openid_consumer.begin(params[:openid_identifier])
9 | rescue OpenID::OpenIDError => e
10 | redirect_to consumer_path, :alert => "Discovery failed for #{params[:openid_identifier]}: #{e}"
11 | return
12 | end
13 | if params[:use_sreg]
14 | sregreq = OpenID::SReg::Request.new
15 | sregreq.policy_url = 'http://www.policy-url.com'
16 | sregreq.request_fields(['nickname', 'email'], true) # required
17 | sregreq.request_fields(['fullname', 'dob'], false) # optional
18 | oidreq.add_extension(sregreq)
19 | oidreq.return_to_args['did_sreg'] = 'y'
20 | end
21 | if params[:use_ax_fetch]
22 | axreq = OpenID::AX::FetchRequest.new
23 | requested_attrs = [['https://openid.tzi.de/spec/schema', 'uid', true],
24 | ['http://axschema.org/namePerson/friendly', 'nickname', true],
25 | ['http://axschema.org/contact/email', 'email', true],
26 | ['http://axschema.org/namePerson', 'fullname'],
27 | ['http://axschema.org/contact/web/default', 'website', false, 2],
28 | ['http://axschema.org/contact/postalCode/home', 'postcode'],
29 | ['http://axschema.org/person/gender', 'gender'],
30 | ['http://axschema.org/birthDate', 'birth_date'],
31 | ['http://axschema.org/contact/country/home', 'country'],
32 | ['http://axschema.org/pref/language', 'language'],
33 | ['http://axschema.org/pref/timezone', 'timezone']]
34 | requested_attrs.each { |a| axreq.add(OpenID::AX::AttrInfo.new(a[0], a[1], a[2] || false, a[3] || 1)) }
35 | oidreq.add_extension(axreq)
36 | oidreq.return_to_args['did_ax_fetch'] = 'y'
37 | end
38 | if params[:use_ax_store]
39 | ax_store_req = OpenID::AX::StoreRequest.new
40 | ax_store_req.set_values('http://axschema.org/contact/email', %w(email@example.com))
41 | ax_store_req.set_values('http://axschema.org/birthDate', %w(1976-08-07))
42 | ax_store_req.set_values('http://axschema.org/customValueThatIsNotSupported', %w(unsupported))
43 | oidreq.add_extension(ax_store_req)
44 | oidreq.return_to_args['did_ax_store'] = 'y'
45 | end
46 | if params[:use_pape]
47 | papereq = OpenID::PAPE::Request.new
48 | papereq.add_policy_uri(OpenID::PAPE::AUTH_PHISHING_RESISTANT)
49 | papereq.max_auth_age = 60
50 | oidreq.add_extension(papereq)
51 | oidreq.return_to_args['did_pape'] = 'y'
52 | end
53 | if params[:force_post]
54 | oidreq.return_to_args['force_post'] = 'x' * 2048
55 | end
56 | if oidreq.send_redirect?(consumer_url, consumer_complete_url, params[:immediate])
57 | redirect_to oidreq.redirect_url(consumer_url, consumer_complete_url, params[:immediate])
58 | else
59 | @form_text = oidreq.form_markup(consumer_url, consumer_complete_url, params[:immediate], { 'id' => 'checkid_form' })
60 | end
61 | end
62 |
63 | def complete
64 | parameters = params.reject{ |k,v| request.path_parameters[k.to_sym] }
65 | oidresp = openid_consumer.complete(parameters, url_for({}))
66 | case oidresp.status
67 | when OpenID::Consumer::SETUP_NEEDED
68 | flash[:alert] = t(:immediate_request_failed_setup_needed)
69 | when OpenID::Consumer::CANCEL
70 | flash[:alert] = t(:openid_transaction_cancelled)
71 | when OpenID::Consumer::FAILURE
72 | flash[:alert] = oidresp.display_identifier ?
73 | t(:verification_of_identifier_failed, :identifier => oidresp.display_identifier, :message => oidresp.message) :
74 | t(:verification_failed_message, :message => oidresp.message)
75 | when OpenID::Consumer::SUCCESS
76 | flash[:notice] = t(:verification_of_identifier_succeeded, :identifier => oidresp.display_identifier)
77 | if params[:did_sreg]
78 | sreg_resp = OpenID::SReg::Response.from_success_response(oidresp)
79 | sreg_message = "\n\n" + t(:simple_registration_data_requested)
80 | if sreg_resp.empty?
81 | sreg_message << ", " + t(:but_none_was_returned)
82 | else
83 | sreg_message << ". " + t(:the_following_data_were_sent) + "\n"
84 | sreg_resp.data.each { |k,v| sreg_message << "#{k}: #{v}\n" }
85 | end
86 | flash[:notice] += sreg_message
87 | end
88 | if params[:did_ax_fetch]
89 | ax_fetch_resp = OpenID::AX::FetchResponse.from_success_response(oidresp)
90 | ax_fetch_message = "\n\n" + t(:attribute_exchange_data_requested)
91 | unless ax_fetch_resp
92 | ax_fetch_message << ", " + t(:but_none_was_returned)
93 | else
94 | ax_fetch_message << ". " + t(:the_following_data_were_sent) + "\n"
95 | ax_fetch_resp.data.each { |k,v| ax_fetch_message << "#{k}: #{v}\n" }
96 | end
97 | flash[:notice] += ax_fetch_message
98 | end
99 | if params[:did_ax_store]
100 | ax_store_resp = OpenID::AX::StoreResponse.from_success_response(oidresp)
101 | ax_store_message = "\n\n" + t(:attribute_exchange_store_requested)
102 | unless ax_store_resp
103 | ax_store_message << ", " + t(:but_got_no_response)
104 | else
105 | if ax_store_resp.succeeded?
106 | ax_store_message << " " + t(:and_saved_at_the_identity_provider)
107 | else
108 | ax_store_message << ", " + t(:but_an_error_occured, :error_message => ax_store_resp.error_message)
109 | end
110 | end
111 | flash[:notice] += ax_store_message
112 | end
113 | if params[:did_pape]
114 | pape_resp = OpenID::PAPE::Response.from_success_response(oidresp)
115 | pape_message = "\n\n" + t(:authentication_policies_requested)
116 | unless pape_resp.auth_policies.empty?
117 | pape_message << ", " + t(:and_server_reported_the_following) + "\n"
118 | pape_resp.auth_policies.each { |p| pape_message << "#{p}\n" }
119 | else
120 | pape_message << ", " + t(:but_the_server_did_not_report_one)
121 | end
122 | pape_message << "\n" + t(:authentication_time) + ": #{pape_resp.auth_time}" if pape_resp.auth_time
123 | pape_message << "\nNIST Auth Level: #{pape_resp.nist_auth_level}" if pape_resp.nist_auth_level
124 | flash[:notice] += pape_message
125 | end
126 | end
127 | redirect_to :action => 'index'
128 | end
129 |
130 | private
131 |
132 | # OpenID consumer reader, used to access the consumer functionality
133 | def openid_consumer
134 | @openid_consumer ||= OpenID::Consumer.new(session, ActiveRecordStore.new)
135 | end
136 | end
137 | end
138 |
--------------------------------------------------------------------------------
/app/models/masq/account.rb:
--------------------------------------------------------------------------------
1 | require 'digest/sha1'
2 |
3 | module Masq
4 | class Account < ActiveRecord::Base
5 | has_many :personas, :dependent => :delete_all, :order => 'id ASC'
6 | has_many :sites, :dependent => :destroy
7 | belongs_to :public_persona, :class_name => "Persona"
8 |
9 | validates_presence_of :login
10 | validates_length_of :login, :within => 3..254
11 | validates_uniqueness_of :login, :case_sensitive => false
12 | validates_format_of :login, :with => /^[A-Za-z0-9_@.-]+$/
13 | validates_presence_of :email
14 | validates_uniqueness_of :email, :case_sensitive => false
15 | validates_format_of :email, :with => /(^([^@\s]+)@((?:[-_a-z0-9]+\.)+[a-z]{2,})$)|(^$)/i
16 | validates_presence_of :password, :if => :password_required?
17 | validates_presence_of :password_confirmation, :if => :password_required?
18 | validates_length_of :password, :within => 6..40, :if => :password_required?
19 | validates_confirmation_of :password, :if => :password_required?
20 | # check `rake routes' for whether this list is still complete when routes are changed
21 | validates_exclusion_of :login, :in => %w[account session password help safe-login forgot_password reset_password login logout server consumer]
22 |
23 | before_save :encrypt_password
24 | after_save :deliver_forgot_password
25 |
26 | attr_accessible :login, :email, :password, :password_confirmation, :public_persona_id, :yubikey_mandatory
27 | attr_accessor :password
28 |
29 | class ActivationCodeNotFound < StandardError; end
30 | class AlreadyActivated < StandardError
31 | attr_reader :user, :message
32 | def initialize(account, message=nil)
33 | @message, @account = message, account
34 | end
35 | end
36 |
37 | # Finds the user with the corresponding activation code, activates their account and returns the user.
38 | #
39 | # Raises:
40 | # [Account::ActivationCodeNotFound] if there is no user with the corresponding activation code
41 | # [Account::AlreadyActivated] if the user with the corresponding activation code has already activated their account
42 | def self.find_and_activate!(activation_code)
43 | raise ArgumentError if activation_code.nil?
44 | user = find_by_activation_code(activation_code)
45 | raise ActivationCodeNotFound unless user
46 | raise AlreadyActivated.new(user) if user.active?
47 | user.send(:activate!)
48 | user
49 | end
50 |
51 | def to_param
52 | login
53 | end
54 |
55 | # The existence of an activation code means they have not activated yet
56 | def active?
57 | activation_code.nil?
58 | end
59 |
60 | def activate!
61 | @activated = true
62 | self.activated_at = Time.now.utc
63 | self.activation_code = nil
64 | self.save
65 | end
66 |
67 | # True if the user has just been activated
68 | def pending?
69 | @activated
70 | end
71 |
72 | # Does the user have the possibility to authenticate with a one time password?
73 | def has_otp_device?
74 | !yubico_identity.nil?
75 | end
76 |
77 | # Authenticates a user by their login name and password.
78 | # Returns the user or nil.
79 | def self.authenticate(login, password, basic_auth_used=false)
80 | a = Account.find_by_login(login)
81 | if a.nil? and Masq::Engine.config.masq['create_auth_ondemand']['enabled']
82 | # Need to set some password - but is never used
83 | if Masq::Engine.config.masq['create_auth_ondemand']['random_password']
84 | pw = SecureRandom.hex(13)
85 | else
86 | pw = password
87 | end
88 | signup = Signup.create_account!(
89 | :login => login,
90 | :password => pw,
91 | :password_confirmation => pw,
92 | :email => "#{login}@#{Masq::Engine.config.masq['create_auth_ondemand']['default_mail_domain']}")
93 | a = signup.account if signup.succeeded?
94 | end
95 |
96 | if not a.nil? and a.active? and a.enabled
97 | if a.authenticated?(password) or (Masq::Engine.config.masq['trust_basic_auth'] and basic_auth_used)
98 | a.last_authenticated_at, a.last_authenticated_with_yubikey = Time.now, a.authenticated_with_yubikey?
99 | a.save(:validate => false)
100 | return a
101 | end
102 | end
103 | end
104 |
105 | # Encrypts some data with the salt.
106 | def self.encrypt(password, salt)
107 | Digest::SHA1.hexdigest("--#{salt}--#{password}--")
108 | end
109 |
110 | # Encrypts the password with the user salt
111 | def encrypt(password)
112 | self.class.encrypt(password, salt)
113 | end
114 |
115 | def authenticated?(password)
116 | if password.nil?
117 | return false
118 | elsif password.length < 50 && !(yubico_identity? && yubikey_mandatory?)
119 | encrypt(password) == crypted_password
120 | elsif Masq::Engine.config.masq['can_use_yubikey']
121 | password, yubico_otp = Account.split_password_and_yubico_otp(password)
122 | encrypt(password) == crypted_password && @authenticated_with_yubikey = yubikey_authenticated?(yubico_otp)
123 | end
124 | end
125 |
126 | # Is the Yubico OTP valid and belongs to this account?
127 | def yubikey_authenticated?(otp)
128 | if yubico_identity? && Account.verify_yubico_otp(otp)
129 | (Account.extract_yubico_identity_from_otp(otp) == yubico_identity)
130 | else
131 | false
132 | end
133 | end
134 |
135 | def authenticated_with_yubikey?
136 | @authenticated_with_yubikey || false
137 | end
138 |
139 | def associate_with_yubikey(otp)
140 | if Account.verify_yubico_otp(otp)
141 | self.yubico_identity = Account.extract_yubico_identity_from_otp(otp)
142 | save(:validate => false)
143 | else
144 | false
145 | end
146 | end
147 |
148 | def remember_token?
149 | remember_token_expires_at && Time.now.utc < remember_token_expires_at
150 | end
151 |
152 | # These create and unset the fields required for remembering users between browser closes
153 | def remember_me
154 | remember_me_for 2.weeks
155 | end
156 |
157 | def remember_me_for(time)
158 | remember_me_until time.from_now.utc
159 | end
160 |
161 | def remember_me_until(time)
162 | self.remember_token_expires_at = time
163 | self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
164 | save(:validate => false)
165 | end
166 |
167 | def forget_me
168 | self.remember_token_expires_at = nil
169 | self.remember_token = nil
170 | save(:validate => false)
171 | end
172 |
173 | def forgot_password!
174 | @forgotten_password = true
175 | make_password_reset_code
176 | save
177 | end
178 |
179 | # First update the password_reset_code before setting the
180 | # reset_password flag to avoid duplicate email notifications.
181 | def reset_password
182 | update_attribute(:password_reset_code, nil)
183 | @reset_password = true
184 | end
185 |
186 | def recently_forgot_password?
187 | @forgotten_password
188 | end
189 |
190 | def recently_reset_password?
191 | @reset_password
192 | end
193 |
194 | def disable!
195 | update_attribute(:enabled, false)
196 | end
197 |
198 | protected
199 |
200 | def encrypt_password
201 | return if password.blank?
202 | self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
203 | self.crypted_password = encrypt(password)
204 | end
205 |
206 | def password_required?
207 | crypted_password.blank? || !password.blank?
208 | end
209 |
210 | def make_password_reset_code
211 | self.password_reset_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
212 | end
213 |
214 | private
215 |
216 | # Returns the first twelve chars from the Yubico OTP,
217 | # which are used to identify a Yubikey
218 | def self.extract_yubico_identity_from_otp(yubico_otp)
219 | yubico_otp[0..11]
220 | end
221 |
222 | # Recieves a login token which consists of the users password and
223 | # a Yubico one time password (the otp is always 44 characters long)
224 | def self.split_password_and_yubico_otp(token)
225 | token.reverse!
226 | yubico_otp = token.slice!(0..43).reverse
227 | password = token.reverse
228 | [password, yubico_otp]
229 | end
230 |
231 | # Utilizes the Yubico library to verify an one time password
232 | def self.verify_yubico_otp(otp)
233 | begin
234 | Yubikey::OTP::Verify.new(otp).valid?
235 | rescue Yubikey::OTP::InvalidOTPError
236 | false
237 | end
238 | end
239 |
240 | def deliver_forgot_password
241 | AccountMailer.forgot_password(self).deliver if recently_forgot_password?
242 | end
243 |
244 | end
245 | end
246 |
--------------------------------------------------------------------------------