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,60 @@
1
+ module Masq
2
+ class SitesController < BaseController
3
+ before_action :login_required
4
+ before_action :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(site_params)
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
+ def create
40
+ end
41
+
42
+ private
43
+
44
+ def site
45
+ @site ||= current_account.sites.find(params[:id])
46
+ end
47
+
48
+ def persona
49
+ @persona ||= site.persona
50
+ end
51
+
52
+ def find_personas
53
+ @personas = current_account.personas.order(:title)
54
+ end
55
+
56
+ def site_params
57
+ params.require(:site).permit(:persona_id, :url, properties: {})
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,25 @@
1
+ module Masq
2
+ class YubikeyAssociationsController < BaseController
3
+ before_action :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
@@ -0,0 +1,57 @@
1
+ module Masq
2
+ module ApplicationHelper
3
+ def page_title
4
+ (@page_title ||= nil) ? "#{@page_title} | #{Masq::Engine.config.masq["name"]}" : Masq::Engine.config.masq["name"]
5
+ end
6
+
7
+ def label_tag(field, text = nil, options = {})
8
+ content_tag(:label, text ? text : field.to_s.humanize, options.reverse_merge(for: field.to_s))
9
+ end
10
+
11
+ def error_messages_for(*objects)
12
+ render("masq/shared/error_messages", objects: objects.flatten)
13
+ end
14
+
15
+ # Is the current page an identity page? This is used to display
16
+ # further information (like the endoint url) in the <head>
17
+ def identity_page?
18
+ active_page?("accounts" => ["show"])
19
+ end
20
+
21
+ # Is the current page the home page? This is used to display
22
+ # further information (like the endoint url) in the <head>
23
+ def home_page?
24
+ active_page?("info" => ["index"])
25
+ end
26
+
27
+ # Custom label names for request properties (like SReg data)
28
+ def property_label_text(property)
29
+ case property.to_sym
30
+ when :image_default then t(:image_url)
31
+ when :web_default then t(:website_url)
32
+ when :web_blog then t(:blog_url)
33
+ else t(property.to_sym)
34
+ end
35
+ end
36
+
37
+ def property_label_text_for_type_uri(type_uri)
38
+ property = Persona.attribute_name_for_type_uri(type_uri)
39
+ property ? property_label_text(property) : type_uri
40
+ end
41
+
42
+ # Renders a navigation element and marks it as active where
43
+ # appropriate. See active_page? for details
44
+ def nav(name, url, pages = nil, active = false)
45
+ content_tag(:li, link_to(name, url), class: ((active || (pages && active_page?(pages))) ? "act" : nil))
46
+ end
47
+
48
+ # Takes a hash with pages and tells whether the current page is among them.
49
+ # The keys must be controller names and their value must be an array of
50
+ # action names. If the array is empty, every action is supposed to be valid.
51
+ def active_page?(pages = {})
52
+ is_active = pages.include?(params[:controller])
53
+ is_active = pages[params[:controller]].include?(params[:action]) if is_active && !pages[params[:controller]].empty?
54
+ is_active
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,15 @@
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_by(&: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_by(&:first)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
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
@@ -0,0 +1,17 @@
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
@@ -0,0 +1,245 @@
1
+ require "digest/sha1"
2
+
3
+ module Masq
4
+ class Account < ActiveRecord::Base
5
+ has_many :personas, ->() { order(:id) }, dependent: :delete_all
6
+ has_many :sites, dependent: :destroy
7
+ belongs_to :public_persona, class_name: "Persona", optional: true
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[A-Za-z0-9_@.-]+\z/
13
+ validates_presence_of :email
14
+ validates_uniqueness_of :email, case_sensitive: false
15
+ validates_format_of :email, with: /(\A([^@\s]+)@((?:[-_a-z0-9]+\.)+[a-z]{2,})\z)/i, allow_blank: true
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
+
31
+ class AlreadyActivated < StandardError
32
+ attr_reader :user, :message
33
+ def initialize(account, message = nil)
34
+ @message, @account = message, account
35
+ end
36
+ end
37
+
38
+ class << self
39
+ # Finds the user with the corresponding activation code, activates their account and returns the user.
40
+ #
41
+ # Raises:
42
+ # [Account::ActivationCodeNotFound] if there is no user with the corresponding activation code
43
+ # [Account::AlreadyActivated] if the user with the corresponding activation code has already activated their account
44
+ def find_and_activate!(activation_code)
45
+ raise ArgumentError if activation_code.nil?
46
+ user = find_by(activation_code: activation_code)
47
+ raise ActivationCodeNotFound unless user
48
+ raise AlreadyActivated.new(user) if user.active?
49
+ user.send(:activate!)
50
+ user
51
+ end
52
+
53
+ # Authenticates a user by their login name and password.
54
+ # Returns the user or nil.
55
+ def authenticate(login, password, basic_auth_used = false)
56
+ a = find_by(login: login)
57
+ if a.nil? && Masq::Engine.config.masq["create_auth_ondemand"]["enabled"]
58
+ # Need to set some password - but is never used
59
+ pw = if Masq::Engine.config.masq["create_auth_ondemand"]["random_password"]
60
+ SecureRandom.hex(13)
61
+ else
62
+ password
63
+ end
64
+ signup = Masq::Signup.create_account!(
65
+ login: login,
66
+ password: pw,
67
+ password_confirmation: pw,
68
+ email: "#{login}@#{Masq::Engine.config.masq["create_auth_ondemand"]["default_mail_domain"]}",
69
+ )
70
+ a = signup.account if signup.succeeded?
71
+ end
72
+
73
+ if !a.nil? && a.active? && a.enabled
74
+ if a.authenticated?(password) || (Masq::Engine.config.masq["trust_basic_auth"] && basic_auth_used)
75
+ a.last_authenticated_at = Time.now.utc
76
+ a.last_authenticated_by_yubikey = a.authenticated_with_yubikey?
77
+ a.save(validate: false)
78
+ a
79
+ end
80
+ end
81
+ end
82
+
83
+ # Encrypts some data with the salt.
84
+ def encrypt(password, salt)
85
+ Digest::SHA1.hexdigest("--#{salt}--#{password}--")
86
+ end
87
+
88
+ # Receives a login token which consists of the users password and
89
+ # a Yubico one time password (the otp is always 44 characters long)
90
+ def split_password_and_yubico_otp(token)
91
+ token.reverse!
92
+ yubico_otp = token.slice!(0..43).reverse
93
+ password = token.reverse
94
+ [password, yubico_otp]
95
+ end
96
+
97
+ # Returns the first twelve chars from the Yubico OTP,
98
+ # which are used to identify a Yubikey
99
+ def extract_yubico_identity_from_otp(yubico_otp)
100
+ yubico_otp[0..11]
101
+ end
102
+
103
+ # Utilizes the Yubico library to verify a one time password
104
+ def verify_yubico_otp(otp)
105
+ Yubikey::OTP::Verify.new(otp).valid?
106
+ rescue Yubikey::OTP::InvalidOTPError
107
+ false
108
+ end
109
+ end
110
+
111
+ def to_param
112
+ login
113
+ end
114
+
115
+ # The existence of an activation code means they have not activated yet
116
+ def active?
117
+ activation_code.nil?
118
+ end
119
+
120
+ def activate!
121
+ @activated = true
122
+ self.activated_at = Time.now.utc
123
+ self.activation_code = nil
124
+ save
125
+ end
126
+
127
+ # True if the user has just been activated
128
+ def pending?
129
+ @activated ||= false
130
+ end
131
+
132
+ # Does the user have the possibility to authenticate with a one time password?
133
+ def has_otp_device?
134
+ !yubico_identity.nil?
135
+ end
136
+
137
+ # Encrypts the password with the user salt
138
+ def encrypt(password)
139
+ self.class.encrypt(password, salt)
140
+ end
141
+
142
+ def authenticated?(password)
143
+ if password.nil?
144
+ false
145
+ elsif password.length < 50 && !(yubico_identity? && yubikey_mandatory?)
146
+ encrypt(password) == crypted_password
147
+ elsif Masq::Engine.config.masq["can_use_yubikey"]
148
+ password, yubico_otp = self.class.split_password_and_yubico_otp(password)
149
+ @authenticated_with_yubikey = yubikey_authenticated?(yubico_otp) if encrypt(password) == crypted_password
150
+ end
151
+ end
152
+
153
+ # Is the Yubico OTP valid and belongs to this account?
154
+ def yubikey_authenticated?(otp)
155
+ if yubico_identity? && self.class.verify_yubico_otp(otp)
156
+ (self.class.extract_yubico_identity_from_otp(otp) == yubico_identity)
157
+ else
158
+ false
159
+ end
160
+ end
161
+
162
+ def authenticated_with_yubikey?
163
+ @authenticated_with_yubikey ||= false
164
+ end
165
+
166
+ def associate_with_yubikey(otp)
167
+ if self.class.verify_yubico_otp(otp)
168
+ self.yubico_identity = self.class.extract_yubico_identity_from_otp(otp)
169
+ save(validate: false)
170
+ else
171
+ false
172
+ end
173
+ end
174
+
175
+ def remember_token?
176
+ remember_token_expires_at && Time.now.utc < remember_token_expires_at
177
+ end
178
+
179
+ # These create and unset the fields required for remembering users between browser closes
180
+ def remember_me
181
+ remember_me_for(2.weeks)
182
+ end
183
+
184
+ def remember_me_for(time)
185
+ remember_me_until(time.from_now.utc)
186
+ end
187
+
188
+ def remember_me_until(time)
189
+ self.remember_token_expires_at = time
190
+ self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
191
+ save(validate: false)
192
+ end
193
+
194
+ def forget_me
195
+ self.remember_token_expires_at = nil
196
+ self.remember_token = nil
197
+ save(validate: false)
198
+ end
199
+
200
+ def forgot_password!
201
+ @forgotten_password = true
202
+ make_password_reset_code
203
+ save
204
+ end
205
+
206
+ # First update the password_reset_code before setting the
207
+ # reset_password flag to avoid duplicate email notifications.
208
+ def reset_password
209
+ update_attribute(:password_reset_code, nil)
210
+ @reset_password = true
211
+ end
212
+
213
+ def recently_forgot_password?
214
+ @forgotten_password ||= false
215
+ end
216
+
217
+ def recently_reset_password?
218
+ @reset_password ||= false
219
+ end
220
+
221
+ def disable!
222
+ update_attribute(:enabled, false)
223
+ end
224
+
225
+ protected
226
+
227
+ def encrypt_password
228
+ return if password.blank?
229
+ self.salt = Digest::SHA1.hexdigest("--#{Time.now}--#{login}--") if new_record?
230
+ self.crypted_password = encrypt(password)
231
+ end
232
+
233
+ def password_required?
234
+ crypted_password.blank? || !password.blank?
235
+ end
236
+
237
+ def make_password_reset_code
238
+ self.password_reset_code = Digest::SHA1.hexdigest(Time.now.to_s.split("").sort_by { rand }.join)
239
+ end
240
+
241
+ def deliver_forgot_password
242
+ Masq::AccountMailer.forgot_password(self).deliver_now if recently_forgot_password?
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,42 @@
1
+ module Masq
2
+ class OpenIdRequest < ActiveRecord::Base
3
+ validates_presence_of :token, :parameters
4
+
5
+ before_validation :make_token, on: :create
6
+
7
+ if Rails.gem_version >= Gem::Version.create("6.1")
8
+ serialize :parameters, type: Hash, coder: JSON
9
+ else # Rails 5.2 & 6.0
10
+ serialize :parameters, JSON
11
+ end
12
+
13
+ def parameters
14
+ self[:parameters]
15
+ end
16
+
17
+ def parameters=(params)
18
+ self[:parameters] =
19
+ case params
20
+ # arbitrary params passed as Hash
21
+ when Hash
22
+ params.delete_if { |k, _v| k.index("openid.") != 0 }
23
+ # params from ActionController (does not inherit directly from HashWithIndifferentAccess after Rails 4.2)
24
+ when ActionController::Parameters
25
+ params.to_unsafe_h.delete_if { |k, _v| k.index("openid.") != 0 }
26
+ end
27
+ end
28
+
29
+ def from_trusted_domain?
30
+ host = URI.parse(parameters["openid.realm"] || parameters["openid.trust_root"]).host
31
+ return false if Masq::Engine.config.masq["trusted_domains"].nil?
32
+
33
+ Masq::Engine.config.masq["trusted_domains"].find { |domain| host.to_s.ends_with?(domain) }
34
+ end
35
+
36
+ protected
37
+
38
+ def make_token
39
+ self.token = Digest::SHA1.hexdigest(Time.now.to_s.split("").sort_by { rand }.join)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,61 @@
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 << self
15
+ def properties
16
+ mappings.keys
17
+ end
18
+
19
+ def attribute_name_for_type_uri(type_uri)
20
+ prop = mappings.detect { |i| i[1].include?(type_uri) }
21
+ prop ? prop[0] : nil
22
+ end
23
+
24
+ # Mappings for SReg names and AX Type URIs to attributes
25
+ def mappings
26
+ Masq::Engine.config.masq["attribute_mappings"]
27
+ end
28
+ end
29
+
30
+ public
31
+
32
+ # Returns the personas attribute for the given SReg name or AX Type URI
33
+ def property(type)
34
+ prop = Persona.mappings.detect { |i| i[1].include?(type) }
35
+ prop ? send(prop[0]).to_s : nil
36
+ end
37
+
38
+ def date_of_birth
39
+ "#{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"}"
40
+ end
41
+
42
+ def fullname=(name)
43
+ self.firstname, self.surname = name.to_s.split(" ")
44
+ self.surname ||= firstname
45
+ self[:fullname] = name
46
+ end
47
+
48
+ def date_of_birth=(dob)
49
+ res = dob.split("-")
50
+ self.dob_year = res[0]
51
+ self.dob_month = res[1]
52
+ self.dob_day = res[2]
53
+ end
54
+
55
+ protected
56
+
57
+ def check_deletable!
58
+ raise ActiveRecord::RecordNotDestroyed unless deletable
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,68 @@
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 received 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 a 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
@@ -0,0 +1,70 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%=h page_title %></title>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6
+ <% if identity_page? %>
7
+ <meta http-equiv="X-XRDS-Location" content="<%= identity_url(:account => @account, :format => :xrds, :protocol => scheme) %>" />
8
+ <link rel="openid.server openid2.provider" href="<%= endpoint_url %>" />
9
+ <% elsif home_page? %>
10
+ <meta http-equiv="X-XRDS-Location" content="<%= server_url(:format => :xrds, :protocol => scheme) %>" />
11
+ <% end %>
12
+ <link rel="seatbelt.config" type="application/xml" href="<%= seatbelt_config_url(:format => :xml, :protocol => scheme) %>" />
13
+ <link rel="Shortcut Icon" href="/favicon.ico" type="image/x-icon" />
14
+ <link rel="icon" href="/favicon.ico" type="image/ico" />
15
+ <%= stylesheet_link_tag 'masq/application' %>
16
+ <%= csrf_meta_tags %>
17
+ </head>
18
+ <body>
19
+ <div id="head">
20
+ <div class="wrap">
21
+ <h1><%= link_to Masq::Engine.config.masq['name'], root_path %></h1>
22
+ <ul id="navi">
23
+ <% if logged_in? %>
24
+ <% unless checkid_request %>
25
+ <%= nav t(:nav_identity), identity_path(current_account), 'accounts' => ['show'] %>
26
+ <%= nav t(:nav_profile), edit_account_path, 'accounts' => ['edit', 'update'] %>
27
+ <%= nav t(:nav_personas), account_personas_path, 'personas' => [] %>
28
+ <%= nav t(:nav_trusted_sites), account_sites_path, 'sites' => [] %>
29
+ <% if not auth_type_used == :basic %>
30
+ <%= nav t(:logout), logout_path %>
31
+ <% end %>
32
+ <% else %>
33
+ <%= nav t(:current_verification_request), proceed_path, 'server' => [] %>
34
+ <% end %>
35
+ <% else %>
36
+ <%= nav t(:login_link), login_path, 'sessions' => ['new', 'create'] %>
37
+ <% unless Masq::Engine.config.masq['disable_registration'] %>
38
+ <%= nav t(:signup_link), new_account_path, 'accounts' => ['new', 'create'] %>
39
+ <% end %>
40
+ <%= nav t(:help), help_path, 'info' => ['help'] %>
41
+ <% end %>
42
+ </ul>
43
+ </div>
44
+ </div>
45
+ <div id="main">
46
+ <div class="wrap">
47
+ <% if flash[:notice] %><div class="notice"><%=simple_format h(flash[:notice]) %></div><% end %>
48
+ <% if flash[:alert] %>
49
+ <div class="error">
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
+ </div>
56
+ <% end %>
57
+ <%= yield %>
58
+ </div>
59
+ </div>
60
+ <div id="foot">
61
+ <div class="wrap">
62
+ <span class="note">
63
+ powered by <%= link_to 'masq', 'https://github.com/oauth-xx/masq2' %>
64
+ and <%= link_to image_tag('masq/openid_symbol.png') + " OpenID", 'https://openid.net/' %> |
65
+ <%= link_to t(:get_help), help_path %>
66
+ </span>
67
+ </div>
68
+ </div>
69
+ </body>
70
+ </html>