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,58 @@
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(
9
+ server_url: server_url,
10
+ handle: assoc.handle,
11
+ secret: assoc.secret,
12
+ issued: assoc.issued,
13
+ lifetime: assoc.lifetime,
14
+ assoc_type: assoc.assoc_type,
15
+ )
16
+ end
17
+
18
+ def get_association(server_url, handle = nil)
19
+ assocs = if handle.blank?
20
+ Association.where(server_url: server_url)
21
+ else
22
+ Association.where(server_url: server_url, handle: handle)
23
+ end
24
+
25
+ assocs.reverse_each do |assoc|
26
+ a = assoc.from_record
27
+ if a.expires_in == 0
28
+ assoc.destroy
29
+ else
30
+ return a
31
+ end
32
+ end if assocs.any?
33
+
34
+ nil
35
+ end
36
+
37
+ def remove_association(server_url, handle)
38
+ Association.where("server_url = ? AND handle = ?", server_url, handle).delete_all > 0
39
+ end
40
+
41
+ def use_nonce(server_url, timestamp, salt)
42
+ return false if Nonce.find_by(server_url: server_url, timestamp: timestamp, salt: salt)
43
+ return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
44
+ Nonce.create(server_url: server_url, timestamp: timestamp, salt: salt)
45
+ true
46
+ end
47
+
48
+ def cleanup_nonces
49
+ now = Time.now.to_i
50
+ Nonce.where("timestamp > ? OR timestamp < ?", now + OpenID::Nonce.skew, now - OpenID::Nonce.skew).delete_all
51
+ end
52
+
53
+ def cleanup_associations
54
+ now = Time.now.to_i
55
+ Association.where("issued + lifetime > ?", now).delete_all
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,136 @@
1
+ module Masq
2
+ module AuthenticatedSystem
3
+ protected
4
+
5
+ # Returns true or false if the account is logged in.
6
+ # Preloads @current_account with the account model if they're logged in.
7
+ def logged_in?
8
+ current_account != :false
9
+ end
10
+
11
+ # Accesses the current account from the session. Set it to :false if login fails
12
+ # so that future calls do not hit the database.
13
+ def current_account
14
+ @current_account ||= login_from_session || login_from_basic_auth || login_from_cookie || :false
15
+ end
16
+
17
+ # Store the given account id in the session.
18
+ def current_account=(new_account)
19
+ if auth_type_used != :basic
20
+ session[:account_id] = (new_account.nil? || new_account.is_a?(Symbol)) ? nil : new_account.id
21
+ end
22
+ @current_account = new_account || :false
23
+ end
24
+
25
+ # Check if the account is authorized
26
+ #
27
+ # Override this method in your controllers if you want to restrict access
28
+ # to only a few actions or if you want to check if the account
29
+ # has the correct rights.
30
+ #
31
+ # Example:
32
+ #
33
+ # # only allow nonbobs
34
+ # def authorized?
35
+ # current_account.login != "bob"
36
+ # end
37
+ def authorized?
38
+ logged_in?
39
+ end
40
+
41
+ # Filter method to enforce a login requirement.
42
+ #
43
+ # To require logins for all actions, use this in your controllers:
44
+ #
45
+ # before_action :login_required
46
+ #
47
+ # To require logins for specific actions, use this in your controllers:
48
+ #
49
+ # before_action :login_required, :only => [ :edit, :update ]
50
+ #
51
+ # To skip this in a subclassed controller:
52
+ #
53
+ # skip_before_action :login_required
54
+ #
55
+ def login_required
56
+ authorized? || access_denied
57
+ end
58
+
59
+ # Redirect as appropriate when an access request fails.
60
+ #
61
+ # The default action is to redirect to the login screen.
62
+ #
63
+ # Override this method in your controllers if you want to have special
64
+ # behavior in case the account is not authorized
65
+ # to access the requested action. For example, a popup window might
66
+ # simply close itself.
67
+ def access_denied
68
+ respond_to do |format|
69
+ format.html do
70
+ store_location
71
+ redirect_to(login_path)
72
+ end
73
+ format.any do
74
+ request_http_basic_authentication("Web Password")
75
+ end
76
+ end
77
+ end
78
+
79
+ # Store the URI of the current request in the session.
80
+ #
81
+ # We can return to this location by calling #redirect_back_or_default.
82
+ def store_location(url = request.fullpath)
83
+ session[:return_to] = url
84
+ end
85
+
86
+ # Redirect to the URI stored by the most recent store_location call or
87
+ # to the passed default.
88
+ def redirect_back_or_default(default)
89
+ redirect_to(session[:return_to] || default)
90
+ session[:return_to] = nil
91
+ end
92
+
93
+ # Inclusion hook to make #current_account and #logged_in?
94
+ # available as ActionView helper methods.
95
+ def self.included(base)
96
+ base.send(:helper_method, :current_account, :logged_in?, :auth_type_used)
97
+ end
98
+
99
+ # Called from #current_account. First attempt to log in by the account id stored in the session.
100
+ def login_from_session
101
+ account = Account.find(session[:account_id]) if session[:account_id]
102
+ self.auth_type_used = :session if !account.nil?
103
+ self.current_account = account
104
+ end
105
+
106
+ def auth_type_used
107
+ @auth_type_used if defined?(@auth_type_used)
108
+ end
109
+
110
+ def auth_type_used= t
111
+ @auth_type_used = t
112
+ end
113
+
114
+ # Called from #current_account. Now, attempt to log in by basic authentication information.
115
+ def login_from_basic_auth
116
+ authenticate_with_http_basic do |accountname, password|
117
+ account = Account.authenticate(accountname, password, true)
118
+ self.auth_type_used = :basic if !account.nil?
119
+ self.current_account = account
120
+ account
121
+ end
122
+ end
123
+
124
+ # Called from #current_account. Finally, attempt to log in by an expiring token in the cookie.
125
+ def login_from_cookie
126
+ account = cookies[:auth_token] && Account.find_by(remember_token: cookies[:auth_token])
127
+ if account && account.remember_token?
128
+ account.remember_me
129
+ cookies[:auth_token] = {value: account.remember_token, expires: account.remember_token_expires_at}
130
+ self.auth_type_used = :cookie if !account.nil?
131
+ self.current_account = account
132
+ account
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,12 @@
1
+ require "rails/engine"
2
+
3
+ module Masq
4
+ class Engine < ::Rails::Engine
5
+ # An isolated engine will set its name according to namespace,
6
+ # so Masq::Engine.engine_name will be “masq”.
7
+ # It will also set Masq.table_name_prefix to “masq_”,
8
+ # changing the MyEngine::Persona model to use the masq_personas table
9
+ # See: http://edgeapi.rubyonrails.org/classes/Rails/Engine.html#label-Isolated+Engine
10
+ isolate_namespace Masq
11
+ end
12
+ end
@@ -0,0 +1,110 @@
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.permit(params.keys.select { |k| k.start_with?("openid.") })
24
+ end
25
+
26
+ # OpenID request accessor
27
+ def openid_request
28
+ @openid_request ||= openid_server.decode_request(openid_params.to_h)
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 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
+ 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(plain: web_response.body, status: 200)
97
+ when OpenID::Server::HTTP_REDIRECT then redirect_to(web_response.headers["location"])
98
+ else render(plain: 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
@@ -0,0 +1,53 @@
1
+ require "digest/sha1"
2
+
3
+ module Masq
4
+ class Signup
5
+ attr_accessor :account
6
+
7
+ class << self
8
+ def create_account!(attrs = {})
9
+ factory = new(attrs)
10
+ factory.send(:create_account!)
11
+ factory
12
+ end
13
+ end
14
+
15
+ def succeeded?
16
+ !account.new_record?
17
+ end
18
+
19
+ def send_activation_email?
20
+ Masq::Engine.config.masq["send_activation_mail"]
21
+ end
22
+
23
+ protected
24
+
25
+ def initialize(attrs = {})
26
+ self.account = Masq::Account.new(attrs)
27
+ end
28
+
29
+ def create_account!
30
+ return false unless account.valid?
31
+
32
+ make_activation_code if send_activation_email?
33
+ account.save!
34
+ make_default_persona
35
+ if send_activation_email?
36
+ Masq::AccountMailer.signup_notification(account).deliver_now
37
+ else
38
+ account.activate!
39
+ end
40
+ account
41
+ end
42
+
43
+ def make_activation_code
44
+ account.activation_code = Digest::SHA1.hexdigest(Time.now.to_s.split("").sort_by { rand }.join)
45
+ end
46
+
47
+ def make_default_persona
48
+ account.public_persona = account.personas.build(title: "Standard", email: account.email)
49
+ account.public_persona.deletable = false
50
+ account.public_persona.save!
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,5 @@
1
+ module Masq
2
+ module Version
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
data/lib/masq.rb ADDED
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # external gems
4
+ require "version_gem"
5
+
6
+ # this library's version
7
+ require_relative "masq/version"
8
+
9
+ module Masq
10
+ # Load order matters here!
11
+ # This gem must be loaded **after** Rails in order for the Engine to register itself automatically.
12
+ # Otherwise, you'd have to manually require what you need from below.
13
+ if defined?(::Rails) && defined?(::Rails::VERSION)
14
+ begin
15
+ require_relative "masq/engine"
16
+ require_relative "masq/authenticated_system"
17
+ require_relative "masq/openid_server_system"
18
+ require_relative "masq/active_record_openid_store/association"
19
+ require_relative "masq/active_record_openid_store/nonce"
20
+ require_relative "masq/active_record_openid_store/openid_ar_store"
21
+ require_relative "masq/signup"
22
+ rescue StandardError, LoadError => error
23
+ if !defined?(::Rails::Engine)
24
+ warn("masq2 is a Rails engine, but Rails::Engine isn't defined.")
25
+ else
26
+ warn(<<~WARNING)
27
+ Unable to load masq2. Please check your configuration.
28
+ If you're using Rails 5.2 or later, you should add masq2 to your Gemfile and run `bundle install`.
29
+ Then add:
30
+ require "masq/engine"
31
+
32
+ If unable to resolve, please report a bug to the issue tracker at https://github.com/oauth-xx/masq2
33
+
34
+ Original Error:
35
+ #{error.class}: #{error.message}
36
+
37
+ BACKTRACE:
38
+ #{Array(error.backtrace).join("\n")}
39
+ WARNING
40
+ end
41
+ end
42
+ else
43
+ warn("masq2 was loaded before Rails. Please check your configuration.")
44
+ end
45
+ end
46
+
47
+ # Ensure version is configured before loading the rest of the library
48
+ Masq::Version.class_eval do
49
+ extend VersionGem::Basic
50
+ end
data/lib/masq2.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative "masq"
@@ -0,0 +1,58 @@
1
+ namespace :masq do
2
+ namespace :install do
3
+ desc "Install configuration and migrations"
4
+ task :all do
5
+ %w(config migrations).each { |t| Rake::Task["masq:install:#{t}"].invoke }
6
+ end
7
+
8
+ desc "Copy configuration file from masq to application"
9
+ task :config do
10
+ target = Rails.root.join("config/masq.yml")
11
+ unless File.exist?(target)
12
+ require "fileutils"
13
+ source = File.expand_path("../../../config/masq.example.yml", __FILE__)
14
+ FileUtils.cp(source, target)
15
+ puts "Created config/masq.yml"
16
+ end
17
+ end
18
+ end
19
+
20
+ namespace :openid do
21
+ desc "Cleanup OpenID store"
22
+ task cleanup_store: :environment do
23
+ Masq::ActiveRecordStore.new.cleanup
24
+ end
25
+ end
26
+
27
+ namespace :test do
28
+ desc "Prepare CI build task"
29
+ task :prepare_ci do
30
+ adapter = ENV["DB_ADAPTER"] || "sqlite3"
31
+ database = ENV["DB_DATABASE"] || (("sqlite3" == adapter) ? "db/test.sqlite3" : "masq_test")
32
+
33
+ config = {
34
+ "test" => {
35
+ "adapter" => adapter,
36
+ "database" => database,
37
+ "username" => ENV["DB_USERNAME"],
38
+ "password" => ENV["DB_PASSWORD"],
39
+ "port" => ENV["DB_PORT"] ? ENV["DB_PORT"].to_i : nil,
40
+ "socket" => ENV["DB_SOCKET"] ? ENV["DB_SOCKET"] : nil,
41
+ "host" => "localhost",
42
+ "encoding" => "utf8",
43
+ "pool" => 5,
44
+ "timeout" => 5000,
45
+ },
46
+ }
47
+
48
+ File.open(Rails.root.join("config/database.yml"), "w") do |f|
49
+ f.write(config.to_yaml)
50
+ end
51
+ end
52
+
53
+ desc "Run CI build task"
54
+ task ci: [:prepare_ci] do
55
+ Rake::Task["test"].invoke
56
+ end
57
+ end
58
+ end
data.tar.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ ��1/����ڦ�̍Ȕ���{�T�CC۠�Xx��j��
2
+ �'���mQ'�[��#�mv6,y~VD�n�d����]�t#o