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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +83 -0
- data/CODE_OF_CONDUCT.md +135 -0
- data/CONTRIBUTING.md +151 -0
- data/LICENSE.txt +22 -0
- data/README.md +426 -0
- data/SECURITY.md +23 -0
- data/app/assets/images/masq/favicon.ico +0 -0
- data/app/assets/images/masq/openid_symbol.png +0 -0
- data/app/assets/images/masq/seatbelt_icon.png +0 -0
- data/app/assets/images/masq/seatbelt_icon_gray.png +0 -0
- data/app/assets/images/masq/seatbelt_icon_high.png +0 -0
- data/app/assets/stylesheets/masq/application.css.erb +61 -0
- data/app/controllers/masq/accounts_controller.rb +132 -0
- data/app/controllers/masq/base_controller.rb +78 -0
- data/app/controllers/masq/consumer_controller.rb +144 -0
- data/app/controllers/masq/info_controller.rb +23 -0
- data/app/controllers/masq/passwords_controller.rb +42 -0
- data/app/controllers/masq/personas_controller.rb +75 -0
- data/app/controllers/masq/server_controller.rb +247 -0
- data/app/controllers/masq/sessions_controller.rb +58 -0
- data/app/controllers/masq/sites_controller.rb +60 -0
- data/app/controllers/masq/yubikey_associations_controller.rb +25 -0
- data/app/helpers/masq/application_helper.rb +57 -0
- data/app/helpers/masq/personas_helper.rb +15 -0
- data/app/helpers/masq/server_helper.rb +15 -0
- data/app/mailers/masq/account_mailer.rb +17 -0
- data/app/models/masq/account.rb +245 -0
- data/app/models/masq/open_id_request.rb +42 -0
- data/app/models/masq/persona.rb +61 -0
- data/app/models/masq/release_policy.rb +11 -0
- data/app/models/masq/site.rb +68 -0
- data/app/views/layouts/masq/base.html.erb +70 -0
- data/app/views/layouts/masq/consumer.html.erb +30 -0
- data/app/views/masq/account_mailer/forgot_password.html.erb +3 -0
- data/app/views/masq/account_mailer/forgot_password.text.erb +3 -0
- data/app/views/masq/account_mailer/signup_notification.html.erb +5 -0
- data/app/views/masq/account_mailer/signup_notification.text.erb +5 -0
- data/app/views/masq/accounts/_hcard.html.erb +29 -0
- data/app/views/masq/accounts/edit.html.erb +100 -0
- data/app/views/masq/accounts/new.html.erb +27 -0
- data/app/views/masq/accounts/show.html.erb +4 -0
- data/app/views/masq/accounts/show.xrds.builder +40 -0
- data/app/views/masq/consumer/index.html.erb +31 -0
- data/app/views/masq/consumer/start.html.erb +2 -0
- data/app/views/masq/info/help.html.erb +8 -0
- data/app/views/masq/info/index.html.erb +5 -0
- data/app/views/masq/info/safe_login.html.erb +24 -0
- data/app/views/masq/passwords/edit.html.erb +13 -0
- data/app/views/masq/passwords/new.html.erb +11 -0
- data/app/views/masq/personas/_form.html.erb +159 -0
- data/app/views/masq/personas/edit.html.erb +9 -0
- data/app/views/masq/personas/index.html.erb +17 -0
- data/app/views/masq/personas/new.html.erb +9 -0
- data/app/views/masq/server/decide.html.erb +146 -0
- data/app/views/masq/server/index.xrds.builder +19 -0
- data/app/views/masq/server/seatbelt_config.xml.builder +24 -0
- data/app/views/masq/server/seatbelt_login_state.xml.builder +4 -0
- data/app/views/masq/sessions/new.html.erb +27 -0
- data/app/views/masq/shared/_error_messages.html.erb +12 -0
- data/app/views/masq/sites/edit.html.erb +42 -0
- data/app/views/masq/sites/index.html.erb +20 -0
- data/config/initializers/configuration.rb +5 -0
- data/config/initializers/mime_types.rb +1 -0
- data/config/initializers/requires.rb +6 -0
- data/config/locales/de.yml +281 -0
- data/config/locales/en.yml +271 -0
- data/config/locales/es.yml +254 -0
- data/config/locales/nl.yml +258 -0
- data/config/locales/rails.de.yml +225 -0
- data/config/locales/ru.yml +272 -0
- data/config/masq.example.yml +132 -0
- data/config/routes.rb +41 -0
- data/db/migrate/20120312120000_masq_schema.rb +152 -0
- data/db/migrate/20130626220915_remame_last_authenticated_with_yubikey_on_masq_accounts.rb +11 -0
- data/db/migrate/20130704205532_add_first_and_lastname_columns_to_personas.rb +11 -0
- data/db/migrate/20130807060329_change_open_id_associations_server_url_column_type.rb +22 -0
- data/lib/masq/active_record_openid_store/association.rb +16 -0
- data/lib/masq/active_record_openid_store/nonce.rb +9 -0
- data/lib/masq/active_record_openid_store/openid_ar_store.rb +58 -0
- data/lib/masq/authenticated_system.rb +136 -0
- data/lib/masq/engine.rb +12 -0
- data/lib/masq/openid_server_system.rb +110 -0
- data/lib/masq/signup.rb +53 -0
- data/lib/masq/version.rb +5 -0
- data/lib/masq.rb +50 -0
- data/lib/masq2.rb +1 -0
- data/lib/tasks/masq_tasks.rake +58 -0
- data.tar.gz.sig +2 -0
- metadata +377 -0
- 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
|