├── 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 | 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 | 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 | 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 | 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 | 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 | 23 | 24 | 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 | 30 | 31 | 35 | 36 | <% end %> 37 |
<%=t :property %><%=t :value %><%=t :disclosure %>
<%= label_tag "site_properties_#{property}", property_label_text(property) %><%= label_tag "site_properties_#{property}", value unless value.blank? %> 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 |
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 | 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 | [![Gem Version](https://badge.fury.io/rb/masq.png)](http://badge.fury.io/rb/masq) 4 | [![Build Status](https://secure.travis-ci.org/dennisreimann/masq.png)](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 | 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 | 51 | 52 | 53 | <% (sreg_request.required + sreg_request.optional).each do |property| %> 54 | 55 | 56 | 57 | 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 | 67 | 68 | 73 | 74 | <% end if ax_fetch_request %> 75 |

<%= t(:requested_information_title) %>

<%= t(:disclosure) %>
<%= label_tag "site_sreg_#{property}", property_label_text(property) %><%= label_tag "site_sreg_#{property}", @site.persona.property(property) %> 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 |
<%= label_tag "site_ax_fetch_#{property.ns_alias}", property_label_text(property.ns_alias) %><%= label_tag "site_ax_fetch_#{property.ns_alias}", supported ? @site.persona.property(property.type_uri) : t(:not_supported), :class => supported ? nil : 'note' %> 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 |
76 | <% end %> 77 | 78 | <% if ax_store_request %> 79 | 80 | 81 | 82 | 83 | 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 | 89 | 90 | 91 | 95 | 96 | <% end %> 97 | <% end %> 98 |

<%= t(:sent_information_title) %>

<%= t(:current) %><%= t(:accept) %>
<%= label_tag "site_ax_store_#{attribute}", property_label_text_for_type_uri(type_uri) %><%= label_tag "site_ax_store_#{attribute}", value %><%= label_tag "site_ax_store_#{attribute}", @site.persona.property(type_uri) %> 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 |
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 | --------------------------------------------------------------------------------