decidim-system 0.30.8 → 0.31.0.rc1
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 +4 -4
- data/app/commands/decidim/system/create_api_user.rb +63 -0
- data/app/commands/decidim/system/create_oauth_application.rb +3 -1
- data/app/commands/decidim/system/create_organization.rb +0 -1
- data/app/commands/decidim/system/refresh_api_user_secret.rb +37 -0
- data/app/commands/decidim/system/update_oauth_application.rb +4 -1
- data/app/commands/decidim/system/update_organization.rb +1 -0
- data/app/controllers/decidim/system/admins_controller.rb +2 -2
- data/app/controllers/decidim/system/api_users_controller.rb +76 -0
- data/app/controllers/decidim/system/application_controller.rb +1 -0
- data/app/controllers/decidim/system/devise/sessions_controller.rb +0 -7
- data/app/controllers/decidim/system/oauth_applications_controller.rb +2 -2
- data/app/controllers/decidim/system/organizations_controller.rb +4 -4
- data/app/forms/decidim/system/api_user_form.rb +24 -0
- data/app/forms/decidim/system/base_organization_form.rb +2 -60
- data/app/forms/decidim/system/oauth_application_form.rb +29 -2
- data/app/helpers/decidim/system/api_users_helper.rb +17 -0
- data/app/packs/entrypoints/decidim_system_overrides.scss +1 -1
- data/app/packs/stylesheets/decidim/system/application.scss +16 -1
- data/app/views/decidim/system/api_users/index.html.erb +13 -0
- data/app/views/decidim/system/api_users/new.html.erb +15 -0
- data/app/views/decidim/system/oauth_applications/_form.html.erb +35 -0
- data/app/views/decidim/system/organizations/_advanced_settings.html.erb +1 -1
- data/app/views/decidim/system/organizations/_file_upload_settings.erb +5 -5
- data/app/views/decidim/system/organizations/_omniauth_provider.html.erb +1 -1
- data/app/views/decidim/system/organizations/edit.html.erb +4 -0
- data/app/views/decidim/system/shared/_api_users.html.erb +66 -0
- data/app/views/layouts/decidim/system/_header.html.erb +2 -2
- data/app/views/layouts/decidim/system/application.html.erb +2 -2
- data/config/assets.rb +2 -2
- data/config/locales/ca-IT.yml +70 -5
- data/config/locales/ca.yml +70 -5
- data/config/locales/cs.yml +34 -4
- data/config/locales/de.yml +10 -0
- data/config/locales/en.yml +73 -4
- data/config/locales/es-MX.yml +69 -4
- data/config/locales/es-PY.yml +69 -4
- data/config/locales/es.yml +69 -4
- data/config/locales/eu.yml +70 -5
- data/config/locales/fi-plain.yml +69 -4
- data/config/locales/fi.yml +69 -4
- data/config/locales/fr-CA.yml +1 -4
- data/config/locales/fr.yml +1 -4
- data/config/locales/ja.yml +68 -0
- data/config/locales/pt-BR.yml +1 -77
- data/config/locales/ro-RO.yml +43 -137
- data/config/locales/sv.yml +67 -53
- data/config/routes.rb +1 -0
- data/lib/decidim/system/engine.rb +1 -1
- data/lib/decidim/system/menu.rb +7 -1
- data/lib/decidim/system/version.rb +1 -1
- data/lib/decidim/system.rb +6 -0
- metadata +14 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 05fe2090e59a09f9d9daa1fc7c3d8b2d99fb7c3b449c2f74ce37ce0ee1a48e8e
|
|
4
|
+
data.tar.gz: bfedfc1597c406c88e5f4e453467c7fcec963bd9e65b5212f3158526560ad9e3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 117e8c702ae168d93f319eab9eaa2825e8234d94893659ab0db0cf07fa39482a5d3f984eb161ffa95ad2dd563ee9a522d58dad865ca7da1f167a517a015f5203
|
|
7
|
+
data.tar.gz: 2f431ec56287f729ebae1019c6bda1aadaf1ebf0a856ec7e8bd31ea41d75c2d33d3a3afa0edf48f04e524cde3bc59442d87eaef3e4036c5c59f45e01caa9e23f
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module System
|
|
5
|
+
class CreateApiUser < Decidim::Command
|
|
6
|
+
def initialize(form, current_admin)
|
|
7
|
+
@form = form
|
|
8
|
+
@current_admin = current_admin
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
return broadcast(:invalid) unless @form.valid?
|
|
13
|
+
|
|
14
|
+
transaction do
|
|
15
|
+
create_api_user
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
broadcast(:ok, @api_user, @api_secret)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :form, :current_admin
|
|
24
|
+
|
|
25
|
+
def create_api_user
|
|
26
|
+
@api_user = Decidim.traceability.create!(
|
|
27
|
+
::Decidim::Api::ApiUser,
|
|
28
|
+
current_admin,
|
|
29
|
+
**api_user_attributes
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def api_user_attributes
|
|
34
|
+
{
|
|
35
|
+
decidim_organization_id: form.organization,
|
|
36
|
+
name: form.name,
|
|
37
|
+
nickname: ::Decidim::UserBaseEntity.nicknamize(form.name, organization: form.organization),
|
|
38
|
+
api_key:,
|
|
39
|
+
api_secret:,
|
|
40
|
+
admin: true,
|
|
41
|
+
admin_terms_accepted_at: Time.current
|
|
42
|
+
}.tap do |attrs|
|
|
43
|
+
attrs[:published_at] = Time.current if ::Decidim::Api::ApiUser.column_names.include?("published_at")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def api_key
|
|
48
|
+
@api_key ||= generate_unique_key
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def api_secret
|
|
52
|
+
@api_secret ||= SecureRandom.alphanumeric(Decidim::System.api_users_secret_length)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def generate_unique_key
|
|
56
|
+
loop do
|
|
57
|
+
key = SecureRandom.alphanumeric(32)
|
|
58
|
+
return key unless Decidim::Api::ApiUser.exists?(api_key: key)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -35,7 +35,9 @@ module Decidim
|
|
|
35
35
|
organization_url: @form.organization_url,
|
|
36
36
|
organization_logo: @form.organization_logo,
|
|
37
37
|
redirect_uri: @form.redirect_uri,
|
|
38
|
-
scopes: "
|
|
38
|
+
scopes: @form.scopes.join(" "),
|
|
39
|
+
confidential: @form.confidential?,
|
|
40
|
+
refresh_tokens_enabled: @form.refresh_tokens_enabled?
|
|
39
41
|
}.tap do |attrs|
|
|
40
42
|
attrs[:organization_logo] = @form.organization_logo if @form.organization_logo.present?
|
|
41
43
|
end
|
|
@@ -63,7 +63,6 @@ module Decidim
|
|
|
63
63
|
users_registration_mode: form.users_registration_mode,
|
|
64
64
|
force_users_to_authenticate_before_access_organization: form.force_users_to_authenticate_before_access_organization,
|
|
65
65
|
badges_enabled: true,
|
|
66
|
-
user_groups_enabled: true,
|
|
67
66
|
default_locale: form.default_locale,
|
|
68
67
|
omniauth_settings: form.encrypted_omniauth_settings,
|
|
69
68
|
smtp_settings: form.encrypted_smtp_settings,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module System
|
|
5
|
+
class RefreshApiUserSecret < Decidim::Command
|
|
6
|
+
def initialize(api_user, current_admin)
|
|
7
|
+
@api_user = api_user
|
|
8
|
+
@current_admin = current_admin
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
return broadcast(:invalid) if @api_user.blank?
|
|
13
|
+
return broadcast(:invalid) if @current_admin.blank?
|
|
14
|
+
|
|
15
|
+
transaction do
|
|
16
|
+
refresh_secret
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
broadcast(:ok, @api_secret)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def refresh_secret
|
|
25
|
+
Decidim.traceability.update!(
|
|
26
|
+
@api_user,
|
|
27
|
+
@current_admin,
|
|
28
|
+
api_secret:
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def api_secret
|
|
33
|
+
@api_secret ||= SecureRandom.alphanumeric(Decidim::System.api_users_secret_length)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -36,7 +36,10 @@ module Decidim
|
|
|
36
36
|
decidim_organization_id: @form.decidim_organization_id,
|
|
37
37
|
organization_name: @form.organization_name,
|
|
38
38
|
organization_url: @form.organization_url,
|
|
39
|
-
redirect_uri: @form.redirect_uri
|
|
39
|
+
redirect_uri: @form.redirect_uri,
|
|
40
|
+
scopes: @form.scopes.join(" "),
|
|
41
|
+
confidential: @form.confidential?,
|
|
42
|
+
refresh_tokens_enabled: @form.refresh_tokens_enabled?
|
|
40
43
|
}.tap do |attrs|
|
|
41
44
|
attrs[:organization_logo] = @form.organization_logo if @form.organization_logo.present?
|
|
42
45
|
end
|
|
@@ -54,6 +54,7 @@ module Decidim
|
|
|
54
54
|
organization.smtp_settings = form.encrypted_smtp_settings
|
|
55
55
|
organization.file_upload_settings = form.file_upload_settings.final
|
|
56
56
|
organization.content_security_policy = form.content_security_policy
|
|
57
|
+
organization.header_snippets = form.header_snippets if Decidim.enable_html_header_snippets
|
|
57
58
|
|
|
58
59
|
organization.save!
|
|
59
60
|
end
|
|
@@ -24,7 +24,7 @@ module Decidim
|
|
|
24
24
|
|
|
25
25
|
on(:invalid) do
|
|
26
26
|
flash.now[:alert] = I18n.t("admins.create.error", scope: "decidim.system")
|
|
27
|
-
render :new
|
|
27
|
+
render :new, status: :unprocessable_entity
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -46,7 +46,7 @@ module Decidim
|
|
|
46
46
|
|
|
47
47
|
on(:invalid) do
|
|
48
48
|
flash.now[:alert] = I18n.t("admins.update.error", scope: "decidim.system")
|
|
49
|
-
render :edit
|
|
49
|
+
render :edit, status: :unprocessable_entity
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module System
|
|
5
|
+
class ApiUsersController < Decidim::System::ApplicationController
|
|
6
|
+
helper ::Decidim::MetaTagsHelper
|
|
7
|
+
helper ::Decidim::Admin::IconLinkHelper
|
|
8
|
+
|
|
9
|
+
def index
|
|
10
|
+
@api_users = api_users
|
|
11
|
+
@secret_user = session.delete(:api_user)&.with_indifferent_access
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def new
|
|
15
|
+
@form = form(::Decidim::System::ApiUserForm).instance
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def destroy
|
|
19
|
+
Decidim.traceability.perform_action!("delete", api_user, current_admin) do
|
|
20
|
+
api_user.destroy!
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
flash[:notice] = I18n.t("api_user.destroy.success", scope: "decidim.system")
|
|
24
|
+
|
|
25
|
+
redirect_to action: :index
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def update
|
|
29
|
+
RefreshApiUserSecret.call(api_user, current_admin) do
|
|
30
|
+
on(:ok) do |secret|
|
|
31
|
+
flash[:notice] = I18n.t("api_user.refresh.success", scope: "decidim.system", user: api_user.api_key)
|
|
32
|
+
session[:api_user] = { id: api_user.id, secret: secret }
|
|
33
|
+
redirect_to action: :index
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
on(:invalid) do
|
|
37
|
+
flash[:notice] = I18n.t("api_user.refresh.error", scope: "decidim.system")
|
|
38
|
+
redirect_to action: :index
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def create
|
|
44
|
+
@form = ::Decidim::System::ApiUserForm.from_params(params.merge!(name: params[:admin][:name], organization: organization))
|
|
45
|
+
CreateApiUser.call(@form, current_admin) do
|
|
46
|
+
on(:ok) do |api_user, secret|
|
|
47
|
+
flash[:notice] = I18n.t("api_user.create.success", scope: "decidim.system", user: api_user.api_key)
|
|
48
|
+
session[:api_user] = { id: api_user.id, secret: secret }
|
|
49
|
+
redirect_to action: :index
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
on(:invalid) do
|
|
53
|
+
flash[:error] = I18n.t("api_user.create.error", scope: "decidim.system")
|
|
54
|
+
render :new, status: :unprocessable_entity
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def api_users
|
|
62
|
+
::Decidim::Api::ApiUser.order(:decidim_organization_id, :id)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def api_user
|
|
66
|
+
return unless params[:id]
|
|
67
|
+
|
|
68
|
+
@api_user ||= ::Decidim::Api::ApiUser.find(params[:id])
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def organization
|
|
72
|
+
::Decidim::Organization.find(params[:admin][:organization])
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -10,17 +10,10 @@ module Decidim
|
|
|
10
10
|
|
|
11
11
|
helper Decidim::DecidimFormHelper
|
|
12
12
|
|
|
13
|
-
rescue_from ActionController::InvalidAuthenticityToken, with: :redirect_to_referer_or_path
|
|
14
|
-
|
|
15
13
|
layout "decidim/system/login"
|
|
16
14
|
|
|
17
15
|
private
|
|
18
16
|
|
|
19
|
-
def redirect_to_referer_or_path
|
|
20
|
-
set_flash_message(:alert, "csrf_token", scope: "devise.failure")
|
|
21
|
-
redirect_back(fallback_location: root_path)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
17
|
def current_organization; end
|
|
25
18
|
end
|
|
26
19
|
end
|
|
@@ -30,7 +30,7 @@ module Decidim
|
|
|
30
30
|
|
|
31
31
|
on(:invalid) do
|
|
32
32
|
flash.now[:alert] = I18n.t("oauth_applications.create.error", scope: "decidim.system")
|
|
33
|
-
render :new
|
|
33
|
+
render :new, status: :unprocessable_entity
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|
|
@@ -55,7 +55,7 @@ module Decidim
|
|
|
55
55
|
on(:invalid) do |application|
|
|
56
56
|
@oauth_application = application
|
|
57
57
|
flash.now[:error] = I18n.t("oauth_applications.update.error", scope: "decidim.system")
|
|
58
|
-
render action: :edit
|
|
58
|
+
render action: :edit, status: :unprocessable_entity
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
end
|
|
@@ -24,12 +24,12 @@ module Decidim
|
|
|
24
24
|
|
|
25
25
|
on(:invalid_invitation) do
|
|
26
26
|
flash.now[:alert] = t("organizations.create.error_invitation", scope: "decidim.system")
|
|
27
|
-
render :new
|
|
27
|
+
render :new, status: :unprocessable_entity
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
on(:invalid) do
|
|
31
31
|
flash.now[:alert] = t("organizations.create.error", scope: "decidim.system")
|
|
32
|
-
render :new
|
|
32
|
+
render :new, status: :unprocessable_entity
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
end
|
|
@@ -55,7 +55,7 @@ module Decidim
|
|
|
55
55
|
|
|
56
56
|
on(:invalid) do
|
|
57
57
|
flash.now[:alert] = I18n.t("organizations.update.error", scope: "decidim.system")
|
|
58
|
-
render :edit
|
|
58
|
+
render :edit, status: :unprocessable_entity
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
end
|
|
@@ -96,7 +96,7 @@ module Decidim
|
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
def provider_enabled?(provider)
|
|
99
|
-
|
|
99
|
+
Decidim.omniauth_providers.dig(provider, :enabled)
|
|
100
100
|
end
|
|
101
101
|
end
|
|
102
102
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module System
|
|
5
|
+
class ApiUserForm < Form
|
|
6
|
+
mimic :admin
|
|
7
|
+
|
|
8
|
+
attribute :name, String
|
|
9
|
+
attribute :organization, ::Decidim::Organization
|
|
10
|
+
|
|
11
|
+
validate :name, :name_uniqueness
|
|
12
|
+
validates :name, presence: true
|
|
13
|
+
validates :organization, presence: true
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def name_uniqueness
|
|
18
|
+
return unless ::Decidim::Api::ApiUser.where(name:).where(organization:).any?
|
|
19
|
+
|
|
20
|
+
errors.add(:name, I18n.t("models.api_user.validations.name_uniqueness", scope: "decidim.system"))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -18,6 +18,7 @@ module Decidim
|
|
|
18
18
|
attribute :available_authorizations, Array[String]
|
|
19
19
|
attribute :users_registration_mode, String
|
|
20
20
|
attribute :default_locale, String
|
|
21
|
+
attribute :header_snippets, String
|
|
21
22
|
|
|
22
23
|
jsonb_attribute :smtp_settings, [
|
|
23
24
|
[:from, String],
|
|
@@ -46,7 +47,7 @@ module Decidim
|
|
|
46
47
|
attribute :file_upload_settings, FileUploadSettingsForm
|
|
47
48
|
|
|
48
49
|
OMNIATH_PROVIDERS_ATTRIBUTES = Decidim::OmniauthProvider.available.keys.map do |provider|
|
|
49
|
-
|
|
50
|
+
Decidim.omniauth_providers[provider].keys.map do |setting|
|
|
50
51
|
if setting == :enabled
|
|
51
52
|
[:"omniauth_settings_#{provider}_enabled", Boolean]
|
|
52
53
|
else
|
|
@@ -61,8 +62,6 @@ module Decidim
|
|
|
61
62
|
validate :validate_organization_uniqueness
|
|
62
63
|
validate :validate_secret_key_base_for_encryption
|
|
63
64
|
validates :users_registration_mode, inclusion: { in: Decidim::Organization.users_registration_modes }
|
|
64
|
-
validate :validate_host_format
|
|
65
|
-
validate :validate_secondary_hosts_format
|
|
66
65
|
|
|
67
66
|
def map_model(model)
|
|
68
67
|
self.default_locale = model.default_locale
|
|
@@ -124,63 +123,6 @@ module Decidim
|
|
|
124
123
|
def validate_organization_uniqueness
|
|
125
124
|
raise "#{self.class.name} is expected to implement #validate_organization_uniqueness"
|
|
126
125
|
end
|
|
127
|
-
|
|
128
|
-
# Validates the host format for organization domains.
|
|
129
|
-
#
|
|
130
|
-
# Valid formats:
|
|
131
|
-
# - Fully Qualified Domain Names (FQDN): example.org, sub.example.org, my-site.example.org
|
|
132
|
-
# - Localhost: localhost (common for development)
|
|
133
|
-
# - IPv4 addresses: 127.0.0.1, 192.168.1.1
|
|
134
|
-
# - IPv6 addresses: ::1, 2001:db8::1, [::1]
|
|
135
|
-
#
|
|
136
|
-
# Invalid formats (will be rejected):
|
|
137
|
-
# - Hosts containing spaces
|
|
138
|
-
# - Hosts with invalid characters (!@#$%^&* etc.)
|
|
139
|
-
# - Hosts with leading/trailing hyphens in labels (e.g., -example.com or example-.com)
|
|
140
|
-
# - Labels longer than 63 characters
|
|
141
|
-
# - Total host length exceeding 253 characters
|
|
142
|
-
#
|
|
143
|
-
# @see https://en.wikipedia.org/wiki/Fully_qualified_domain_name
|
|
144
|
-
# @see https://en.wikipedia.org/wiki/IPv4_address
|
|
145
|
-
# @see https://en.wikipedia.org/wiki/IPv6_address
|
|
146
|
-
#
|
|
147
|
-
HOST_FORMAT_REGEX = %r{
|
|
148
|
-
\A
|
|
149
|
-
(?:
|
|
150
|
-
# FQDN: requires at least one dot, labels separated by dots.
|
|
151
|
-
# Each label: alphanumeric start/end, alphanumerics and hyphens inside, max 63 chars.
|
|
152
|
-
(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}
|
|
153
|
-
|
|
|
154
|
-
# Localhost: common development hostname.
|
|
155
|
-
localhost
|
|
156
|
-
|
|
|
157
|
-
# IPv4: four octets (0-255 each).
|
|
158
|
-
(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)
|
|
159
|
-
|
|
|
160
|
-
# IPv6: bracketed form [::1] or unbracketed (standard and compressed forms).
|
|
161
|
-
(?:\[[\da-fA-F:]+\]|[\da-fA-F:]+\z)
|
|
162
|
-
)
|
|
163
|
-
\z
|
|
164
|
-
}x
|
|
165
|
-
|
|
166
|
-
def validate_host_format
|
|
167
|
-
return if host.blank?
|
|
168
|
-
|
|
169
|
-
return if host.match?(HOST_FORMAT_REGEX)
|
|
170
|
-
|
|
171
|
-
errors.add(:host, :invalid)
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def validate_secondary_hosts_format
|
|
175
|
-
return if secondary_hosts.blank?
|
|
176
|
-
|
|
177
|
-
clean_secondary_hosts.each do |secondary_host|
|
|
178
|
-
next if secondary_host.match?(HOST_FORMAT_REGEX)
|
|
179
|
-
|
|
180
|
-
errors.add(:secondary_hosts, :invalid)
|
|
181
|
-
break
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
126
|
end
|
|
185
127
|
end
|
|
186
128
|
end
|
|
@@ -9,30 +9,57 @@ module Decidim
|
|
|
9
9
|
mimic :oauth_application
|
|
10
10
|
|
|
11
11
|
attribute :name, String
|
|
12
|
+
attribute :application_type, String
|
|
12
13
|
attribute :decidim_organization_id, Integer
|
|
13
14
|
attribute :organization_name, String
|
|
14
15
|
attribute :organization_url, String
|
|
15
16
|
attribute :organization_logo
|
|
16
17
|
attribute :redirect_uri, String
|
|
18
|
+
attribute :scopes, Array[String], default: ::Doorkeeper.config.default_scopes.all
|
|
19
|
+
attribute :refresh_tokens_enabled, Boolean, default: false
|
|
17
20
|
|
|
18
21
|
validates :name, :redirect_uri, :decidim_organization_id, :organization_name, :organization_url, presence: true
|
|
19
|
-
validates :
|
|
22
|
+
validates :application_type, inclusion: { in: :application_types }
|
|
23
|
+
validates :organization_url, url: true
|
|
20
24
|
validates :organization_logo, presence: true, unless: :persisted?
|
|
21
25
|
validates :organization_logo, passthru: { to: Decidim::OAuthApplication }
|
|
26
|
+
validates :scopes, inclusion: { in: ::Doorkeeper.config.scopes.all }
|
|
22
27
|
validate :redirect_uri_is_ssl
|
|
23
28
|
|
|
29
|
+
def map_model(model)
|
|
30
|
+
self.application_type = model.confidential? ? "confidential" : "public"
|
|
31
|
+
end
|
|
32
|
+
|
|
24
33
|
def organization
|
|
25
34
|
current_organization || Decidim::Organization.find_by(id: decidim_organization_id)
|
|
26
35
|
end
|
|
27
36
|
|
|
37
|
+
def application_types
|
|
38
|
+
%w(confidential public)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def confidential?
|
|
42
|
+
application_type == "confidential"
|
|
43
|
+
end
|
|
44
|
+
|
|
28
45
|
private
|
|
29
46
|
|
|
30
47
|
def redirect_uri_is_ssl
|
|
31
48
|
return if redirect_uri.blank?
|
|
32
49
|
|
|
33
50
|
uri = URI.parse(redirect_uri)
|
|
51
|
+
return if local_uri?(uri)
|
|
52
|
+
|
|
53
|
+
# We assume confidential clients need to always connect through `https`.
|
|
54
|
+
#
|
|
55
|
+
# For public clients, we display the error only if the scheme is `http`
|
|
56
|
+
# because e.g. iOS application redirect URI may require to use a
|
|
57
|
+
# different scheme.
|
|
58
|
+
errors.add(:redirect_uri, :must_be_ssl) if (confidential? && uri.scheme != "https") || uri.scheme == "http"
|
|
59
|
+
end
|
|
34
60
|
|
|
35
|
-
|
|
61
|
+
def local_uri?(uri)
|
|
62
|
+
%w(localhost 10.0.2.2).include?(uri.host)
|
|
36
63
|
end
|
|
37
64
|
end
|
|
38
65
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module System
|
|
5
|
+
module ApiUsersHelper
|
|
6
|
+
def fresh_token?(api_user)
|
|
7
|
+
@secret_user.present? &&
|
|
8
|
+
@secret_user[:id].present? &&
|
|
9
|
+
@secret_user[:id] == api_user.id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def organizations
|
|
13
|
+
Decidim::Organization.all
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Application specific styles: https://docs.decidim.org/en/customize/styles/
|
|
2
|
-
@
|
|
2
|
+
@use "stylesheets/decidim/system/decidim_application";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@
|
|
1
|
+
@use "tom-select/dist/scss/tom-select";
|
|
2
2
|
|
|
3
3
|
:root {
|
|
4
4
|
--primary: #e02d2d;
|
|
@@ -52,7 +52,10 @@ table {
|
|
|
52
52
|
tr:nth-child(even) td {
|
|
53
53
|
@apply bg-gray-3;
|
|
54
54
|
}
|
|
55
|
+
}
|
|
55
56
|
|
|
57
|
+
table,
|
|
58
|
+
.text-content {
|
|
56
59
|
a {
|
|
57
60
|
@apply text-primary underline;
|
|
58
61
|
}
|
|
@@ -164,3 +167,15 @@ dl {
|
|
|
164
167
|
.tabs-panel[aria-hidden="true"] {
|
|
165
168
|
@apply hidden;
|
|
166
169
|
}
|
|
170
|
+
|
|
171
|
+
.input-group__password {
|
|
172
|
+
@apply w-full relative;
|
|
173
|
+
|
|
174
|
+
input {
|
|
175
|
+
@apply w-full;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
button {
|
|
179
|
+
@apply absolute ltr:right-2 rtl:left-2 top-4;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<% add_decidim_page_title(t(".manage")) %>
|
|
2
|
+
|
|
3
|
+
<% provide :title do %>
|
|
4
|
+
<h1 class="h1"><%= t ".manage" %></h1>
|
|
5
|
+
<% end %>
|
|
6
|
+
|
|
7
|
+
<div class="text-content space-y-4">
|
|
8
|
+
<%= t ".explanation_html", oauth_link: decidim_system.oauth_applications_path %>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<%= link_to t("new", scope:"decidim.system.actions.api_users"), [:new, :api_user], class: "button button__sm md:button__lg button__primary" %>
|
|
12
|
+
|
|
13
|
+
<%= render partial: "decidim/system/shared/api_users", locals: { api_users: @api_users } %>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<% add_decidim_page_title(t(".title")) %>
|
|
2
|
+
|
|
3
|
+
<% provide :title do %>
|
|
4
|
+
<h1 class="h1"><%= t ".title" %></h1>
|
|
5
|
+
<% end %>
|
|
6
|
+
<%= decidim_form_for(@form, url: api_users_path, method: :post, class: "api-user", html: { id: "new_api_user" }) do |f| %>
|
|
7
|
+
<div class="form__wrapper">
|
|
8
|
+
<%= f.select :organization, organizations.map { |organization| ["#{organization_name(organization)} (#{organization.host})", organization.id] }, prompt: t(".select_organization") %>
|
|
9
|
+
<%= f.text_field :name, autofocus: true %>
|
|
10
|
+
|
|
11
|
+
<div class="form__wrapper-block">
|
|
12
|
+
<%= f.submit t("decidim.system.actions.api_users.create"), class: "button button__sm md:button__lg button__primary" %>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<% end %>
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
<div class="form__wrapper">
|
|
2
2
|
<%= form.text_field :name %>
|
|
3
|
+
|
|
4
|
+
<%= field_set_tag form.label(:application_type, nil, for: nil) do %>
|
|
5
|
+
<span class="help-text"><%= t(".application_type_help_html", client_type_link: "https://datatracker.ietf.org/doc/html/rfc6749#section-2.1", pkce_link: "https://datatracker.ietf.org/doc/html/rfc7636") %></span>
|
|
6
|
+
<%=
|
|
7
|
+
form.collection_radio_buttons(
|
|
8
|
+
:application_type,
|
|
9
|
+
form.object.application_types,
|
|
10
|
+
:to_s,
|
|
11
|
+
->(type) { t(".application_type.#{type}.name") }
|
|
12
|
+
) do |builder|
|
|
13
|
+
builder.label(for: nil, class: "form__wrapper-checkbox-label") do
|
|
14
|
+
explanation = t(".application_type.#{builder.value}.explanation")
|
|
15
|
+
builder.radio_button(id: nil) + %(#{sanitize(builder.text)}<br><span class="help-text">#{sanitize(explanation)}</span>).html_safe
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
%>
|
|
19
|
+
<% end %>
|
|
20
|
+
|
|
3
21
|
<%= form.text_field :redirect_uri %>
|
|
4
22
|
<%= form.select :decidim_organization_id,
|
|
5
23
|
Decidim::Organization.all.map { |o| [organization_name(o), o.id] },
|
|
@@ -8,4 +26,21 @@
|
|
|
8
26
|
<%= form.text_field :organization_name %>
|
|
9
27
|
<%= form.text_field :organization_url %>
|
|
10
28
|
<%= form.upload :organization_logo, required: true, button_class: "button button__lg button__transparent-primary" %>
|
|
29
|
+
|
|
30
|
+
<%= field_set_tag form.label(:refresh_tokens, nil, for: nil) do %>
|
|
31
|
+
<span class="help-text"><%= t(".refresh_tokens_help_html") %></span>
|
|
32
|
+
<%= form.check_box :refresh_tokens_enabled, label_options: { class: "form__wrapper-checkbox-label" } %>
|
|
33
|
+
<% end %>
|
|
34
|
+
|
|
35
|
+
<%= field_set_tag form.label(:scopes, nil, for: nil) do %>
|
|
36
|
+
<%=
|
|
37
|
+
form.collection_check_boxes :scopes, Doorkeeper.config.scopes.all, :to_s, :to_s, include_hidden: false, help_text: t(".scopes_help_html") do |option|
|
|
38
|
+
option.label(class: "form__wrapper-checkbox-label") do
|
|
39
|
+
option.check_box(checked: form.object.scopes.include?(option.value)) +
|
|
40
|
+
option.text +
|
|
41
|
+
content_tag(:div, class: "help-text") { t(".scopes_explanation.#{option.value}") }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
%>
|
|
45
|
+
<% end %>
|
|
11
46
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<button id="advanced-settings-trigger" data-
|
|
1
|
+
<button id="advanced-settings-trigger" data-controller="dropdown" data-target="advanced-settings-panel" type="button" class="button button__sm md:button__lg button__primary w-fit" aria-expanded="false">
|
|
2
2
|
<span><%= t(".show") %></span>
|
|
3
3
|
<span><%= t(".hide") %></span>
|
|
4
4
|
</button>
|