masq2 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +83 -0
  4. data/CODE_OF_CONDUCT.md +135 -0
  5. data/CONTRIBUTING.md +151 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +426 -0
  8. data/SECURITY.md +23 -0
  9. data/app/assets/images/masq/favicon.ico +0 -0
  10. data/app/assets/images/masq/openid_symbol.png +0 -0
  11. data/app/assets/images/masq/seatbelt_icon.png +0 -0
  12. data/app/assets/images/masq/seatbelt_icon_gray.png +0 -0
  13. data/app/assets/images/masq/seatbelt_icon_high.png +0 -0
  14. data/app/assets/stylesheets/masq/application.css.erb +61 -0
  15. data/app/controllers/masq/accounts_controller.rb +132 -0
  16. data/app/controllers/masq/base_controller.rb +78 -0
  17. data/app/controllers/masq/consumer_controller.rb +144 -0
  18. data/app/controllers/masq/info_controller.rb +23 -0
  19. data/app/controllers/masq/passwords_controller.rb +42 -0
  20. data/app/controllers/masq/personas_controller.rb +75 -0
  21. data/app/controllers/masq/server_controller.rb +247 -0
  22. data/app/controllers/masq/sessions_controller.rb +58 -0
  23. data/app/controllers/masq/sites_controller.rb +60 -0
  24. data/app/controllers/masq/yubikey_associations_controller.rb +25 -0
  25. data/app/helpers/masq/application_helper.rb +57 -0
  26. data/app/helpers/masq/personas_helper.rb +15 -0
  27. data/app/helpers/masq/server_helper.rb +15 -0
  28. data/app/mailers/masq/account_mailer.rb +17 -0
  29. data/app/models/masq/account.rb +245 -0
  30. data/app/models/masq/open_id_request.rb +42 -0
  31. data/app/models/masq/persona.rb +61 -0
  32. data/app/models/masq/release_policy.rb +11 -0
  33. data/app/models/masq/site.rb +68 -0
  34. data/app/views/layouts/masq/base.html.erb +70 -0
  35. data/app/views/layouts/masq/consumer.html.erb +30 -0
  36. data/app/views/masq/account_mailer/forgot_password.html.erb +3 -0
  37. data/app/views/masq/account_mailer/forgot_password.text.erb +3 -0
  38. data/app/views/masq/account_mailer/signup_notification.html.erb +5 -0
  39. data/app/views/masq/account_mailer/signup_notification.text.erb +5 -0
  40. data/app/views/masq/accounts/_hcard.html.erb +29 -0
  41. data/app/views/masq/accounts/edit.html.erb +100 -0
  42. data/app/views/masq/accounts/new.html.erb +27 -0
  43. data/app/views/masq/accounts/show.html.erb +4 -0
  44. data/app/views/masq/accounts/show.xrds.builder +40 -0
  45. data/app/views/masq/consumer/index.html.erb +31 -0
  46. data/app/views/masq/consumer/start.html.erb +2 -0
  47. data/app/views/masq/info/help.html.erb +8 -0
  48. data/app/views/masq/info/index.html.erb +5 -0
  49. data/app/views/masq/info/safe_login.html.erb +24 -0
  50. data/app/views/masq/passwords/edit.html.erb +13 -0
  51. data/app/views/masq/passwords/new.html.erb +11 -0
  52. data/app/views/masq/personas/_form.html.erb +159 -0
  53. data/app/views/masq/personas/edit.html.erb +9 -0
  54. data/app/views/masq/personas/index.html.erb +17 -0
  55. data/app/views/masq/personas/new.html.erb +9 -0
  56. data/app/views/masq/server/decide.html.erb +146 -0
  57. data/app/views/masq/server/index.xrds.builder +19 -0
  58. data/app/views/masq/server/seatbelt_config.xml.builder +24 -0
  59. data/app/views/masq/server/seatbelt_login_state.xml.builder +4 -0
  60. data/app/views/masq/sessions/new.html.erb +27 -0
  61. data/app/views/masq/shared/_error_messages.html.erb +12 -0
  62. data/app/views/masq/sites/edit.html.erb +42 -0
  63. data/app/views/masq/sites/index.html.erb +20 -0
  64. data/config/initializers/configuration.rb +5 -0
  65. data/config/initializers/mime_types.rb +1 -0
  66. data/config/initializers/requires.rb +6 -0
  67. data/config/locales/de.yml +281 -0
  68. data/config/locales/en.yml +271 -0
  69. data/config/locales/es.yml +254 -0
  70. data/config/locales/nl.yml +258 -0
  71. data/config/locales/rails.de.yml +225 -0
  72. data/config/locales/ru.yml +272 -0
  73. data/config/masq.example.yml +132 -0
  74. data/config/routes.rb +41 -0
  75. data/db/migrate/20120312120000_masq_schema.rb +152 -0
  76. data/db/migrate/20130626220915_remame_last_authenticated_with_yubikey_on_masq_accounts.rb +11 -0
  77. data/db/migrate/20130704205532_add_first_and_lastname_columns_to_personas.rb +11 -0
  78. data/db/migrate/20130807060329_change_open_id_associations_server_url_column_type.rb +22 -0
  79. data/lib/masq/active_record_openid_store/association.rb +16 -0
  80. data/lib/masq/active_record_openid_store/nonce.rb +9 -0
  81. data/lib/masq/active_record_openid_store/openid_ar_store.rb +58 -0
  82. data/lib/masq/authenticated_system.rb +136 -0
  83. data/lib/masq/engine.rb +12 -0
  84. data/lib/masq/openid_server_system.rb +110 -0
  85. data/lib/masq/signup.rb +53 -0
  86. data/lib/masq/version.rb +5 -0
  87. data/lib/masq.rb +50 -0
  88. data/lib/masq2.rb +1 -0
  89. data/lib/tasks/masq_tasks.rake +58 -0
  90. data.tar.gz.sig +2 -0
  91. metadata +377 -0
  92. metadata.gz.sig +0 -0
@@ -0,0 +1,78 @@
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,
12
+ :extract_login_from_identifier,
13
+ :checkid_request,
14
+ :identifier,
15
+ :endpoint_url,
16
+ :scheme,
17
+ :email_as_login?
18
+
19
+ protected
20
+
21
+ def endpoint_url
22
+ server_url(protocol: scheme)
23
+ end
24
+
25
+ # Returns the OpenID identifier for an account
26
+ def identifier(account)
27
+ identity_url(account: account, protocol: scheme)
28
+ end
29
+
30
+ # Extracts the hostname from the given url, which is used to
31
+ # display the name of the requesting website to the user
32
+ def extract_host(u)
33
+ URI.split(u).compact[1]
34
+ end
35
+
36
+ def extract_login_from_identifier(openid_url)
37
+ openid_url.gsub(/^https?:\/\/.*\//, "")
38
+ end
39
+
40
+ def checkid_request
41
+ unless @checkid_request ||= nil
42
+ req = openid_server.decode_request(current_openid_request.parameters) if current_openid_request
43
+ @checkid_request = req.is_a?(OpenID::Server::CheckIDRequest) ? req : false
44
+ end
45
+ @checkid_request
46
+ end
47
+
48
+ def current_openid_request
49
+ @current_openid_request ||= OpenIdRequest.find_by(token: session[:request_token]) if session[:request_token]
50
+ end
51
+
52
+ def render_404
53
+ render_error(404)
54
+ end
55
+
56
+ def render_422
57
+ render_error(422)
58
+ end
59
+
60
+ def render_500
61
+ render_error(500)
62
+ end
63
+
64
+ def render_error(status_code)
65
+ render(file: "#{Rails.root}/public/#{status_code}.html", formats: [:html], status: status_code, layout: false)
66
+ end
67
+
68
+ private
69
+
70
+ def scheme
71
+ Masq::Engine.config.masq["use_ssl"] ? "https" : "http"
72
+ end
73
+
74
+ def email_as_login?
75
+ Masq::Engine.config.masq["email_as_login"]
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,144 @@
1
+ module Masq
2
+ class ConsumerController < BaseController
3
+ skip_before_action :verify_authenticity_token
4
+
5
+ def index
6
+ end
7
+
8
+ def start
9
+ begin
10
+ open_id_req = openid_consumer.begin(params[:openid_identifier])
11
+ rescue OpenID::OpenIDError => e
12
+ redirect_to(consumer_path, alert: "Discovery failed for #{params[:openid_identifier]}: #{e}")
13
+ return
14
+ end
15
+ if params[:use_sreg]
16
+ open_id_sreg_req = OpenID::SReg::Request.new
17
+ open_id_sreg_req.policy_url = "http://www.policy-url.com"
18
+ open_id_sreg_req.request_fields(["nickname", "email"], true) # required
19
+ open_id_sreg_req.request_fields(["fullname", "dob"], false) # optional
20
+ open_id_req.add_extension(open_id_sreg_req)
21
+ open_id_req.return_to_args["did_sreg"] = "y"
22
+ end
23
+ if params[:use_ax_fetch]
24
+ axreq = OpenID::AX::FetchRequest.new
25
+ requested_attrs = [
26
+ ["https://openid.tzi.de/spec/schema", "uid", true],
27
+ ["http://axschema.org/namePerson/friendly", "nickname", true],
28
+ ["http://axschema.org/contact/email", "email", true],
29
+ ["http://axschema.org/namePerson", "fullname"],
30
+ ["http://axschema.org/contact/web/default", "website", false, 2],
31
+ ["http://axschema.org/contact/postalCode/home", "postcode"],
32
+ ["http://axschema.org/person/gender", "gender"],
33
+ ["http://axschema.org/birthDate", "birth_date"],
34
+ ["http://axschema.org/contact/country/home", "country"],
35
+ ["http://axschema.org/pref/language", "language"],
36
+ ["http://axschema.org/pref/timezone", "timezone"],
37
+ ]
38
+ requested_attrs.each { |a| axreq.add(OpenID::AX::AttrInfo.new(a[0], a[1], a[2] || false, a[3] || 1)) }
39
+ open_id_req.add_extension(axreq)
40
+ open_id_req.return_to_args["did_ax_fetch"] = "y"
41
+ end
42
+ if params[:use_ax_store]
43
+ ax_store_req = OpenID::AX::StoreRequest.new
44
+ ax_store_req.set_values("http://axschema.org/contact/email", %w(email@example.com))
45
+ ax_store_req.set_values("http://axschema.org/birthDate", %w(1976-08-07))
46
+ ax_store_req.set_values("http://axschema.org/customValueThatIsNotSupported", %w(unsupported))
47
+ open_id_req.add_extension(ax_store_req)
48
+ open_id_req.return_to_args["did_ax_store"] = "y"
49
+ end
50
+ if params[:use_pape]
51
+ open_id_pape_req = OpenID::PAPE::Request.new
52
+ open_id_pape_req.add_policy_uri(OpenID::PAPE::AUTH_PHISHING_RESISTANT)
53
+ open_id_pape_req.max_auth_age = 60
54
+ open_id_req.add_extension(open_id_pape_req)
55
+ open_id_req.return_to_args["did_pape"] = "y"
56
+ end
57
+ if params[:force_post]
58
+ open_id_req.return_to_args["force_post"] = "x" * 2048
59
+ end
60
+ if open_id_req.send_redirect?(consumer_url, consumer_complete_url, params[:immediate])
61
+ redirect_to(open_id_req.redirect_url(consumer_url, consumer_complete_url, params[:immediate]))
62
+ else
63
+ @form_text = open_id_req.form_markup(consumer_url, consumer_complete_url, params[:immediate], {"id" => "checkid_form"})
64
+ end
65
+ end
66
+
67
+ def complete
68
+ parameters = params.to_unsafe_h.reject { |k, _v| request.path_parameters[k.to_sym] }
69
+ open_id_req = openid_consumer.complete(parameters, url_for({}))
70
+ case open_id_req.status
71
+ when OpenID::Consumer::SETUP_NEEDED
72
+ flash[:alert] = t(:immediate_request_failed_setup_needed)
73
+ when OpenID::Consumer::CANCEL
74
+ flash[:alert] = t(:openid_transaction_cancelled)
75
+ when OpenID::Consumer::FAILURE
76
+ flash[:alert] = open_id_req.display_identifier ?
77
+ t(:verification_of_identifier_failed, identifier: open_id_req.display_identifier, message: open_id_req.message) :
78
+ t(:verification_failed_message, message: open_id_req.message)
79
+ when OpenID::Consumer::SUCCESS
80
+ flash[:notice] = t(:verification_of_identifier_succeeded, identifier: open_id_req.display_identifier)
81
+ if params[:did_sreg]
82
+ sreg_resp = OpenID::SReg::Response.from_success_response(open_id_req)
83
+ sreg_message = "\n\n" + t(:simple_registration_data_requested)
84
+ if sreg_resp.empty?
85
+ sreg_message << ", " + t(:but_none_was_returned)
86
+ else
87
+ sreg_message << ". " + t(:the_following_data_were_sent) + "\n"
88
+ sreg_resp.data.each { |k, v| sreg_message << "#{k}: #{v}\n" }
89
+ end
90
+ flash[:notice] += sreg_message
91
+ end
92
+ if params[:did_ax_fetch]
93
+ ax_fetch_resp = OpenID::AX::FetchResponse.from_success_response(open_id_req)
94
+ ax_fetch_message = "\n\n" + t(:attribute_exchange_data_requested)
95
+ if ax_fetch_resp
96
+ ax_fetch_message << ". " + t(:the_following_data_were_sent) + "\n"
97
+ ax_fetch_resp.data.each { |k, v| ax_fetch_message << "#{k}: #{v}\n" }
98
+ else
99
+ ax_fetch_message << ", " + t(:but_none_was_returned)
100
+ end
101
+ flash[:notice] += ax_fetch_message
102
+ end
103
+ if params[:did_ax_store]
104
+ ax_store_resp = OpenID::AX::StoreResponse.from_success_response(open_id_req)
105
+ ax_store_message = "\n\n" + t(:attribute_exchange_store_requested)
106
+ ax_store_message << if ax_store_resp
107
+ if ax_store_resp.succeeded?
108
+ " " + t(:and_saved_at_the_identity_provider)
109
+ else
110
+ ", " + t(:but_an_error_occured, error_message: ax_store_resp.error_message)
111
+ end
112
+ else
113
+ ", " + t(:but_got_no_response)
114
+ end
115
+ flash[:notice] += ax_store_message
116
+ end
117
+ if params[:did_pape]
118
+ pape_resp = OpenID::PAPE::Response.from_success_response(open_id_req)
119
+ pape_message = "\n\n" + t(:authentication_policies_requested)
120
+ if pape_resp.auth_policies.empty?
121
+ pape_message << ", " + t(:but_the_server_did_not_report_one)
122
+ else
123
+ pape_message << ", " + t(:and_server_reported_the_following) + "\n"
124
+ pape_resp.auth_policies.each { |p| pape_message << "#{p}\n" }
125
+ end
126
+ pape_message << "\n" + t(:authentication_time) + ": #{pape_resp.auth_time}" if pape_resp.auth_time
127
+ pape_message << "\nNIST Auth Level: #{pape_resp.nist_auth_level}" if pape_resp.nist_auth_level
128
+ flash[:notice] += pape_message
129
+ end
130
+ else
131
+ # NOOP
132
+ # This should never happen.
133
+ end
134
+ redirect_to(action: "index")
135
+ end
136
+
137
+ private
138
+
139
+ # OpenID consumer reader, used to access the consumer functionality
140
+ def openid_consumer
141
+ @openid_consumer ||= OpenID::Consumer.new(session, ActiveRecordStore.new)
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,23 @@
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 !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
@@ -0,0 +1,42 @@
1
+ module Masq
2
+ class PasswordsController < BaseController
3
+ before_action :check_can_change_password, only: [:create, :edit, :update]
4
+ before_action :find_account_by_reset_code, only: [:edit, :update]
5
+
6
+ # Forgot password
7
+ def create
8
+ if account = Account.find_by(email: params[:email], activation_code: nil)
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
+ if params[:password].blank?
20
+ flash[:alert] = t(:password_cannot_be_blank)
21
+ render(action: "edit")
22
+ elsif @account.update(password: params[:password], password_confirmation: params[:password_confirmation])
23
+ redirect_to(login_path, notice: t(:password_reset))
24
+ else
25
+ flash[:alert] = t(:password_mismatch)
26
+ render(action: "edit")
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def find_account_by_reset_code
33
+ @reset_code = params[:id]
34
+ @account = @reset_code.blank? ? nil : Account.find_by(password_reset_code: @reset_code)
35
+ redirect_to(forgot_password_path, alert: t(:reset_code_invalid_try_again)) unless @account
36
+ end
37
+
38
+ def check_can_change_password
39
+ render_404 unless Masq::Engine.config.masq["can_change_password"]
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,75 @@
1
+ module Masq
2
+ class PersonasController < BaseController
3
+ before_action :login_required
4
+ before_action :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 new
17
+ @persona = current_account.personas.new
18
+ end
19
+
20
+ def create
21
+ respond_to do |format|
22
+ if persona.save!
23
+ flash[:notice] = t(:persona_successfully_created)
24
+ format.html { redirect_back_or_default(account_personas_path) }
25
+ else
26
+ format.html { render(action: "new") }
27
+ end
28
+ end
29
+ end
30
+
31
+ def update
32
+ respond_to do |format|
33
+ if persona.update(persona_params)
34
+ flash[:notice] = t(:persona_updated)
35
+ format.html { redirect_back_or_default(account_personas_path) }
36
+ else
37
+ format.html { render(action: "edit") }
38
+ end
39
+ end
40
+ end
41
+
42
+ def destroy
43
+ respond_to do |format|
44
+ unless persona.destroy
45
+ flash[:alert] = t(:persona_cannot_be_deleted)
46
+ end
47
+ format.html { redirect_to(account_personas_path) }
48
+ end
49
+ end
50
+
51
+ protected
52
+
53
+ def persona
54
+ @persona ||= params[:id].present? ?
55
+ current_account.personas.find(params[:id]) :
56
+ current_account.personas.new(persona_params)
57
+ end
58
+
59
+ def persona_params
60
+ rejected_keys = [:created_at, :updated_at, :account_id, :deletable]
61
+ params.require(:persona).permit!.except(rejected_keys)
62
+ end
63
+
64
+ def redirect_back_or_default(default)
65
+ case session[:return_to]
66
+ when decide_path then redirect_to(decide_path(persona_id: persona.id))
67
+ else super
68
+ end
69
+ end
70
+
71
+ def store_return_url
72
+ store_location(params[:return]) unless params[:return].blank?
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,247 @@
1
+ module Masq
2
+ class ServerController < BaseController
3
+ # CSRF-protection must be skipped, because incoming
4
+ # OpenID requests lack an authenticity token
5
+ skip_before_action :verify_authenticity_token
6
+ # Error handling
7
+ rescue_from OpenID::Server::ProtocolError, with: :render_openid_error
8
+ # Actions other than index require a logged-in user
9
+ before_action :login_required, except: %i[index cancel seatbelt_config seatbelt_login_state]
10
+ before_action :ensure_valid_checkid_request, except: %i[index cancel seatbelt_config seatbelt_login_state]
11
+ after_action :clear_checkid_request, only: %i[cancel complete]
12
+ # These methods are used to display information about the request to the user
13
+ helper_method :sreg_request, :ax_fetch_request, :ax_store_request
14
+
15
+ # This is the server endpoint which handles all incoming OpenID requests.
16
+ # Associate and CheckAuth requests are answered directly - functionality
17
+ # therefor is provided by the ruby-openid gem. Handling of CheckId requests
18
+ # dependents on the users login state (see handle_checkid_request).
19
+ # Yadis requests return information about this endpoint.
20
+ def index
21
+ clear_checkid_request
22
+ respond_to do |format|
23
+ format.html do
24
+ if openid_request.is_a?(OpenID::Server::CheckIDRequest)
25
+ handle_checkid_request
26
+ elsif openid_request
27
+ handle_non_checkid_request
28
+ else
29
+ render(plain: t(:this_is_openid_not_a_human_resource))
30
+ end
31
+ end
32
+ format.xrds
33
+ end
34
+ end
35
+
36
+ # This action decides how to process the current request and serves as
37
+ # dispatcher and re-entry in case the request could not be processed
38
+ # directly (for instance if the user had to log in first).
39
+ # When the user has already trusted the relying party, the request will
40
+ # be answered based on the users release policy. If the request is immediate
41
+ # (relying party wants no user interaction, used e.g. for ajax requests)
42
+ # the request can only be answered if no further information (like simple
43
+ # registration data) is requested. Otherwise, the user will be redirected
44
+ # to the decision page.
45
+ def proceed
46
+ identity = identifier(current_account)
47
+ @site = current_account.sites.find_by(url: checkid_request.trust_root)
48
+ if @site
49
+ resp = checkid_request.answer(true, nil, identity)
50
+ resp = add_sreg(resp, @site.sreg_properties) if sreg_request
51
+ resp = add_ax(resp, @site.ax_properties) if ax_fetch_request
52
+ resp = add_pape(resp, auth_policies, auth_level, auth_time)
53
+ render_response(resp)
54
+ elsif checkid_request.immediate && (sreg_request || ax_store_request || ax_fetch_request)
55
+ render_response(checkid_request.answer(false))
56
+ elsif checkid_request.immediate
57
+ render_response(checkid_request.answer(true, nil, identity))
58
+ else
59
+ redirect_to(decide_path)
60
+ end
61
+ end
62
+
63
+ # Displays the decision page on that the user can confirm the request and
64
+ # choose which data should be transferred to the relying party.
65
+ def decide
66
+ @site = current_account.sites.where(url: checkid_request.trust_root).first_or_initialize
67
+ @site.persona = current_account.personas.find_by(params[:persona_id]) || current_account.personas.first if sreg_request || ax_store_request || ax_fetch_request
68
+ end
69
+
70
+ # This action is called by submitting the decision form, the information entered by
71
+ # the user is used to answer the request. If the user decides to always trust the
72
+ # relying party, a new site according to the release policies will be created.
73
+ def complete
74
+ if params[:cancel]
75
+ cancel
76
+ else
77
+ resp = checkid_request.answer(true, nil, identifier(current_account))
78
+ if params[:always]
79
+ @site = current_account.sites.where(persona_id: params[:site][:persona_id], url: params[:site][:url]).first_or_create
80
+ @site.update(site_params)
81
+ elsif sreg_request || ax_fetch_request
82
+ @site = current_account.sites.where(persona_id: params[:site][:persona_id], url: params[:site][:url]).first_or_create
83
+ @site.attributes = site_params
84
+ elsif ax_store_request
85
+ @site = current_account.sites.where(persona_id: params[:site][:persona_id], url: params[:site][:url]).first_or_create
86
+ not_supported = []
87
+ not_accepted = []
88
+ accepted = []
89
+ ax_store_request.data.each do |type_uri, values|
90
+ property = Persona.attribute_name_for_type_uri(type_uri)
91
+ if property
92
+ store_attribute = params[:site][:ax_store][property.to_sym]
93
+ if store_attribute && !store_attribute[:value].blank?
94
+ @site.persona.update_attribute(property, values.first)
95
+ accepted << type_uri
96
+ else
97
+ not_accepted << type_uri
98
+ end
99
+ else
100
+ not_supported << type_uri
101
+ end
102
+ end
103
+ ax_store_response = (accepted.count > 0) ? OpenID::AX::StoreResponse.new : OpenID::AX::StoreResponse.new(false, "None of the attributes were accepted.")
104
+ resp.add_extension(ax_store_response)
105
+ end
106
+ resp = add_pape(resp, auth_policies, auth_level, auth_time)
107
+ resp = add_sreg(resp, @site.sreg_properties) if sreg_request && @site.sreg_properties
108
+ resp = add_ax(resp, @site.ax_properties) if ax_fetch_request && @site.ax_properties
109
+ render_response(resp)
110
+ end
111
+ end
112
+
113
+ # Cancels the current OpenID request
114
+ def cancel
115
+ if checkid_request
116
+ redirect_to(checkid_request.cancel_url)
117
+ else
118
+ reset_session
119
+ redirect_to(login_path)
120
+ end
121
+ end
122
+
123
+ protected
124
+
125
+ # Decides how to process an incoming checkid request. If the user is
126
+ # already logged in he will be forwarded to the proceed action. If
127
+ # the user is not logged in and the request is immediate, the request
128
+ # cannot be answered successfully. In case the user is not logged in,
129
+ # the request will be stored and the user is asked to log in.
130
+ def handle_checkid_request
131
+ if allow_verification?
132
+ save_checkid_request
133
+ redirect_to(proceed_path)
134
+ elsif openid_request.immediate
135
+ render_response(openid_request.answer(false))
136
+ else
137
+ reset_session
138
+ request = save_checkid_request
139
+ session[:return_to] = proceed_path
140
+ redirect_to(request.from_trusted_domain? ? login_path : safe_login_path)
141
+ end
142
+ end
143
+
144
+ # Stores the current OpenID request.
145
+ # Returns the OpenIdRequest
146
+ def save_checkid_request
147
+ clear_checkid_request
148
+ request = OpenIdRequest.create!(parameters: openid_params)
149
+ session[:request_token] = request.token
150
+
151
+ request
152
+ end
153
+
154
+ # Deletes the old request when a new one comes in.
155
+ def clear_checkid_request
156
+ unless session[:request_token].blank?
157
+ OpenIdRequest.where(token: session[:request_token]).destroy_all
158
+ session[:request_token] = nil
159
+ end
160
+ end
161
+
162
+ # Use this as before_action for every CheckID request based action.
163
+ # Loads the current openid request and cancels if none can be found.
164
+ # The user has to log in, if he has not verified his ownership of
165
+ # the identifier, yet.
166
+ def ensure_valid_checkid_request
167
+ self.openid_request = checkid_request
168
+ if !openid_request.is_a?(OpenID::Server::CheckIDRequest)
169
+ redirect_to(root_path, alert: t(:identity_verification_request_invalid))
170
+ elsif !allow_verification?
171
+ flash[:notice] = (logged_in? && !pape_requirements_met?(auth_time)) ?
172
+ t(:service_provider_requires_reauthentication_last_login_too_long_ago) :
173
+ t(:login_to_verify_identity)
174
+ session[:return_to] = proceed_path
175
+ redirect_to(login_path)
176
+ end
177
+ end
178
+
179
+ # The user must be logged in, he must be the owner of the claimed identifier
180
+ # and the PAPE requirements must be met if applicable.
181
+ def allow_verification?
182
+ logged_in? && correct_identifier? && pape_requirements_met?(auth_time)
183
+ end
184
+
185
+ # Is the user allowed to verify the claimed identifier? The user
186
+ # must be logged in, so that we know his identifier or the identifier
187
+ # has to be selected by the server (id_select).
188
+ def correct_identifier?
189
+ openid_request.identity == identifier(current_account) || openid_request.id_select
190
+ end
191
+
192
+ # Clears the stored request and answers
193
+ def render_response(resp)
194
+ clear_checkid_request
195
+ render_openid_response(resp)
196
+ end
197
+
198
+ # Transforms the parameters from the form to valid AX response values
199
+ def transform_ax_data(parameters)
200
+ data = {}
201
+ parameters.each_pair do |key, details|
202
+ if details["value"]
203
+ data["type.#{key}"] = details["type"]
204
+ data["value.#{key}"] = details["value"]
205
+ end
206
+ end
207
+ data
208
+ end
209
+
210
+ # Renders the exception message as text output
211
+ def render_openid_error(exception)
212
+ error = case exception
213
+ when OpenID::Server::MalformedTrustRoot then "Malformed trust root '#{exception}'"
214
+ else exception.to_s
215
+ end
216
+ render(plain: "Invalid OpenID request: #{error}", status: 500)
217
+ end
218
+
219
+ private
220
+
221
+ # The NIST Assurance Level, see:
222
+ # http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#anchor12
223
+ def auth_level
224
+ if Masq::Engine.config.masq["use_ssl"]
225
+ current_account.last_authenticated_by_yubikey? ? 3 : 2
226
+ else
227
+ 0
228
+ end
229
+ end
230
+
231
+ def auth_time
232
+ current_account.last_authenticated_at
233
+ end
234
+
235
+ def auth_policies
236
+ current_account.last_authenticated_by_yubikey? ?
237
+ [OpenID::PAPE::AUTH_MULTI_FACTOR, OpenID::PAPE::AUTH_PHISHING_RESISTANT] :
238
+ []
239
+ end
240
+
241
+ def site_params
242
+ authorized_params = params.require(:site).permit(:persona_id, :url)
243
+ additional_data = params[:site].slice(:ax_fetch, :sreg, :properties).permit!
244
+ authorized_params.merge(additional_data)
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,58 @@
1
+ module Masq
2
+ class SessionsController < BaseController
3
+ before_action :login_required, only: :destroy
4
+ after_action :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 = Masq::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 = Masq::Account.find_by(login: params[:login])
17
+ if a.nil?
18
+ redirect_to(login_path(error: "incorrect-login"), alert: t(:login_incorrect))
19
+ elsif a.active? && a.enabled?
20
+ redirect_to(login_path(error: "incorrect-password"), alert: t(:password_incorrect))
21
+ elsif !a.enabled?
22
+ redirect_to(login_path(error: "deactivated"), alert: t(:account_deactivated))
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
+ }
45
+ end
46
+ end
47
+
48
+ def redirect_after_login
49
+ return_to = session[:return_to]
50
+ if return_to
51
+ session[:return_to] = nil
52
+ redirect_to(return_to)
53
+ else
54
+ redirect_to(identifier(current_account))
55
+ end
56
+ end
57
+ end
58
+ end