decidim-trusted_ids 0.12.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
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +344 -0
- data/Rakefile +40 -0
- data/app/commands/concerns/decidim/trusted_ids/system/create_organization_override.rb +22 -0
- data/app/commands/concerns/decidim/trusted_ids/system/needs_census_config.rb +29 -0
- data/app/commands/concerns/decidim/trusted_ids/system/update_organization_override.rb +21 -0
- data/app/controllers/concerns/decidim/trusted_ids/admin/impersonations_controller_override.rb +31 -0
- data/app/controllers/concerns/decidim/trusted_ids/check_existing_authorizations.rb +40 -0
- data/app/controllers/concerns/decidim/trusted_ids/check_omniauth_email_on_login.rb +25 -0
- data/app/controllers/concerns/decidim/trusted_ids/needs_trusted_ids_snippets.rb +26 -0
- data/app/events/decidim/trusted_ids/verifications/invalid_notification.rb +10 -0
- data/app/events/decidim/trusted_ids/verifications/success_notification.rb +33 -0
- data/app/forms/concerns/decidim/trusted_ids/system/organization_form_override.rb +93 -0
- data/app/forms/decidim/trusted_ids/verifications/trusted_ids_handler.rb +65 -0
- data/app/forms/decidim/via_oberta/verifications/via_oberta_handler.rb +103 -0
- data/app/jobs/decidim/trusted_ids/application_job.rb +8 -0
- data/app/jobs/decidim/trusted_ids/omniauth_verification_job.rb +76 -0
- data/app/jobs/decidim/trusted_ids/system/propagate_census_config_job.rb +29 -0
- data/app/jobs/decidim/trusted_ids/system/propagate_census_settings_job.rb +29 -0
- data/app/models/concerns/decidim/trusted_ids/authorization_override.rb +21 -0
- data/app/models/concerns/decidim/trusted_ids/organization_override.rb +17 -0
- data/app/models/decidim/trusted_ids/organization_config.rb +20 -0
- data/app/overrides/decidim/devise/sessions/new/add_trusted_id_buttons.html.erb.deface +3 -0
- data/app/overrides/decidim/devise/sessions/new/wrap_default_login_form.html.erb.deface +4 -0
- data/app/overrides/decidim/devise/shared/_omniauth_buttons/replace_button_content.html.erb.deface +17 -0
- data/app/overrides/decidim/devise/shared/_omniauth_buttons/skip_trusted_ids_provider.html.erb.deface +3 -0
- data/app/overrides/decidim/system/organizations/_advanced_settings/settings_form.html.erb.deface +3 -0
- data/app/overrides/decidim/system/organizations/_omniauth_provider/add_icon_placeholder.html.erb.deface +3 -0
- data/app/overrides/layouts/decidim/_head/add_trusted_ids_tags.html.erb.deface +11 -0
- data/app/packs/entrypoints/decidim_trusted_ids.js +3 -0
- data/app/packs/images/idcat_mobil-icon.svg +8 -0
- data/app/packs/images/valid-icon.png +0 -0
- data/app/packs/stylesheets/decidim/trusted_ids/oauth_icons.scss +75 -0
- data/app/services/decidim/via_oberta/api/request.rb +156 -0
- data/app/services/decidim/via_oberta/api/response.rb +67 -0
- data/app/views/decidim/trusted_ids/devise/sessions/_login_box.html.erb +30 -0
- data/app/views/decidim/trusted_ids/system/organizations/_icon_path_placeholder.html.erb +10 -0
- data/app/views/decidim/trusted_ids/system/organizations/_settings_form.html.erb +25 -0
- data/app/views/decidim/trusted_ids/verifications/_form.html.erb +39 -0
- data/app/views/decidim/via_oberta/verifications/_form.html.erb +56 -0
- data/config/assets.rb +16 -0
- data/config/i18n-tasks.yml +12 -0
- data/config/locales/ca.yml +132 -0
- data/config/locales/de.yml +168 -0
- data/config/locales/en.yml +169 -0
- data/config/locales/es.yml +132 -0
- data/config/locales/fr.yml +168 -0
- data/config/locales/it.yml +168 -0
- data/config/locales/oc.yml +132 -0
- data/config/locales/zz_fallbacks.rb +45 -0
- data/db/migrate/20230718082548_create_organization_trusted_ids_config.rb +14 -0
- data/lib/decidim/trusted_ids/engine.rb +181 -0
- data/lib/decidim/trusted_ids/test/factories.rb +21 -0
- data/lib/decidim/trusted_ids/verifications.rb +10 -0
- data/lib/decidim/trusted_ids/version.rb +10 -0
- data/lib/decidim/trusted_ids.rb +69 -0
- data/lib/omniauth/strategies/valid.rb +134 -0
- data/lib/omniauth/strategies.rb +7 -0
- data/lib/tasks/decidim_trusted_ids_upgrade_tasks.rake +5 -0
- data/package-lock.json +29154 -0
- data/package.json +43 -0
- metadata +200 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module TrustedIds
|
|
5
|
+
module System
|
|
6
|
+
module OrganizationFormOverride
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
include Decidim::AttributeObject::TypeMap
|
|
9
|
+
|
|
10
|
+
# Prepended into the form class so that super() correctly resolves to
|
|
11
|
+
# the core's encrypted_omniauth_settings. When icon_path is blank, sets the
|
|
12
|
+
# default path via the jsonb sub-attribute setter BEFORE calling super, so
|
|
13
|
+
# the core picks it up and encrypts it correctly. Empty string would be truthy
|
|
14
|
+
# in omniauth_settings and cause oauth_icon → external_icon("") to crash.
|
|
15
|
+
module EncryptedOmniauthSettingsOverride
|
|
16
|
+
def encrypted_omniauth_settings
|
|
17
|
+
provider = Decidim::TrustedIds.omniauth_provider
|
|
18
|
+
icon_attr = :"omniauth_settings_#{provider}_icon_path"
|
|
19
|
+
enabled_attr = :"omniauth_settings_#{provider}_enabled"
|
|
20
|
+
|
|
21
|
+
# Only normalize blank icon_path when the provider is being enabled.
|
|
22
|
+
# If not enabled, we leave all settings blank so the core can return nil
|
|
23
|
+
# (preserving ENV-var-based configuration).
|
|
24
|
+
if public_send(enabled_attr) && public_send(icon_attr).blank?
|
|
25
|
+
default = Decidim::TrustedIds.omniauth[:icon_path].presence ||
|
|
26
|
+
"media/images/#{provider.downcase}-icon.png"
|
|
27
|
+
public_send(:"#{icon_attr}=", default)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
included do
|
|
35
|
+
prepend EncryptedOmniauthSettingsOverride
|
|
36
|
+
|
|
37
|
+
jsonb_attribute :trusted_ids_census_settings, TrustedIds.census_config_attributes
|
|
38
|
+
attribute :trusted_ids_census_expiration_days, Integer
|
|
39
|
+
translatable_attribute :trusted_ids_census_tos, String
|
|
40
|
+
attribute :census_expiration_apply_all_tenants, Boolean
|
|
41
|
+
attribute :census_tos_apply_all_tenants, Boolean
|
|
42
|
+
|
|
43
|
+
validate :validate_icon_path
|
|
44
|
+
|
|
45
|
+
def map_model(model)
|
|
46
|
+
self.secondary_hosts = model.secondary_hosts.join("\n")
|
|
47
|
+
self.omniauth_settings = (model.omniauth_settings || {}).transform_values do |v|
|
|
48
|
+
Decidim::OmniauthProvider.value_defined?(v) ? Decidim::AttributeEncryptor.decrypt(v) : v
|
|
49
|
+
end
|
|
50
|
+
self.file_upload_settings = Decidim::System::FileUploadSettingsForm.from_model(model.file_upload_settings)
|
|
51
|
+
|
|
52
|
+
self.trusted_ids_census_settings = model.trusted_ids_census_config&.settings || {}
|
|
53
|
+
self.trusted_ids_census_expiration_days = model.trusted_ids_census_config&.expiration_days
|
|
54
|
+
self.trusted_ids_census_tos = model.trusted_ids_census_config&.tos || {}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def default_expiration_days
|
|
58
|
+
@default_expiration_days ||= Decidim::TrustedIds.verification_expiration_time.to_i / 86_400
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def default_icon_path
|
|
62
|
+
Decidim::TrustedIds.omniauth[:icon_path].presence ||
|
|
63
|
+
"media/images/#{Decidim::TrustedIds.omniauth_provider.downcase}-icon.png"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def validate_icon_path
|
|
70
|
+
# jsonb_attribute generates a getter method for each sub-field that handles
|
|
71
|
+
# both string and symbol hash keys. Use it instead of direct hash access.
|
|
72
|
+
icon_key = :"omniauth_settings_#{Decidim::TrustedIds.omniauth_provider}_icon_path"
|
|
73
|
+
path = public_send(icon_key)
|
|
74
|
+
return if path.blank? # blank = will use default on save, always valid
|
|
75
|
+
|
|
76
|
+
# Must follow the media/images/ format expected by Shakapacker
|
|
77
|
+
unless path.start_with?("media/images/")
|
|
78
|
+
errors.add(icon_key, :invalid_icon_path_format)
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# File must exist in the module's registered assets images directory.
|
|
83
|
+
# This is future-proof: any new icon added to app/packs/images/ will
|
|
84
|
+
# automatically pass this validation without needing code changes.
|
|
85
|
+
filename = path.delete_prefix("media/images/")
|
|
86
|
+
file_exists = Decidim::TrustedIds::Engine.root.join("app/packs/images", filename).exist?
|
|
87
|
+
|
|
88
|
+
errors.add(icon_key, :icon_path_not_found) unless file_exists
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module TrustedIds
|
|
5
|
+
module Verifications
|
|
6
|
+
class TrustedIdsHandler < AuthorizationHandler
|
|
7
|
+
attribute :provider, String
|
|
8
|
+
attribute :uid, String
|
|
9
|
+
attribute :raw_data
|
|
10
|
+
|
|
11
|
+
validates :uid, presence: true
|
|
12
|
+
validate :trusted_ids_provider?
|
|
13
|
+
validate :exising_trusted_ids_identity?
|
|
14
|
+
|
|
15
|
+
def metadata
|
|
16
|
+
super.merge(
|
|
17
|
+
uid: uid,
|
|
18
|
+
provider: provider,
|
|
19
|
+
extra: extra_attributes
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def unique_id
|
|
24
|
+
Digest::SHA512.hexdigest(
|
|
25
|
+
"#{uid}-#{user&.decidim_organization_id}-#{Rails.application.secret_key_base}"
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# no public attributes
|
|
30
|
+
def form_attributes
|
|
31
|
+
attributes.except(:id, :user, :provider, :uid, :extra).keys
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_partial_path
|
|
35
|
+
"decidim/trusted_ids/verifications/form"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def extra_attributes
|
|
41
|
+
return {} unless TrustedIds.authorization_metadata.respond_to? :map
|
|
42
|
+
|
|
43
|
+
TrustedIds.authorization_metadata.to_h do |key, parts|
|
|
44
|
+
parts = [parts] unless parts.is_a? Array
|
|
45
|
+
[key, raw_data.dig(*parts)]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def trusted_ids_provider?
|
|
50
|
+
return false if errors.any?
|
|
51
|
+
return false if provider == TrustedIds.omniauth_provider.to_s
|
|
52
|
+
|
|
53
|
+
errors.add(:base, I18n.t("decidim.verifications.trusted_ids.errors.invalid_method"))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def exising_trusted_ids_identity?
|
|
57
|
+
return false if errors.any?
|
|
58
|
+
return false if user&.identities&.exists?(provider: TrustedIds.omniauth_provider)
|
|
59
|
+
|
|
60
|
+
errors.add(:base, I18n.t("decidim.verifications.trusted_ids.errors.no_identity"))
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module ViaOberta
|
|
5
|
+
module Verifications
|
|
6
|
+
class ViaObertaHandler < AuthorizationHandler
|
|
7
|
+
# VALID: 1=NIF, 2=NIE, 3=Passaport, 4=Altres
|
|
8
|
+
# VIAOBERTA: 1=NIF, 2=passaport, 3=permís residència, 4=NIE
|
|
9
|
+
DOCUMENT_TYPE = {
|
|
10
|
+
1 => :nif,
|
|
11
|
+
2 => :nie,
|
|
12
|
+
3 => :passport,
|
|
13
|
+
4 => :others
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
attribute :document_id, String
|
|
17
|
+
attribute :document_type, Integer
|
|
18
|
+
attribute :tos_agreement, Boolean
|
|
19
|
+
validates :document_id, :document_type, presence: true
|
|
20
|
+
validates :tos_agreement, allow_nil: false, acceptance: true
|
|
21
|
+
|
|
22
|
+
validate :existing_via_oberta_identity
|
|
23
|
+
|
|
24
|
+
attr_reader :response_error, :response_code
|
|
25
|
+
|
|
26
|
+
def unique_id
|
|
27
|
+
return unless organization
|
|
28
|
+
return unless census_response.success?
|
|
29
|
+
|
|
30
|
+
Digest::SHA256.hexdigest(
|
|
31
|
+
"#{document_id}-#{user&.decidim_organization_id}-#{Rails.application.secret_key_base}"
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# use from previous authorization metadata. We don't allow user input here to prevent spoofing
|
|
36
|
+
# not memoized because @document_id would be defined if sent via form
|
|
37
|
+
def document_id
|
|
38
|
+
trusted_authorization&.metadata&.dig("uid") || impersonated_document_id
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# use from previous authorization metadata or use the one from the form as a fallback
|
|
42
|
+
# not memoized because @document_id would be defined if sent via form
|
|
43
|
+
def document_type
|
|
44
|
+
document_type_from_metadata || DOCUMENT_TYPE[attributes[:document_type]]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def document_type_from_metadata
|
|
48
|
+
@document_type_from_metadata ||= DOCUMENT_TYPE[trusted_authorization&.metadata&.dig("extra", "identifier_type").to_i]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def document_types
|
|
52
|
+
@document_types ||= DOCUMENT_TYPE.map { |k, v| [I18n.t("decidim.via_oberta.verifications.document_type.#{v}", default: v.to_s), k] }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def document_type_string
|
|
56
|
+
return "" unless document_type
|
|
57
|
+
|
|
58
|
+
I18n.t("decidim.via_oberta.verifications.document_type.#{document_type}", default: document_type.to_s)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def census_response
|
|
62
|
+
@census_response ||= ViaOberta::Api::Request.new(document_id: document_id, document_type: document_type, organization: organization).response
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def existing_via_oberta_identity
|
|
66
|
+
return unless tos_agreement
|
|
67
|
+
|
|
68
|
+
return errors.add(:base, I18n.t("decidim.verifications.trusted_ids.errors.invalid_id")) if document_id.blank?
|
|
69
|
+
return errors.add(:base, I18n.t("decidim.verifications.trusted_ids.errors.invalid_type")) if document_type.blank?
|
|
70
|
+
|
|
71
|
+
return if census_response.found?
|
|
72
|
+
|
|
73
|
+
errors.add(:base, I18n.t("decidim.verifications.trusted_ids.errors.invalid_census"))
|
|
74
|
+
@response_error = census_response.error
|
|
75
|
+
@response_code = census_response.code
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# # no public attributes
|
|
79
|
+
def form_attributes
|
|
80
|
+
attributes.except(:id, :user, :document_id, :document_type).keys
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def to_partial_path
|
|
84
|
+
"decidim/via_oberta/verifications/form"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def impersonated_document_id
|
|
90
|
+
attributes[:document_id] if user&.managed?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def trusted_authorization
|
|
94
|
+
@trusted_authorization ||= Decidim::Authorization.find_by(name: "trusted_ids_handler", user: user)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def organization
|
|
98
|
+
user&.organization
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module TrustedIds
|
|
5
|
+
class OmniauthVerificationJob < ApplicationJob
|
|
6
|
+
queue_as :default
|
|
7
|
+
|
|
8
|
+
def perform(data)
|
|
9
|
+
@data = data
|
|
10
|
+
|
|
11
|
+
unless data[:provider].to_s == Decidim::TrustedIds.omniauth_provider.to_s
|
|
12
|
+
Rails.logger.debug { "OmniauthVerificationJob: Omniauth [#{data[:provider]}] is not a valid trusted_ids provider (#{Decidim::TrustedIds.omniauth_provider})" }
|
|
13
|
+
return
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
unless trusted_ids_identity?
|
|
17
|
+
Rails.logger.error "OmniauthVerificationJob: User #{user.id} does not have a trusted_ids (#{Decidim::TrustedIds.omniauth_provider}) identity"
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
authorize_user!
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
attr_reader :data
|
|
27
|
+
|
|
28
|
+
def user
|
|
29
|
+
@user ||= Decidim::User.find(data[:user_id])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def trusted_ids_identity?
|
|
33
|
+
@trusted_ids_identity ||= user&.identities&.exists?(provider: Decidim::TrustedIds.omniauth_provider)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def handler
|
|
37
|
+
@handler ||= Decidim::AuthorizationHandler.handler_for("trusted_ids_handler", user: user, uid: data[:uid], provider: data[:provider], raw_data: data[:raw_data])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def authorization
|
|
41
|
+
@authorization ||= Decidim::Authorization.find_by(unique_id: handler.unique_id)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def authorize_user!
|
|
45
|
+
already_granted = authorization&.granted?
|
|
46
|
+
Decidim::Verifications::AuthorizeUser.call(handler, user.organization) do
|
|
47
|
+
on(:ok) do
|
|
48
|
+
notify_user(user, :ok) unless already_granted
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
on(:invalid) do
|
|
52
|
+
Rails.logger.error "OmniauthVerificationJob: User #{user.id} could not be authorized with #{handler.provider}. Errors: #{handler.errors.full_messages}"
|
|
53
|
+
notify_user(user, :invalid)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def notify_user(user, status)
|
|
59
|
+
return unless Decidim::TrustedIds.send_verification_notifications
|
|
60
|
+
|
|
61
|
+
notification_class = status == :ok ? Decidim::TrustedIds::Verifications::SuccessNotification : Decidim::TrustedIds::Verifications::InvalidNotification
|
|
62
|
+
Decidim::EventsManager.publish(
|
|
63
|
+
event: "decidim.events.trusted_ids.verifications.#{status}",
|
|
64
|
+
event_class: notification_class,
|
|
65
|
+
resource: authorization,
|
|
66
|
+
affected_users: [user],
|
|
67
|
+
extra: {
|
|
68
|
+
status: status.to_s,
|
|
69
|
+
errors: handler.errors.full_messages,
|
|
70
|
+
force_email: true
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module TrustedIds
|
|
5
|
+
module System
|
|
6
|
+
class PropagateCensusConfigJob < ApplicationJob
|
|
7
|
+
queue_as :default
|
|
8
|
+
|
|
9
|
+
def perform(config_id, keys)
|
|
10
|
+
@config = Decidim::TrustedIds::OrganizationConfig.find_by(id: config_id)
|
|
11
|
+
return unless @config
|
|
12
|
+
return if keys.blank?
|
|
13
|
+
|
|
14
|
+
propagate_config!(@config.organization, keys)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def propagate_config!(organization, keys)
|
|
18
|
+
Decidim::Organization.where.not(id: organization).find_each do |tenant|
|
|
19
|
+
config = Decidim::TrustedIds::OrganizationConfig.find_or_create_by(organization: tenant)
|
|
20
|
+
config.expiration_days = @config.expiration_days if keys.include?(:expiration_days)
|
|
21
|
+
config.tos = @config.tos if keys.include?(:tos)
|
|
22
|
+
|
|
23
|
+
config.save!
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module TrustedIds
|
|
5
|
+
module System
|
|
6
|
+
class PropagateCensusSettingsJob < ApplicationJob
|
|
7
|
+
queue_as :default
|
|
8
|
+
|
|
9
|
+
def perform(config_id, keys)
|
|
10
|
+
@config = Decidim::TrustedIds::OrganizationConfig.find_by(id: config_id)
|
|
11
|
+
return unless @config
|
|
12
|
+
|
|
13
|
+
propagate_settings!(@config.organization, keys)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def propagate_settings!(organization, keys)
|
|
17
|
+
Decidim::Organization.where.not(id: organization).find_each do |tenant|
|
|
18
|
+
conf = Decidim::TrustedIds::OrganizationConfig.find_or_create_by(organization: tenant)
|
|
19
|
+
keys.each do |key|
|
|
20
|
+
conf.settings[key] = @config.settings[key]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
conf.save!
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module TrustedIds
|
|
5
|
+
module AuthorizationOverride
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
# allows to configure the expiration time with the organization config properties
|
|
10
|
+
def expires_at
|
|
11
|
+
return unless workflow_manifest
|
|
12
|
+
|
|
13
|
+
expires_in = organization.trusted_ids_census_config&.expiration_days&.days&.to_i || workflow_manifest.expires_in # rubocop:disable Style/SafeNavigationChainLength
|
|
14
|
+
return if expires_in.zero?
|
|
15
|
+
|
|
16
|
+
(granted_at || created_at) + expires_in
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module TrustedIds
|
|
5
|
+
module OrganizationOverride
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
has_one :trusted_ids_census_config,
|
|
10
|
+
foreign_key: "decidim_organization_id",
|
|
11
|
+
class_name: "Decidim::TrustedIds::OrganizationConfig",
|
|
12
|
+
inverse_of: :organization,
|
|
13
|
+
dependent: :destroy
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module TrustedIds
|
|
5
|
+
class OrganizationConfig < ApplicationRecord
|
|
6
|
+
include Decidim::TranslatableResource
|
|
7
|
+
include Decidim::TranslatableAttributes
|
|
8
|
+
self.table_name = "organization_trusted_ids_configs"
|
|
9
|
+
|
|
10
|
+
belongs_to :organization,
|
|
11
|
+
foreign_key: "decidim_organization_id",
|
|
12
|
+
class_name: "Decidim::Organization"
|
|
13
|
+
translatable_fields :tos
|
|
14
|
+
|
|
15
|
+
def translated_tos
|
|
16
|
+
translated_attribute(tos, organization).presence || I18n.t("content_html", scope: "decidim.via_oberta.verifications.tos")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- replace "erb[loud]:contains('decidim_form_for')" -->
|
|
2
|
+
|
|
3
|
+
<% override_login = Decidim::TrustedIds.custom_login_screen? && Devise.mappings[:user].omniauthable? && current_organization.enabled_omniauth_providers&.include?(Decidim::TrustedIds.omniauth_provider&.to_sym) %>
|
|
4
|
+
<%= decidim_form_for(resource, namespace: "session", as: resource_name, url: session_path(resource_name), html: {class: "new_user decidim-login #{'hide' if override_login}"}) do |f| %>
|
data/app/overrides/decidim/devise/shared/_omniauth_buttons/replace_button_content.html.erb.deface
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- replace "erb[loud]:contains('oauth_icon provider')" -->
|
|
2
|
+
|
|
3
|
+
<%= begin
|
|
4
|
+
if provider.to_s == Decidim::TrustedIds.omniauth_provider.to_s
|
|
5
|
+
# For the VÀLid provider, fall back to the default PNG icon when
|
|
6
|
+
# icon_path is not configured in the organization's omniauth settings or in the module's omniauth settings.
|
|
7
|
+
settings = current_organization.enabled_omniauth_providers[provider]
|
|
8
|
+
icon_path = settings&.dig(:icon_path).presence ||
|
|
9
|
+
Decidim::TrustedIds.omniauth[:icon_path].presence ||
|
|
10
|
+
"media/images/#{Decidim::TrustedIds.omniauth_provider.downcase}-icon.png"
|
|
11
|
+
external_icon(icon_path)
|
|
12
|
+
else
|
|
13
|
+
oauth_icon provider
|
|
14
|
+
end
|
|
15
|
+
rescue ::Shakapacker::Manifest::MissingEntryError
|
|
16
|
+
nil
|
|
17
|
+
end %>
|
data/app/overrides/decidim/devise/shared/_omniauth_buttons/skip_trusted_ids_provider.html.erb.deface
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<!-- insert_after "erb[silent]:contains('current_organization.enabled_omniauth_providers.keys.each')" -->
|
|
2
|
+
|
|
3
|
+
<% next if controller_name == "sessions" && action_name == "new" && provider == Decidim::TrustedIds.omniauth_provider&.to_sym && Decidim::TrustedIds.custom_login_screen? %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
<!-- insert_after "erb[loud]:contains('append_stylesheet_pack_tag')" -->
|
|
3
|
+
|
|
4
|
+
<%#
|
|
5
|
+
The CSS must be added to the header because the CSS files are not loaded on all screens.
|
|
6
|
+
To avoid making a more complex override of the Devise view, it is added to the header as is done
|
|
7
|
+
in other modules like Decidim Awesome.
|
|
8
|
+
%>
|
|
9
|
+
|
|
10
|
+
<% append_stylesheet_pack_tag "decidim_trusted_ids", media: "all" %>
|
|
11
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="56" viewBox="0 0 44 56">
|
|
2
|
+
<g id="valid-fill" fill="#FFF" fill-rule="evenodd">
|
|
3
|
+
<path fill-rule="nonzero" d="M11.53 6.73L2.07 6.73 2.07.09 11.53.09 11.53 6.73zM2.07 9.4L11.53 9.4 11.53 34.19 2.07 34.19 2.07 9.4zM42.71 34.19L33.59 34.19 33.59 31.47 33.5 31.47C32.3 33.04 30.87 34.81 26.24 34.81 19.24 34.81 15.82 27.89 15.82 21.68 15.82 15.85 18.07 8.78 26.38 8.78 29.05573 8.70308662 31.5954717 9.95797374 33.16 12.13L33.26 12.13 33.26.13 42.71.13 42.71 34.19zM29.44 15.66C26.04 15.66 25.28 18.86 25.28 21.77 25.28 24.68 26.04 27.93 29.44 27.93 32.84 27.93 33.59 24.73 33.59 21.77 33.59 18.81 32.83 15.66 29.44 15.66z"/>
|
|
4
|
+
<path d="M10.94,46.13 C10.8353818,44.8627645 9.77151075,43.8904562 8.5,43.9 C6.31,43.9 5.61,46.02 5.61,48.06 C5.61,50.1 6.31,52.22 8.5,52.22 C10.07,52.22 10.68,51.12 10.96,49.69 L15.55,49.69 C15.55,52.75 13.06,56 8.62,56 C3.72,56 0.94,52.54 0.94,48.06 C0.94,43.28 3.94,40.12 8.62,40.12 C12.79,40.12 15.08,42.32 15.48,46.12 L10.94,46.13 Z"/>
|
|
5
|
+
<path fill-rule="nonzero" d="M21.7,40.48 L26.27,40.48 L31.79,55.64 L27,55.64 L26.36,53.47 L21.49,53.47 L20.81,55.64 L16.12,55.64 L21.7,40.48 Z M25.38,50.22 L24,45.55 L22.53,50.22 L25.38,50.22 Z"/>
|
|
6
|
+
<polygon points="34.93 44.36 30.68 44.36 30.68 40.48 43.85 40.48 43.85 44.36 39.6 44.36 39.6 55.64 34.93 55.64"/>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* Decidim has a bug when using external_icon method that automatically adds a display:none inline css */
|
|
2
|
+
|
|
3
|
+
.social-register,
|
|
4
|
+
.button--social,
|
|
5
|
+
.button--unverified-login {
|
|
6
|
+
display: flex;
|
|
7
|
+
padding: 0 1.5rem 0 0;
|
|
8
|
+
min-width: 250px;
|
|
9
|
+
color: #fff;
|
|
10
|
+
height: 50px;
|
|
11
|
+
text-decoration-line: none;
|
|
12
|
+
font-weight: normal;
|
|
13
|
+
|
|
14
|
+
.button--social__icon {
|
|
15
|
+
img.icon.external-icon {
|
|
16
|
+
display: inline-block !important;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.login__omniauth-button {
|
|
22
|
+
background-color: var(--secondary) !important;
|
|
23
|
+
border-color: var(--secondary) !important;
|
|
24
|
+
|
|
25
|
+
img.icon.external-icon {
|
|
26
|
+
display: inline-block !important;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.login__omniauth-button > span {
|
|
31
|
+
color: white !important;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.button--social,
|
|
35
|
+
.button--valid {
|
|
36
|
+
background-color: var(--secondary);
|
|
37
|
+
justify-content: center;
|
|
38
|
+
align-items: center;
|
|
39
|
+
gap: 0.5rem;
|
|
40
|
+
padding: 0 0.5rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.button--unverified-login {
|
|
44
|
+
border-color: var(--secondary);
|
|
45
|
+
background-color: var(--secondary);
|
|
46
|
+
color: white;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.trusted-ids-login-box {
|
|
50
|
+
background: #fff;
|
|
51
|
+
border: 1px solid #ccc;
|
|
52
|
+
border-radius: 15px;
|
|
53
|
+
padding: 2em !important;
|
|
54
|
+
margin-top: 2em;
|
|
55
|
+
|
|
56
|
+
h6 {
|
|
57
|
+
font-size: 1em;
|
|
58
|
+
font-weight: 600;
|
|
59
|
+
margin-top: 2em;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
hr {
|
|
63
|
+
width: 80%;
|
|
64
|
+
margin: 2em auto;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
ul {
|
|
68
|
+
margin-left: 1.25rem;
|
|
69
|
+
list-style-type: disc !important;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.hide {
|
|
74
|
+
display: none;
|
|
75
|
+
}
|