diff --git a/Gemfile b/Gemfile index 82b5511e3b277b46949fa3410bf513563ace3d5b..af3fd4c06cea9052d9a549e293f69a253f07c9de 100644 --- a/Gemfile +++ b/Gemfile @@ -44,7 +44,7 @@ gem "i18n_date_range" gem "image_processing" gem "jbuilder" gem "jquery-rails" -gem "jquery-ui-rails", "~> 6.0.1" +gem "jquery-ui-rails", git: "https://github.com/jquery-ui-rails/jquery-ui-rails.git", tag: "v7.0.0" gem "kamifusen"#, path: "../kamifusen" gem "kaminari" gem "leaflet-rails" diff --git a/Gemfile.lock b/Gemfile.lock index d1cd931225a28335683b42d72ebc4912e450a83f..7cdbcd67e65274f94b1aea585201fa1cc1c3a344 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,11 @@ +GIT + remote: https://github.com/jquery-ui-rails/jquery-ui-rails.git + revision: 413265e81f790f795239e07e7e25e01429b2f18d + tag: v7.0.0 + specs: + jquery-ui-rails (7.0.0) + railties (>= 3.2.16) + GIT remote: https://github.com/noesya/summernote-rails.git revision: 32fd182c929cdcacaa6e3bd3569871bd025fa669 @@ -8,9 +16,9 @@ GIT GIT remote: https://github.com/noesya/two_factor_authentication.git - revision: 16fb01e5731c2b08ef0885134e5e0bdec2ed87ff + revision: a3505e961baf7cb0bf68bb3a6349aeaf5e1baf97 specs: - two_factor_authentication (4.1.1) + two_factor_authentication (4.1.2) devise encryptor rails (>= 3.1.1) @@ -112,20 +120,20 @@ GEM autoprefixer-rails (10.4.16.0) execjs (~> 2) aws-eventstream (1.3.0) - aws-partitions (1.859.0) - aws-sdk-core (3.188.0) - aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (1.863.0) + aws-sdk-core (3.190.0) + aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) + aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.73.0) + aws-sdk-kms (1.74.0) aws-sdk-core (~> 3, >= 3.188.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.140.0) - aws-sdk-core (~> 3, >= 3.188.0) + aws-sdk-s3 (1.141.0) + aws-sdk-core (~> 3, >= 3.189.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.6) - aws-sigv4 (1.7.0) + aws-sigv4 (~> 1.8) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) base64 (0.2.0) bcrypt (3.1.20) @@ -248,7 +256,7 @@ GEM ffi (1.16.3) figaro (1.2.0) thor (>= 0.14.0, < 2) - font-awesome-sass (6.4.2) + font-awesome-sass (6.5.1) sassc (~> 2.0) front_matter_parser (1.0.1) geo_calc (0.7.8) @@ -298,7 +306,7 @@ GEM mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) io-console (0.6.0) - irb (1.9.1) + irb (1.10.1) rdoc reline (>= 0.3.8) jbuilder (2.11.5) @@ -309,9 +317,7 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - jquery-ui-rails (6.0.1) - railties (>= 3.2.16) - json (2.6.3) + json (2.7.1) jwt (2.7.1) kamifusen (1.11.2) image_processing @@ -369,7 +375,7 @@ GEM nesty (1.0.2) net-http (0.4.0) uri - net-imap (0.4.6) + net-imap (0.4.7) date net-protocol net-pop (0.1.2) @@ -378,7 +384,7 @@ GEM timeout net-smtp (0.4.0) net-protocol - nio4r (2.6.1) + nio4r (2.7.0) nokogiri (1.15.5-arm64-darwin) racc (~> 1.4) nokogiri (1.15.5-x86_64-darwin) @@ -405,7 +411,7 @@ GEM omniauth-saml (2.1.0) omniauth (~> 2.0) ruby-saml (~> 1.12) - open-uri (0.4.0) + open-uri (0.4.1) stringio time uri @@ -428,7 +434,7 @@ GEM rack (>= 1.2.0) rack-protection (3.1.0) rack (~> 2.2, >= 2.2.4) - rack-session (1.0.1) + rack-session (1.0.2) rack (< 3) rack-test (2.1.0) rack (>= 1.3) @@ -474,10 +480,10 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rdoc (6.6.0) + rdoc (6.6.1) psych (>= 4.0.0) - regexp_parser (2.8.2) - reline (0.4.0) + regexp_parser (2.8.3) + reline (0.4.1) io-console (~> 0.5) requests (1.0.2) require_all (3.0.0) @@ -654,7 +660,7 @@ DEPENDENCIES image_processing jbuilder jquery-rails - jquery-ui-rails (~> 6.0.1) + jquery-ui-rails! kamifusen kaminari leaflet-rails diff --git a/app/assets/javascripts/admin/commons/association.js b/app/assets/javascripts/admin/commons/association.js index b4a0fcd5ef60ab17826587d0d644a773cf68ff86..e93455cbbb97d83a390c2ceac4d2eef0bae9e7ed 100644 --- a/app/assets/javascripts/admin/commons/association.js +++ b/app/assets/javascripts/admin/commons/association.js @@ -10,8 +10,8 @@ $(function () { type: 'POST', url: target, data: { - objectId: id, - objectType: type + 'object_id': id, + 'object_type': type } }).done(function () { location.reload(); diff --git a/app/assets/javascripts/application/plugins/ujs.js b/app/assets/javascripts/application/plugins/ujs.js new file mode 100644 index 0000000000000000000000000000000000000000..132a22b3e0745d916b8607664a26b0841464a491 --- /dev/null +++ b/app/assets/javascripts/application/plugins/ujs.js @@ -0,0 +1,13 @@ +/*global $, jQuery */ +/* This allow ujs requests to automatically inject nonce */ +$(function () { + 'use strict'; + $.ajaxSetup({ + converters: { + 'text script': function (text) { + jQuery.globalEval(text, { nonce: $('meta[name="csp-nonce"]').attr('content') }); + return text; + } + } + }); +}); \ No newline at end of file diff --git a/app/assets/javascripts/devise.js b/app/assets/javascripts/devise.js index 87f4ad18ee3ead168bb2e72b76cfb736557c7246..4cd55b2404226f88157a036863b8fa4a60d0aaae 100644 --- a/app/assets/javascripts/devise.js +++ b/app/assets/javascripts/devise.js @@ -9,6 +9,5 @@ //= require cropperjs/dist/cropper //= require jquery-cropper/dist/jquery-cropper //= require_self -//= require_tree ./admin/plugins window.osuny = {}; diff --git a/app/assets/javascripts/leaflet.js b/app/assets/javascripts/leaflet.js new file mode 100644 index 0000000000000000000000000000000000000000..afba78aec076765102b68eeaebcc5ed116ad9377 --- /dev/null +++ b/app/assets/javascripts/leaflet.js @@ -0,0 +1 @@ +//= require leaflet/dist/leaflet.js \ No newline at end of file diff --git a/app/assets/stylesheets/admin/commons/sidebar.sass b/app/assets/stylesheets/admin/commons/sidebar.sass new file mode 100644 index 0000000000000000000000000000000000000000..dd8be56994922225227e9e2706ff170e25034ab7 --- /dev/null +++ b/app/assets/stylesheets/admin/commons/sidebar.sass @@ -0,0 +1,2 @@ +.sidebar-icon + min-width: 30px \ No newline at end of file diff --git a/app/assets/stylesheets/extranet/layout/_header.sass b/app/assets/stylesheets/extranet/layout/_header.sass index 0056fb05b3145cd6710ecc859bcbffd47197bd64..2f0442340d2f8c85e11c47180d252023ea544bf3 100644 --- a/app/assets/stylesheets/extranet/layout/_header.sass +++ b/app/assets/stylesheets/extranet/layout/_header.sass @@ -12,4 +12,10 @@ header, &__title @extend h1 p - margin-bottom: 0 \ No newline at end of file + margin-bottom: 0 +@include media-breakpoint-up(md) + .organizations-show, + .persons-show + .header + h1 + margin-right: 25% \ No newline at end of file diff --git a/app/assets/stylesheets/leaflet.sass b/app/assets/stylesheets/leaflet.sass new file mode 100644 index 0000000000000000000000000000000000000000..95cd15cc02a44b044174bf6754e4e950c3725eeb --- /dev/null +++ b/app/assets/stylesheets/leaflet.sass @@ -0,0 +1 @@ +@import 'leaflet/dist/leaflet' \ No newline at end of file diff --git a/app/controllers/admin/communication/blocks/headings_controller.rb b/app/controllers/admin/communication/blocks/headings_controller.rb index 53e597801d721d002943bb1879034058a51121e6..d1983eb11a31a6fb8056c5b064face059fed0690 100644 --- a/app/controllers/admin/communication/blocks/headings_controller.rb +++ b/app/controllers/admin/communication/blocks/headings_controller.rb @@ -20,7 +20,12 @@ class Admin::Communication::Blocks::HeadingsController < Admin::Communication::B end def new - @heading.about = PolymorphicObjectFinder.find params, :about + @heading.about = PolymorphicObjectFinder.find( + params, + key: :about, + university: current_university, + only: Communication::Block::Heading.permitted_about_types + ) breadcrumb end diff --git a/app/controllers/admin/communication/blocks_controller.rb b/app/controllers/admin/communication/blocks_controller.rb index 4baa5141c4484bb4f4cf9ba13cd4047d7201d7cb..2e745313da1685cbf62509e26bd66eae8426b135 100644 --- a/app/controllers/admin/communication/blocks_controller.rb +++ b/app/controllers/admin/communication/blocks_controller.rb @@ -18,7 +18,12 @@ class Admin::Communication::BlocksController < Admin::Communication::Application end def new - @block.about = PolymorphicObjectFinder.find params, :about + @block.about = PolymorphicObjectFinder.find( + params, + key: :about, + university: current_university, + only: Communication::Block.permitted_about_types + ) breadcrumb end @@ -61,12 +66,17 @@ class Admin::Communication::BlocksController < Admin::Communication::Application return unless request.xhr? cookies.signed[Communication::Block::BLOCK_COPY_COOKIE] = { value: params[:id], - path: '/admin' + path: '/admin' } end def paste - about = PolymorphicObjectFinder.find(params, :about) + about = PolymorphicObjectFinder.find( + params, + key: :about, + university: current_university, + only: Communication::Block.permitted_about_types + ) # On réattribue à @block pour bénéficier du calcul dans about_path @block = @block.paste(about) cookies.delete(Communication::Block::BLOCK_COPY_COOKIE, path: '/admin') diff --git a/app/controllers/admin/communication/contents_controller.rb b/app/controllers/admin/communication/contents_controller.rb index ff344cda5f4cd5c317525ee6fe728c11f649decb..17f13e68c21d5507b48ad3536b14ce143a0159aa 100644 --- a/app/controllers/admin/communication/contents_controller.rb +++ b/app/controllers/admin/communication/contents_controller.rb @@ -13,8 +13,12 @@ class Admin::Communication::ContentsController < Admin::Communication::Applicati protected def load_about - @about = PolymorphicObjectFinder.find(params, :about) - raise_403_unless @about.university == current_university + @about = PolymorphicObjectFinder.find( + params, + key: :about, + university: current_university, + only: Communication::Block.permitted_about_types + ) raise_403_unless can?(:edit, @about) end end \ No newline at end of file diff --git a/app/controllers/admin/communication/extranets/contacts_controller.rb b/app/controllers/admin/communication/extranets/contacts_controller.rb index 6283e05b3c111c225d3b8c241221eee617bdec51..3223163012c81be9d98098b41da5a85e8c98e8be 100644 --- a/app/controllers/admin/communication/extranets/contacts_controller.rb +++ b/app/controllers/admin/communication/extranets/contacts_controller.rb @@ -53,8 +53,11 @@ class Admin::Communication::Extranets::ContactsController < Admin::Communication protected def load_object - object_type = params[:objectType] - object_id = params[:objectId] - @object = object_type.constantize.find object_id + @object = PolymorphicObjectFinder.find( + params, + key: :object, + university: current_university, + only: Communication::Extranet::Connection.permitted_about_types + ) end end diff --git a/app/controllers/admin/communication/websites/pages_controller.rb b/app/controllers/admin/communication/websites/pages_controller.rb index f788465d0e79b9c662e1cfc2d37bb8872588ab81..d50f72ac0350757b66a21b84ad036d280175ffaf 100644 --- a/app/controllers/admin/communication/websites/pages_controller.rb +++ b/app/controllers/admin/communication/websites/pages_controller.rb @@ -134,9 +134,12 @@ class Admin::Communication::Websites::PagesController < Admin::Communication::We protected def load_object - object_type = params[:objectType] - object_id = params[:objectId] - @object = object_type.constantize.find object_id + @object = PolymorphicObjectFinder.find( + params, + key: :object, + university: current_university, + only: [@page.class.direct_connection_permitted_about_type] + ) end def breadcrumb diff --git a/app/controllers/admin/communication/websites/permalinks_controller.rb b/app/controllers/admin/communication/websites/permalinks_controller.rb index 49a39f0c4d723c22af3f1e60a082819609f8b9ce..7ca26206dace644e43226b8264565f0ba4ec830c 100644 --- a/app/controllers/admin/communication/websites/permalinks_controller.rb +++ b/app/controllers/admin/communication/websites/permalinks_controller.rb @@ -2,7 +2,12 @@ class Admin::Communication::Websites::PermalinksController < Admin::Communicatio def create @path = params['communication_website_permalink']['path'] - @about = PolymorphicObjectFinder.find(params, :about) + @about = PolymorphicObjectFinder.find( + params, + key: :about, + university: current_university, + only: Communication::Website::Permalink.permitted_about_types + ) @permalink = @about.add_redirection(@path) end end \ No newline at end of file diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 122be9729080707ea6d0024449d06e01e7e66d6e..4dc7f7af9db45d3c7ed3bbaadcaee8db2000a549 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -21,9 +21,12 @@ class Admin::UsersController < Admin::ApplicationController def favorite operation = params[:operation] - id = params[:about_id] - type = params[:about_type] - about = type.constantize.find id + about = PolymorphicObjectFinder.find( + params, + key: :about, + university: current_university, + only: User::Favorite.permitted_about_types + ) if operation == 'add' current_user.add_favorite(about) else diff --git a/app/controllers/extranet/account_controller.rb b/app/controllers/extranet/account_controller.rb index 56389029177958ffa40f8e7e89d89e2dbe2f7726..e7ef509d9eb38ba5f51d5efe30c32be3fc52af82 100644 --- a/app/controllers/extranet/account_controller.rb +++ b/app/controllers/extranet/account_controller.rb @@ -11,28 +11,39 @@ class Extranet::AccountController < Extranet::ApplicationController end def update - manage_password - current_user.update user_params - redirect_to account_path, notice: t('extranet.account.updated') + if update_user(user_params) + bypass_sign_in current_user, scope: :user if sign_in_after_change_password? + redirect_to account_path, notice: t('extranet.account.updated') + else + breadcrumb + add_breadcrumb t('extranet.account.edit') + render :edit, status: :unprocessable_entity + end end protected - def manage_password - # to prevent cognitive complexity (the bottom block should be in an if condition where password present) - # Password not provided when user from sso - params[:user][:password] ||= '' - - if params[:user][:password].blank? - params[:user].delete(:password) + def update_user(params) + if params[:password].blank? + params.delete(:current_password) + current_user.update_without_password(params) else - current_user.reset_password(params[:user][:password], params[:user][:password]) + current_user.update_with_password(params) end end def user_params params.require(:user) - .permit(:first_name, :last_name, :email, :mobile_phone, :language_id, :password, :picture, :picture_infos, :picture_delete) + .permit( + :first_name, :last_name, :email, :mobile_phone, :language_id, + :current_password, :password, :password_confirmation, + :picture, :picture_infos, :picture_delete + ) + end + + def sign_in_after_change_password? + return true if user_params[:password].blank? + Devise.sign_in_after_change_password end def breadcrumb diff --git a/app/controllers/server/websites_controller.rb b/app/controllers/server/websites_controller.rb index 61eedc5f944557aa503f10827f6309b0545ac2d3..033ac3daa4b5da0a4fbaea8ca29930baee5b94cc 100644 --- a/app/controllers/server/websites_controller.rb +++ b/app/controllers/server/websites_controller.rb @@ -1,5 +1,6 @@ class Server::WebsitesController < Server::ApplicationController - before_action :load_website, except: :index + before_action :load_websites, only: [:index, :manage_versions, :update_all_themes] + before_action :load_website, except: [:index, :manage_versions, :update_all_themes] has_scope :for_theme_version has_scope :for_production @@ -8,10 +9,22 @@ class Server::WebsitesController < Server::ApplicationController has_scope :for_updatable_theme def index - @websites = apply_scopes(Communication::Website.all).ordered breadcrumb end + def manage_versions + load_filters + breadcrumb + add_breadcrumb "Gestion des versions" + end + + def update_all_themes + @websites.find_each do |website| + website.clean_and_rebuild + end + redirect_back(fallback_location: manage_versions_server_websites_path, notice: t('server_admin.websites.update_all_themes_notice')) + end + def sync_theme_version @website.get_current_theme_version! end @@ -38,6 +51,10 @@ class Server::WebsitesController < Server::ApplicationController add_breadcrumb Communication::Website.model_name.human(count: 2), server_websites_path end + def load_websites + @websites = apply_scopes(Communication::Website.all).ordered + end + def load_website @website = Communication::Website.find params[:id] end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 10a4cba84df37181f4cf310fd85d8f0aaa5d90ba..4fc43214765db741225255f8701d0c53bae7a16f 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,3 +1,13 @@ class ApplicationRecord < ActiveRecord::Base self.abstract_class = true + + def self.models_with_concern(concern) + descendants.select { |model| + model.included_modules.include?(concern) + } + end + + def self.model_names_with_concern(concern) + models_with_concern(concern).map(&:name) + end end diff --git a/app/models/communication/block.rb b/app/models/communication/block.rb index e36664e7a1529c426d7230ba06e8366618b4b9f4..9fe79cf0f33ea1761474c979d1f3f63f8b1997e9 100644 --- a/app/models/communication/block.rb +++ b/app/models/communication/block.rb @@ -102,6 +102,10 @@ class Communication::Block < ApplicationRecord before_save :attach_template_blobs before_validation :set_university_and_website_from_about, on: :create + def self.permitted_about_types + ApplicationRecord.model_names_with_concern(WithBlocks) + end + # When we set data from json, we pass it to the template. # The json we save is first sanitized and prepared by the template. def data=(value) diff --git a/app/models/communication/block/component/text.rb b/app/models/communication/block/component/text.rb index 66db4f641734a098b436b30206d305c58e94d5a1..e15fe3f3e140bc49b9a05d1413878820f52556f2 100644 --- a/app/models/communication/block/component/text.rb +++ b/app/models/communication/block/component/text.rb @@ -1,7 +1,7 @@ class Communication::Block::Component::Text < Communication::Block::Component::Base def data=(value) - @data = Osuny::Sanitizer.sanitize value, 'string' + @data = Osuny::Sanitizer.sanitize value, 'text' end def full_text diff --git a/app/models/communication/block/heading.rb b/app/models/communication/block/heading.rb index e41fc573dac3fcc9d35e58a3b4db248f53afb848..5cefb2f8f65ac610ebb6bd735ea33d86764c170b 100644 --- a/app/models/communication/block/heading.rb +++ b/app/models/communication/block/heading.rb @@ -51,6 +51,10 @@ class Communication::Block::Heading < ApplicationRecord before_validation :compute_level + def self.permitted_about_types + ApplicationRecord.model_names_with_concern(WithBlocks) + end + def references [about] end diff --git a/app/models/communication/extranet/connection.rb b/app/models/communication/extranet/connection.rb index 30cc10e73c1a9daca0b247b86939f03094c171fc..04d435182d15c9819fe821369978c74eb203c39a 100644 --- a/app/models/communication/extranet/connection.rb +++ b/app/models/communication/extranet/connection.rb @@ -3,17 +3,17 @@ # Table name: communication_extranet_connections # # id :uuid not null, primary key -# object_type :string indexed => [object_id] +# about_type :string indexed => [about_id] # created_at :datetime not null # updated_at :datetime not null +# about_id :uuid indexed => [about_type] # extranet_id :uuid not null, indexed -# object_id :uuid indexed => [object_type] # university_id :uuid not null, indexed # # Indexes # # index_communication_extranet_connections_on_extranet_id (extranet_id) -# index_communication_extranet_connections_on_object (object_type,object_id) +# index_communication_extranet_connections_on_object (about_type,about_id) # index_communication_extranet_connections_on_university_id (university_id) # # Foreign Keys @@ -24,5 +24,9 @@ class Communication::Extranet::Connection < ApplicationRecord belongs_to :university belongs_to :extranet, class_name: 'Communication::Extranet' - belongs_to :object, polymorphic: true + belongs_to :about, polymorphic: true + + def self.permitted_about_types + ["University::Organization", "University::Person"] + end end diff --git a/app/models/communication/extranet/post.rb b/app/models/communication/extranet/post.rb index a8a1e16cc56a8754c46f17d8a9f3e90b37446a37..b8c6fe37c7e7fb7d899e2b6f17d30625df76d07f 100644 --- a/app/models/communication/extranet/post.rb +++ b/app/models/communication/extranet/post.rb @@ -34,9 +34,9 @@ # fk_rails_86cc935add (author_id => university_people.id) # class Communication::Extranet::Post < ApplicationRecord + include Contentful include Sanitizable include WithAccessibility - include WithBlocks include WithFeaturedImage include WithPublication include WithPermalink diff --git a/app/models/communication/extranet/with_connected_objects.rb b/app/models/communication/extranet/with_connected_objects.rb index 640985bff751a8f6d1c680dd77461d9aa4ea4acd..3270f65d016c629725ad12a06a4fd20db11378f4 100644 --- a/app/models/communication/extranet/with_connected_objects.rb +++ b/app/models/communication/extranet/with_connected_objects.rb @@ -2,33 +2,33 @@ module Communication::Extranet::WithConnectedObjects extend ActiveSupport::Concern included do - has_many :connections + has_many :connections, dependent: :destroy end def connected?(object) - connections.where(university: university, object: object).any? + connections.where(university: university, about: object).any? end def connect(object) - connections.where(university: university, object: object).first_or_create + connections.where(university: university, about: object).first_or_create end def disconnect(object) - connections.where(university: university, object: object).destroy_all + connections.where(university: university, about: object).destroy_all end def connected_organizations - ids = connections.where(object_type: 'University::Organization').pluck(:object_id) - University::Organization.where(id: ids) + ids = connections.where(about_type: 'University::Organization').pluck(:about_id) + university.organizations.where(id: ids) end def connected_people - ids = connections.where(object_type: 'University::Person').pluck(:object_id) - University::Person.where(id: ids) + ids = connections.where(about_type: 'University::Person').pluck(:about_id) + university.people.where(id: ids) end def experiences_through_connections - University::Person::Experience.where(person_id: connected_people, organization_id: connected_organizations) + university.person_experiences.where(person_id: connected_people, organization_id: connected_organizations) end end diff --git a/app/models/communication/website/agenda/category.rb b/app/models/communication/website/agenda/category.rb index 9ab27e26bccbd1b0dc0f7c0b1efc6e6d5f407f76..e7cb08747accf07841d39d19b3391b7c8a84cd47 100644 --- a/app/models/communication/website/agenda/category.rb +++ b/app/models/communication/website/agenda/category.rb @@ -34,9 +34,9 @@ # class Communication::Website::Agenda::Category < ApplicationRecord include AsDirectObject + include Contentful include Sanitizable include WithBlobs - include WithBlocks include WithFeaturedImage include WithMenuItemTarget include WithPermalink diff --git a/app/models/communication/website/agenda/event.rb b/app/models/communication/website/agenda/event.rb index 23f8c48131f2b670674df07169b6d5d0ba6d9223..d920a93635851de9d8dfead16447f89dfe7e19d7 100644 --- a/app/models/communication/website/agenda/event.rb +++ b/app/models/communication/website/agenda/event.rb @@ -42,10 +42,10 @@ # class Communication::Website::Agenda::Event < ApplicationRecord include AsDirectObject + include Contentful include Sanitizable include WithAccessibility include WithBlobs - include WithBlocks include WithDuplication include WithFeaturedImage include WithMenuItemTarget @@ -75,7 +75,7 @@ class Communication::Website::Agenda::Event < ApplicationRecord scope :for_category, -> (category_id) { joins(:categories).where(communication_website_categories: { id: category_id }).distinct } scope :future, -> { where('from_day > :today', today: Date.today).ordered_asc } - scope :future_or_current, -> { where('from_day <= :today', today: Date.today).ordered_asc } + scope :future_or_current, -> { where('from_day >= :today', today: Date.today).ordered_asc } scope :current, -> { where('(from_day <= :today AND to_day IS NULL) OR (from_day <= :today AND to_day >= :today)', today: Date.today).ordered_asc } scope :archive, -> { where('to_day < :today', today: Date.today).ordered_desc } scope :past, -> { archive } @@ -131,7 +131,7 @@ class Communication::Website::Agenda::Event < ApplicationRecord def dependencies active_storage_blobs + - blocks + + contents_dependencies + [website.config_default_content_security_policy] end diff --git a/app/models/communication/website/page.rb b/app/models/communication/website/page.rb index 6ddca2a2d837f8c13e0f96d7c7052b5d184a7569..78c701a5daab019beb21dcd64afdda5f733676b3 100644 --- a/app/models/communication/website/page.rb +++ b/app/models/communication/website/page.rb @@ -48,11 +48,11 @@ class Communication::Website::Page < ApplicationRecord self.ignored_columns = %w(path) include AsDirectObject + include Contentful include Sanitizable include WithAccessibility include WithAutomaticMenus include WithBlobs - include WithBlocks include WithDuplication include WithFeaturedImage include WithMenuItemTarget @@ -103,7 +103,7 @@ class Communication::Website::Page < ApplicationRecord end def dependencies - calculated_dependencies = active_storage_blobs + blocks + calculated_dependencies = active_storage_blobs + contents_dependencies calculated_dependencies += [website.config_default_content_security_policy] # children are used only if there is no block to display calculated_dependencies += children unless blocks.published.any? @@ -142,6 +142,12 @@ class Communication::Website::Page < ApplicationRecord .where.not(id: id) end + # Some special pages can override this method to allow explicit direct connections + # Example: The Communication::Website::Page::Person special page allows to connect University::Person records directly. + def self.direct_connection_permitted_about_type + nil + end + protected def check_accessibility diff --git a/app/models/communication/website/page/organization.rb b/app/models/communication/website/page/organization.rb index b17a2c96df03ca8f4819200d84a799838bdfbaef..3be3848727842f7944a6ebaa6cdeb484ac87ee75 100644 --- a/app/models/communication/website/page/organization.rb +++ b/app/models/communication/website/page/organization.rb @@ -57,6 +57,10 @@ class Communication::Website::Page::Organization < Communication::Website::Page University::Organization.where(id: ids) end + def self.direct_connection_permitted_about_type + "University::Organization" + end + protected def current_git_path diff --git a/app/models/communication/website/page/person.rb b/app/models/communication/website/page/person.rb index 4c11cd187e8f3ffe3983031d02bd4906cf685d39..55b60fa8c5cfbd88ae4df97a9c9609a4bc8cae79 100644 --- a/app/models/communication/website/page/person.rb +++ b/app/models/communication/website/page/person.rb @@ -56,6 +56,10 @@ class Communication::Website::Page::Person < Communication::Website::Page University::Person.where(id: ids) end + def self.direct_connection_permitted_about_type + "University::Person" + end + protected def current_git_path diff --git a/app/models/communication/website/page/with_type.rb b/app/models/communication/website/page/with_type.rb index 40a787c771a007c85ed3a905be42430dc29b7a72..375794a9624e4636da5b4b01e2c615d50f53bf3f 100644 --- a/app/models/communication/website/page/with_type.rb +++ b/app/models/communication/website/page/with_type.rb @@ -103,8 +103,8 @@ module Communication::Website::Page::WithType def initialize_special_page i18n_key = "communication.website.pages.defaults.#{type_key}" - self.title = I18n.t("#{i18n_key}.title") - self.slug = I18n.t("#{i18n_key}.slug") + self.title = I18n.t("#{i18n_key}.title", locale: language.iso_code) + self.slug = I18n.t("#{i18n_key}.slug", locale: language.iso_code) self.parent = default_parent self.full_width = full_width_by_default? self.published = published_by_default? diff --git a/app/models/communication/website/permalink.rb b/app/models/communication/website/permalink.rb index debfbb16c6d9db3bd07bdb3cfee723e92f7d16b6..12f5dedeb4fe0a113a181857523ac5939d1e7961 100644 --- a/app/models/communication/website/permalink.rb +++ b/app/models/communication/website/permalink.rb @@ -104,6 +104,10 @@ class Communication::Website::Permalink < ApplicationRecord clean_path end + def self.permitted_about_types + ApplicationRecord.model_names_with_concern(WithPermalink) + end + def pattern language = about.respond_to?(:language) ? about.language : website.default_language self.class.pattern_in_website(website, language) diff --git a/app/models/communication/website/post.rb b/app/models/communication/website/post.rb index 7db99e32fd9849d2e6a9d8c8b757569d163d92da..a28f85d16bdc4658a50fb7d10fb1c6c46b70b0ce 100644 --- a/app/models/communication/website/post.rb +++ b/app/models/communication/website/post.rb @@ -40,10 +40,10 @@ # class Communication::Website::Post < ApplicationRecord include AsDirectObject + include Contentful include Sanitizable include WithAccessibility include WithBlobs - include WithBlocks include WithDuplication include WithFeaturedImage include WithMenuItemTarget @@ -118,7 +118,7 @@ class Communication::Website::Post < ApplicationRecord def dependencies active_storage_blobs + - blocks + + contents_dependencies + categories + [author&.author] + [website.config_default_content_security_policy] diff --git a/app/models/communication/website/post/category.rb b/app/models/communication/website/post/category.rb index e9a64759ac1cd1ffa3f8e31e3514df9f7ab49a2f..eaf0474ffef3838bc85cb8e336ee33a9132ae221 100644 --- a/app/models/communication/website/post/category.rb +++ b/app/models/communication/website/post/category.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: communication_website_categories +# Table name: communication_website_post_categories # # id :uuid not null, primary key # featured_image_alt :string @@ -24,27 +24,27 @@ # Indexes # # idx_communication_website_post_cats_on_communication_website_id (communication_website_id) -# index_communication_website_categories_on_language_id (language_id) -# index_communication_website_categories_on_original_id (original_id) -# index_communication_website_categories_on_parent_id (parent_id) -# index_communication_website_categories_on_program_id (program_id) -# index_communication_website_categories_on_slug (slug) -# index_communication_website_categories_on_university_id (university_id) +# index_communication_website_post_categories_on_language_id (language_id) +# index_communication_website_post_categories_on_original_id (original_id) +# index_communication_website_post_categories_on_parent_id (parent_id) +# index_communication_website_post_categories_on_program_id (program_id) +# index_communication_website_post_categories_on_slug (slug) +# index_communication_website_post_categories_on_university_id (university_id) # # Foreign Keys # # fk_rails_3186d8e327 (language_id => languages.id) -# fk_rails_52bd5968c9 (original_id => communication_website_categories.id) -# fk_rails_86a9ce3cea (parent_id => communication_website_categories.id) +# fk_rails_52bd5968c9 (original_id => communication_website_post_categories.id) +# fk_rails_86a9ce3cea (parent_id => communication_website_post_categories.id) # fk_rails_9d4210dc43 (university_id => universities.id) # fk_rails_c7c9f7ddc7 (communication_website_id => communication_websites.id) # fk_rails_e58348b119 (program_id => education_programs.id) # class Communication::Website::Post::Category < ApplicationRecord include AsDirectObject + include Contentful include Sanitizable include WithBlobs - include WithBlocks include WithFeaturedImage include WithMenuItemTarget include WithPermalink @@ -80,7 +80,7 @@ class Communication::Website::Post::Category < ApplicationRecord end def git_path(website) - "#{git_path_content_prefix(website)}categories/#{slug_with_ancestors_slugs}/_index.html" + "#{git_path_content_prefix(website)}posts_categories/#{slug_with_ancestors_slugs}/_index.html" end def template_static @@ -89,7 +89,7 @@ class Communication::Website::Post::Category < ApplicationRecord def dependencies active_storage_blobs + - blocks + + contents_dependencies + children + [website.config_default_content_security_policy] end diff --git a/app/models/concerns/with_blocks.rb b/app/models/concerns/contentful.rb similarity index 90% rename from app/models/concerns/with_blocks.rb rename to app/models/concerns/contentful.rb index 4788d0812029145a4cd3fa83005c32a184dbfc39..8478c1fa455882c17027d071fe478eb430a5465c 100644 --- a/app/models/concerns/with_blocks.rb +++ b/app/models/concerns/contentful.rb @@ -1,4 +1,4 @@ -module WithBlocks +module Contentful extend ActiveSupport::Concern included do @@ -24,6 +24,10 @@ module WithBlocks @contents_full_text ||= contents.collect(&:full_text).join("\r") end + def contents_dependencies + blocks + headings + end + # Basic rule is: TOC if 2 titles or more def show_toc? headings.many? diff --git a/app/models/education/diploma.rb b/app/models/education/diploma.rb index 9146d6e87660c00b7da559f631662c1cf7960b12..655948b20e8a82ed045527aa11304c7a2cd3efce 100644 --- a/app/models/education/diploma.rb +++ b/app/models/education/diploma.rb @@ -25,8 +25,8 @@ # class Education::Diploma < ApplicationRecord include AsIndirectObject + include Contentful include Sanitizable - include WithBlocks include WithGitFiles include WithPermalink include WithSlug diff --git a/app/models/education/program.rb b/app/models/education/program.rb index 17f16defadb306f6dba2589cdef4106f9abed1d5..b8e2c446d5b5afcf81aec038d980bea7f1a1bd1c 100644 --- a/app/models/education/program.rb +++ b/app/models/education/program.rb @@ -53,11 +53,11 @@ class Education::Program < ApplicationRecord include Aboutable include AsIndirectObject + include Contentful include Sanitizable include WithAccessibility include WithAlumni include WithBlobs - include WithBlocks include WithDiploma include WithFeaturedImage include WithGitFiles @@ -146,7 +146,7 @@ class Education::Program < ApplicationRecord def dependencies active_storage_blobs + - blocks + + contents_dependencies + university_people_through_involvements.map(&:teacher) + university_people_through_role_involvements.map(&:administrator) + [diploma] diff --git a/app/models/research/journal/paper.rb b/app/models/research/journal/paper.rb index ba9aee71e07ead5a4d9d10851d1c7a878b83e28b..83946342a9364979794d2a0f95d834fa0da5cf6f 100644 --- a/app/models/research/journal/paper.rb +++ b/app/models/research/journal/paper.rb @@ -47,7 +47,7 @@ class Research::Journal::Paper < ApplicationRecord include AsIndirectObject include Sanitizable include WithBlobs - include WithBlocks + include Contentful include WithCitations include WithGitFiles include WithPermalink @@ -93,7 +93,7 @@ class Research::Journal::Paper < ApplicationRecord def dependencies active_storage_blobs + - blocks + + contents_dependencies + people.map(&:researcher) end diff --git a/app/models/university/organization.rb b/app/models/university/organization.rb index 2798cf884befab8655b8ef9077062645fa17c586..3d2ed70191a43be0f59aede521971f4a02822e9b 100644 --- a/app/models/university/organization.rb +++ b/app/models/university/organization.rb @@ -48,10 +48,10 @@ # class University::Organization < ApplicationRecord include AsIndirectObject + include Contentful include Backlinkable include Sanitizable include WithBlobs - include WithBlocks include WithCountry include WithGeolocation include WithGitFiles diff --git a/app/models/university/person.rb b/app/models/university/person.rb index d7e55cd2dfc55b8db52682f3f4fcf94453364a79..8f7e1823e9eb277c60dde5972d576a8231dbc321 100644 --- a/app/models/university/person.rb +++ b/app/models/university/person.rb @@ -56,9 +56,9 @@ class University::Person < ApplicationRecord include AsIndirectObject include Backlinkable + include Contentful include Sanitizable include WithBlobs - include WithBlocks include WithCountry # WithRoles included before WithEducation because needed for the latter include WithRoles @@ -197,7 +197,7 @@ class University::Person < ApplicationRecord end def dependencies - blocks + + contents_dependencies + active_storage_blobs end diff --git a/app/models/user/favorite.rb b/app/models/user/favorite.rb index a92b1631091e3affdd3c76ef1304d094dbadba49..03876b3453a69abd68e10091c6e0324156fa3a23 100644 --- a/app/models/user/favorite.rb +++ b/app/models/user/favorite.rb @@ -21,4 +21,8 @@ class User::Favorite < ApplicationRecord belongs_to :user belongs_to :about, polymorphic: true + + def self.permitted_about_types + ApplicationRecord.model_names_with_concern(Favoritable) + end end diff --git a/app/services/importers/cleaner.rb b/app/services/importers/cleaner.rb index ac6cf93f2d8dcd355cf3e7fbc41d31a2b7a3b9b2..62bf081d6f234852be97f4c611e1190589060af0 100644 --- a/app/services/importers/cleaner.rb +++ b/app/services/importers/cleaner.rb @@ -9,6 +9,7 @@ module Importers end def self.clean_string(string) + string = string.to_s string = string.gsub(' ', ' ') string = string.gsub('&', '&') string = ActionView::Base.full_sanitizer.sanitize string diff --git a/app/services/polymorphic_object_finder.rb b/app/services/polymorphic_object_finder.rb index 47bf78dcc2dc60a45d67211efcac6dc71819e2ff..4e44c64589c417b78f88a2eef7248901745472d7 100644 --- a/app/services/polymorphic_object_finder.rb +++ b/app/services/polymorphic_object_finder.rb @@ -1,12 +1,31 @@ class PolymorphicObjectFinder - # @block.about = Polymorphic.find params, :about + # @block.about = Polymorphic.find( + # params, + # key: :about, + # university: current_university, + # only: ["Communication::Website::Page"] + # ) # Rails uses ActiveRecord::Inheritance#polymorphic_name to hydrate the about_type. # Example: A Block for a Communication::Website::Page::Home will have about_type = "Communication::Website::Page" - def self.find(params, key) + def self.find(params, key:, university:, only: []) key_id = "#{key}_id".to_sym key_type = "#{key}_type".to_sym - klass = params[key_type].constantize + model_name = self.find_model_name(params, key_type, only) + return if model_name.nil? + + model = model_name.constantize id = params[key_id] - klass.find id + model.where(university: university).find(id) + end + + private + + def self.find_model_name(params, key_type, only) + if only.any? + # Whitelist user input + only.detect { |item| item == params[key_type] } + else + params[key_type] + end end end \ No newline at end of file diff --git a/app/views/admin/communication/blocks/edit.html.erb b/app/views/admin/communication/blocks/edit.html.erb index 202ced7c83c49ce4aa9cdd93f091d907159a059e..53c851c49b23361c6a8e9992cce2f431a438f967 100644 --- a/app/views/admin/communication/blocks/edit.html.erb +++ b/app/views/admin/communication/blocks/edit.html.erb @@ -43,7 +43,7 @@ <%# Include vue.js before call Vue.createApp %> <%= javascript_include_tag 'vue' %> -<script> +<script nonce="<%= request.content_security_policy_nonce %>"> var app = Vue.createApp({ components: { draggable: VueDraggableNext.VueDraggableNext, diff --git a/app/views/admin/communication/blocks/templates/organizations/_show.html.erb b/app/views/admin/communication/blocks/templates/organizations/_show.html.erb index da0688f65b857beb1f33dbadb37b3b4c5d817422..775a2426b883f0d96dee546a20402628f62d6092 100644 --- a/app/views/admin/communication/blocks/templates/organizations/_show.html.erb +++ b/app/views/admin/communication/blocks/templates/organizations/_show.html.erb @@ -25,14 +25,8 @@ <% if block.template.layout == "grid" %> <div class="organizations grid"> <% else # Map %> + <% content_for :leaflet_required, true %> <div class="map" data-marker-icon="<%= image_path 'map-marker.svg' %>"> - <link rel="stylesheet" - href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" - integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" - crossorigin=""/> - <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js" - integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM=" - crossorigin=""></script> <% end %> <% block.template.elements.each do |element| %> <article class="organization" diff --git a/app/views/admin/communication/extranets/_sidebar.html.erb b/app/views/admin/communication/extranets/_sidebar.html.erb index be24c414d50b3add9016031d429897d6873ce4ca..8fee9f9275d3dc0f5a5bc48adb3c78f3803af0cb 100644 --- a/app/views/admin/communication/extranets/_sidebar.html.erb +++ b/app/views/admin/communication/extranets/_sidebar.html.erb @@ -25,7 +25,7 @@ %> <li class="mb-3"> <a class="d-block py-1 <%= active ? 'text-black' : 'text-muted' %>" href="<%= object[:path] %>"> - <i class="<%= object[:icon] %>" style="min-width: 30px"></i> + <i class="sidebar-icon <%= object[:icon] %>"></i> <%= object[:title].html_safe %> </a> </li> diff --git a/app/views/admin/communication/extranets/contacts/_toggle.html.erb b/app/views/admin/communication/extranets/contacts/_toggle.html.erb index 0d8b6960ef6d6fa5bfe9e930931f478a6d3e5b10..23b704726f1696b33b212dc16db5583359bf4e11 100644 --- a/app/views/admin/communication/extranets/contacts/_toggle.html.erb +++ b/app/views/admin/communication/extranets/contacts/_toggle.html.erb @@ -2,8 +2,8 @@ connected = @extranet.connected?(about) path = toggle_admin_communication_extranet_contacts_path( extranet_id: @extranet.id, - objectId: about.id, - objectType: about.class, + object_id: about.id, + object_type: about.class, ) %> <input class="form-check-input" diff --git a/app/views/admin/communication/photo_imports/_selector.html.erb b/app/views/admin/communication/photo_imports/_selector.html.erb index 318a89a28ea04bea3b27069910c19c0e3962b2da..45aca8d0ffd90fd7e873c9e4837483b30496adf1 100644 --- a/app/views/admin/communication/photo_imports/_selector.html.erb +++ b/app/views/admin/communication/photo_imports/_selector.html.erb @@ -157,7 +157,7 @@ pexels_path = admin_communication_pexels_path(website_id: nil, extranet_id: nil, <%# Include vue.js before call Vue.createApp %> <%= javascript_include_tag 'vue' %> -<script> +<script nonce="<%= request.content_security_policy_nonce %>"> var app = Vue.createApp({ data() { return { diff --git a/app/views/admin/communication/websites/_sidebar.html.erb b/app/views/admin/communication/websites/_sidebar.html.erb index 16b0a106a7935443bbbd88270a25f6eed8f6d8c7..9572cc2609e83687f686f7a48862f1eafbec2508 100644 --- a/app/views/admin/communication/websites/_sidebar.html.erb +++ b/app/views/admin/communication/websites/_sidebar.html.erb @@ -52,7 +52,7 @@ %> <li class="mb-3"> <a class="d-block py-1 <%= active ? 'text-black' : 'text-muted' %>" href="<%= object[:path] %>"> - <i class="<%= object[:icon] %>" style="min-width: 30px"></i> + <i class="sidebar-icon <%= object[:icon] %>"></i> <%= object[:title].html_safe %> </a> </li> diff --git a/app/views/admin/communication/websites/pages/show/special_pages/_organization.html.erb b/app/views/admin/communication/websites/pages/show/special_pages/_organization.html.erb index 9292423bb3dd199addde2162b44dfc65b95c9e72..232e8ca990df86ef916cc18f3574779d5502ae94 100644 --- a/app/views/admin/communication/websites/pages/show/special_pages/_organization.html.erb +++ b/app/views/admin/communication/websites/pages/show/special_pages/_organization.html.erb @@ -22,7 +22,7 @@ <tr> <td><%= link_to organization, [:admin, organization] %></td> <td><%= link_to 'Déconnecter', - disconnect_admin_communication_website_page_path(@page, objectId: organization.id, objectType: organization.class), + disconnect_admin_communication_website_page_path(@page, object_id: organization.id, object_type: organization.class), class: button_classes_danger, method: :post %></td> </tr> diff --git a/app/views/admin/communication/websites/pages/show/special_pages/_person.html.erb b/app/views/admin/communication/websites/pages/show/special_pages/_person.html.erb index 0c263addfee0e9a659edc838c79bb93bed010ee7..babffb39e1f03431d2e7e2131891f3d9ad331dfd 100644 --- a/app/views/admin/communication/websites/pages/show/special_pages/_person.html.erb +++ b/app/views/admin/communication/websites/pages/show/special_pages/_person.html.erb @@ -22,7 +22,7 @@ <tr> <td><%= link_to person, [:admin, person] %></td> <td><%= link_to 'Déconnecter', - disconnect_admin_communication_website_page_path(@page, objectId: person.id, objectType: person.class), + disconnect_admin_communication_website_page_path(@page, object_id: person.id, object_type: person.class), class: button_classes_danger, method: :post %></td> </tr> diff --git a/app/views/admin/communication/websites/posts/static.html.erb b/app/views/admin/communication/websites/posts/static.html.erb index f7488dd2763219b917c9e86777ed1105e390dd96..6430ba49ea00aaa811dfc5194098c203108f1986 100644 --- a/app/views/admin/communication/websites/posts/static.html.erb +++ b/app/views/admin/communication/websites/posts/static.html.erb @@ -14,10 +14,6 @@ authors: - "<%= @about.translated_author.slug %>" <% end %> <% if @about.categories.any? %> -categories: -<% @about.categories.each do |category| %> - - "<%= category.slug_with_ancestors_slugs %>" -<% end %> posts_categories: <% @about.categories.each do |category| %> - "<%= category.slug_with_ancestors_slugs %>" diff --git a/app/views/admin/layouts/application.html.erb b/app/views/admin/layouts/application.html.erb index e87875475c5ecfb9f00d9554f57b71dc0a46a456..cf5d4b6c54211e69d077716c8e07783e4844ca4c 100644 --- a/app/views/admin/layouts/application.html.erb +++ b/app/views/admin/layouts/application.html.erb @@ -6,7 +6,7 @@ <title><%= content_for?(:title) ? raw("#{yield(:title)} ∙ Osuny") : 'Osuny' %></title> <%= csrf_meta_tags %> <%= csp_meta_tag %> - <script> + <script nonce="<%= request.content_security_policy_nonce %>"> // Avoid opening menu on load </script> <%= stylesheet_link_tag "admin/#{current_admin_theme}", media: 'all' %> diff --git a/app/views/admin/layouts/preview.html.erb b/app/views/admin/layouts/preview.html.erb index 0e4d2be82f43cc2e25b9f6a8e5c51ab8aedca937..5c348032aa10315da332ab7560ece91f9c3da791 100644 --- a/app/views/admin/layouts/preview.html.erb +++ b/app/views/admin/layouts/preview.html.erb @@ -18,6 +18,10 @@ padding-top: 0; } </style> + <% if content_for?(:leaflet_required) %> + <%= stylesheet_link_tag 'leaflet', media: 'all' %> + <%= javascript_include_tag 'leaflet' %> + <% end %> </head> <body class="full-width"> <header class="hero <%= 'hero--with-image hero--image-square' if yield(:image).present? %>"> @@ -40,4 +44,5 @@ </main> </body> <script src="https://example.osuny.org/js/preview.js"></script> + </html> diff --git a/app/views/admin/research/journals/_sidebar.html.erb b/app/views/admin/research/journals/_sidebar.html.erb index 3c5e1f64edd1a5181e33fd36efd5e994d29bc35a..39fff2df7b1ea4298211ad7f548df1a4ea968d4a 100644 --- a/app/views/admin/research/journals/_sidebar.html.erb +++ b/app/views/admin/research/journals/_sidebar.html.erb @@ -29,7 +29,7 @@ %> <li class="mb-3"> <a class="d-block py-1 <%= active ? 'text-black' : 'text-muted' %>" href="<%= object[:path] %>"> - <i class="<%= object[:icon] %>" style="min-width: 30px"></i> + <i class="sidebar-icon <%= object[:icon] %>"></i> <%= object[:title].html_safe %> </a> </li> diff --git a/app/views/application/_bugsnag.html.erb b/app/views/application/_bugsnag.html.erb index 9847d93b66881498989b2da4fd3977202e86e777..435d2667b181c5759df78c7b9e4198625a123db8 100644 --- a/app/views/application/_bugsnag.html.erb +++ b/app/views/application/_bugsnag.html.erb @@ -1,6 +1,6 @@ <% unless Rails.env.development? %> <script src="//d2wy8f7a9ursnm.cloudfront.net/v7/bugsnag.min.js"></script> - <script type="text/javascript"> + <script type="text/javascript" nonce="<%= request.content_security_policy_nonce %>"> Bugsnag.start({ apiKey: "<%= j ENV['BUGSNAG_JAVASCRIPT_KEY'] %>", releaseStage: "<%= j ENV['APPLICATION_ENV'] %>" diff --git a/app/views/extranet/account/edit.html.erb b/app/views/extranet/account/edit.html.erb index 68ea4442d42e5683edfb31ee619ac3b9e13d8484..d9615f63d78a2a59ab849cb6b63f84ca39f62d06 100644 --- a/app/views/extranet/account/edit.html.erb +++ b/app/views/extranet/account/edit.html.erb @@ -22,9 +22,23 @@ <div class="col-lg-6"> <%= f.input :email %> <%= f.input :mobile_phone %> + </div> + </div> + + <h3 class="mt-5 mb-4"><%= t("devise.passwords.edit.new") %></h3> + + <div class="row"> + <div class="col-lg-6"> + <%= f.input :current_password, + as: :password, + input_html: { autocomplete: "current-password" } %> + </div> + </div> + + <div class="row"> + <div class="col-lg-6"> <%= f.input :password, as: :password_with_hints, - hint: t('admin.password_hint'), allow_password_uncloaking: true, validators: { length: Devise.password_length.first, @@ -33,9 +47,19 @@ numeric_char: true, special_char: Rails.application.config.allowed_special_chars }, + label: t('devise.passwords.edit.new_password'), + required: false, + input_html: { autocomplete: "new-password" } %> + </div> + <div class="col-lg-6"> + <%= f.input :password_confirmation, + as: :password_with_sync, + allow_password_uncloaking: true, + compare_with_field: :password, input_html: { autocomplete: "new-password" } %> </div> </div> + <%= submit f %> <% if current_user.visitor? %> diff --git a/app/views/extranet/layouts/application.html.erb b/app/views/extranet/layouts/application.html.erb index 1a43b0f03be7f1811e0e01325048e6d58c3437bc..f6f93c0fb044da6a40b4fb75f8f952dbeed43370 100644 --- a/app/views/extranet/layouts/application.html.erb +++ b/app/views/extranet/layouts/application.html.erb @@ -2,6 +2,10 @@ <html> <head> <%= render 'extranet/application/head' %> + <% if content_for?(:leaflet_required) %> + <%= stylesheet_link_tag 'leaflet', media: 'all' %> + <%= javascript_include_tag 'leaflet' %> + <% end %> </head> <body class="extranet <%= body_classes %> full-width"> <%= render 'application/notice' %> diff --git a/app/views/server/layouts/application.html.erb b/app/views/server/layouts/application.html.erb index 11b3a9e841fad85e728205ea3838967cb332f502..bf62d98d8f88685a9c49071148e1df3c34e2f7e4 100644 --- a/app/views/server/layouts/application.html.erb +++ b/app/views/server/layouts/application.html.erb @@ -6,7 +6,7 @@ <title><%= content_for?(:title) ? raw("#{yield(:title)} ∙ Osuny") : 'Osuny' %></title> <%= csrf_meta_tags %> <%= csp_meta_tag %> - <script> + <script nonce="<%= request.content_security_policy_nonce %>"> // Avoid opening menu on load </script> <%= stylesheet_link_tag 'admin/pure', media: 'all' %> diff --git a/app/views/server/universities/_sso_mapping.html.erb b/app/views/server/universities/_sso_mapping.html.erb index a353ec6e660120312bb8c5de09b2b39f9b13e98a..1c51ceb03077c8e6df96279c3474d9ada96580b6 100644 --- a/app/views/server/universities/_sso_mapping.html.erb +++ b/app/views/server/universities/_sso_mapping.html.erb @@ -74,7 +74,7 @@ end </div> -<script> +<script nonce="<%= request.content_security_policy_nonce %>"> var app = Vue.createApp({ components: { draggable: VueDraggableNext.VueDraggableNext, diff --git a/app/views/server/websites/_list.html.erb b/app/views/server/websites/_list.html.erb index 0c60c5694cf32eca98209aad247f14ff8facca8c..a033585a978587a1c10a022ff8ce08e765a8047f 100644 --- a/app/views/server/websites/_list.html.erb +++ b/app/views/server/websites/_list.html.erb @@ -5,9 +5,9 @@ <th><%= Communication::Website.human_attribute_name('name') %></th> <th><%= University.model_name.human %></th> <th>Back-office</th> + <th>Site</th> <th colspan="2">Référentiel Git</th> - <th colspan="2">Site</th> - <th> + <th>Version</th> </tr> </thead> <tbody> @@ -19,37 +19,25 @@ <span class="badge bg-success">Prod</span> <% end %> </td> - <td><%= link_to website.university, + <td><%= link_to website.university, [:server, website.university] %></td> - <td><%= link_to 'Admin', - admin_communication_website_url(website, host: website.university.url), + <td><%= link_to 'Admin', + admin_communication_website_url(website, host: website.university.url), target: :_blank, class: 'btn btn-xs btn-light' %></td> - <td><%= link_to 'Référentiel', - website.repository_url, + <td><%= link_to 'Site', + website.url, + target: :_blank, + class: 'btn btn-xs btn-light' if website.url.present? %></td> + <td><%= link_to 'Référentiel', + website.repository_url, target: :_blank, class: 'btn btn-xs btn-light' if website.repository.present? %></td> - <td><%= image_tag website.deployment_status_badge, + <td><%= image_tag website.deployment_status_badge, alt: '' if website.deployment_status_badge.present? %></td> - <td><%= link_to 'Site', - website.url, - target: :_blank, - class: 'btn btn-xs btn-light' if website.url.present? %></td> <td> - <% if website.url.present? %> - <span class="js-version"> - <%= link_to website.theme_version, - sync_theme_version_server_website_path(website), - method: :post, - remote: true if website.theme_version_url.present? %> - </span> - <% end %> + <%= website.theme_version %> </td> - <td><%= link_to t('server_admin.websites.buttons.theme.update'), - update_theme_server_website_path(website), - method: :post, - remote: true, - class: button_classes if website.url.present? && website.github? %></td> </tr> <% end %> </tbody> diff --git a/app/views/server/websites/index.html.erb b/app/views/server/websites/index.html.erb index 32fd7c7a742e28495c9436a523bc83b35cc7c9a4..5bed945b65946e80c99e46e793eaeaa93df62dc8 100644 --- a/app/views/server/websites/index.html.erb +++ b/app/views/server/websites/index.html.erb @@ -1,9 +1,15 @@ <% content_for :title, "#{@websites.count} #{Communication::Website.model_name.human(count: @websites.count).downcase}" %> -<p><%= Communication::Website.in_production.count %> en production</p> +<p><%= @websites.in_production.count %> en production</p> <%= render 'admin/application/filters', current_path: server_websites_path, filters: @filters if @filters.any? %> <%= render 'server/websites/list', websites: @websites %> + +<% content_for :action_bar_left do %> + <%= link_to "Gestion des versions", + manage_versions_server_websites_path, + class: button_classes %> +<% end %> \ No newline at end of file diff --git a/app/views/server/websites/manage_versions.html.erb b/app/views/server/websites/manage_versions.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..b993c8818c3be0961e81f5cf529bdc726baa69f6 --- /dev/null +++ b/app/views/server/websites/manage_versions.html.erb @@ -0,0 +1,65 @@ +<% content_for :title, "Gestion des versions" %> + +<p><%= @websites.in_production.count %> en production</p> + +<%= render 'admin/application/filters', + current_path: manage_versions_server_websites_path, + filters: @filters if @filters.any? %> + +<div class="table-responsive"> + <table class="<%= table_classes %> table-hover"> + <thead> + <tr> + <th><%= Communication::Website.human_attribute_name('name') %></th> + <th><%= University.model_name.human %></th> + <th colspan="2">Référentiel Git</th> + <th>Version</th> + <th> </th> + </tr> + </thead> + <tbody> + <% @websites.ordered.each do |website| %> + <tr id="website-<%= website.id %>"> + <td> + <%= link_to website.name, server_website_path(website) %> + <% if website.in_production %> + <span class="badge bg-success">Prod</span> + <% end %> + </td> + <td><%= link_to website.university, + [:server, website.university] %></td> + <td><%= link_to 'Référentiel', + website.repository_url, + target: :_blank, + class: 'btn btn-xs btn-light' if website.repository.present? %></td> + <td><%= image_tag website.deployment_status_badge, + alt: '' if website.deployment_status_badge.present? %></td> + <td> + <span class="js-version"> + <%= website.theme_version %> + </span> + </td> + <td> + <%= link_to t('server_admin.websites.buttons.theme.sync'), + sync_theme_version_server_website_path(website), + method: :post, + remote: true, + class: button_classes if website.url.present? && website.theme_version_url.present? %> + <%= link_to t('server_admin.websites.buttons.theme.update'), + update_theme_server_website_path(website), + method: :post, + remote: true, + class: button_classes if website.url.present? && website.github? %> + </td> + </tr> + <% end %> + </tbody> + </table> +</div> + +<% content_for :action_bar_left do %> + <%= link_to "Tout mettre à jour", + update_all_themes_server_websites_path(current_scopes), + method: :post, + class: button_classes %> +<% end %> \ No newline at end of file diff --git a/app/views/server/websites/sync_theme_version.js.erb b/app/views/server/websites/sync_theme_version.js.erb index 87c4b39b2d7413305b683d57811c5c203eed6864..a27880911fd5ed5370ae27946e581f00aed3b3d3 100644 --- a/app/views/server/websites/sync_theme_version.js.erb +++ b/app/views/server/websites/sync_theme_version.js.erb @@ -1,2 +1,14 @@ var version = document.querySelector('#website-<%= @website.id %> .js-version'); version.innerHTML = "<%= j @website.theme_version if @website.theme_version_url.present? %>"; +var notyf = new Notyf(); +notyf.open({ + type: 'success', + position: { + x: 'center', + y: 'bottom' + }, + message: "<%= j(t('server_admin.websites.sync_theme_version_notice', website: @website.to_s)) %>", + duration: 5000, + ripple: true, + dismissible: true +}); diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index b3076b38fe14399a56099ba187b1cb21cac15f09..636b9d479f2ca5780f99e944beac6e84aaac45a8 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -4,22 +4,53 @@ # See the Securing Rails Applications Guide for more information: # https://guides.rubyonrails.org/security.html#content-security-policy-header -# Rails.application.configure do -# config.content_security_policy do |policy| -# policy.default_src :self, :https -# policy.font_src :self, :https, :data -# policy.img_src :self, :https, :data -# policy.object_src :none -# policy.script_src :self, :https -# policy.style_src :self, :https -# # Specify URI for violation reports -# # policy.report_uri "/csp-violation-report-endpoint" -# end -# -# # Generate session nonces for permitted importmap, inline scripts, and inline styles. -# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } -# config.content_security_policy_nonce_directives = %w(script-src style-src) -# -# # Report violations without enforcing the policy. -# # config.content_security_policy_report_only = true -# end +Rails.application.configure do + + style_urls = %w() + img_urls = %w() + script_urls = %w( + https://example.osuny.org/js/ + https://plausible.io + https://d2wy8f7a9ursnm.cloudfront.net/v7/ + ) + script_urls << "https://cdn.jsdelivr.net/npm/summernote@#{SummernoteRails::Rails::VERSION.split('.').take(3).join('.')}/dist/lang/" + + font_urls = %w() + media_urls = %w() + frame_urls = %w() + child_urls = %w() + connect_urls = %w() + form_action_urls = %w() + + defaults = %i[self https] + + config.content_security_policy do |policy| + policy.base_uri :none + policy.default_src *defaults + policy.font_src *defaults, :data, *font_urls + policy.img_src *defaults, :data, *img_urls + policy.media_src *defaults, *media_urls + policy.frame_src *defaults, *frame_urls + policy.child_src *defaults, *child_urls + policy.object_src :none + # We specify :unsafe_inline for browsers which not support nonce. + # Unsafe eval is required for Vue scripts + policy.script_src :self, :unsafe_eval, *script_urls + policy.style_src :self, :unsafe_inline, *style_urls + # If you are using webpack-dev-server then specify webpack-dev-server host + # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? + policy.connect_src *defaults, *connect_urls + policy.form_action *defaults, *form_action_urls + + # Specify URI for violation reports + # policy.report_uri "/csp-violation-report-endpoint" + end + + + # Generate session nonces for permitted importmap, inline scripts, and inline styles. + config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } + config.content_security_policy_nonce_directives = %w(script-src) + + # Report violations without enforcing the policy. + # config.content_security_policy_report_only = true +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 31e2c4cac44c1d5501ba4b893da02c834dbc74b3..cc960592d2b3bbba87ffa16d2c6ca0556af4e5fe 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -273,7 +273,7 @@ en: text_line_3_html: "Number of lines in the file: %{number}." text_error_msg: "Line %{line}: %{error}" text_errors_title: "Some errors have occured:" - low_sms_credits: + low_sms_credits: body_1_html: "Warning, your SMS credits are low: %{credits} credits remaining!" body_2_html: "Click <a href=\"%{link}\" target=\"_blank\" style=\"color: #c72b43;\">here</a> to refull." subject: "Osuny - Low SMS Credits (%{credits})" @@ -330,8 +330,10 @@ en: websites: buttons: theme: - sync: Sync - update: Update + sync: Sync version + update: Update theme + sync_theme_version_notice: The theme's version of %{website} has been synced + update_all_themes_notice: All themes will be updated. This can take a few minutes. update_theme_notice: The theme of %{website} will be updated in a moment seo: SEO simple_form: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 304eeb6bad444599f70be388756f7fc3b8179454..38199de0a5cf90ca2a83056f29865464572a2b3f 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -273,7 +273,7 @@ fr: text_line_3_html: "Nombre de lignes traitées : %{number}." text_error_msg: "Ligne %{line} : %{error}" text_errors_title: "Des erreurs sont survenues :" - low_sms_credits: + low_sms_credits: body_1_html: "Attention, vos crédits SMS sont bas : %{credits} crédits restants !" body_2_html: "Cliquez <a href=\"%{link}\" target=\"_blank\" style=\"color: #c72b43;\">ici</a> pour recharger le compte." subject: "Osuny - Credits SMS bas (%{credits})" @@ -330,8 +330,10 @@ fr: websites: buttons: theme: - sync: Sync - update: Mettre à jour + sync: Synchroniser la version + update: Mettre à jour le thème + sync_theme_version_notice: La version du thème de %{website} a été synchronisée + update_all_themes_notice: Tous les thèmes vont être mis à jour. Cela peut prendre quelques minutes. update_theme_notice: Le thème de %{website} va être mis à jour dans quelques instants seo: SEO simple_form: diff --git a/config/routes/server.rb b/config/routes/server.rb index a5c8ee6adfd9b07fad58a245262daa916a4add7c..311ba5d9080f9790206717f69ab425cb82deff34 100644 --- a/config/routes/server.rb +++ b/config/routes/server.rb @@ -2,6 +2,10 @@ namespace :server do resources :universities resources :languages resources :websites do + collection do + get :manage_versions + post :update_all_themes + end member do post :sync_theme_version post :update_theme diff --git a/db/migrate/20231208123041_rename_polymorphic_in_extranet_connections.rb b/db/migrate/20231208123041_rename_polymorphic_in_extranet_connections.rb new file mode 100644 index 0000000000000000000000000000000000000000..a34ff825b333ddfdbc37406c39d35c7b05bcca66 --- /dev/null +++ b/db/migrate/20231208123041_rename_polymorphic_in_extranet_connections.rb @@ -0,0 +1,6 @@ +class RenamePolymorphicInExtranetConnections < ActiveRecord::Migration[7.1] + def change + rename_column :communication_extranet_connections, :object_id, :about_id + rename_column :communication_extranet_connections, :object_type, :about_type + end +end diff --git a/db/schema.rb b/db/schema.rb index c5612a4210889058256602ed82919f6774a8ddd8..d1c031b53ce95ec1fc368860b58ae21d15ac77fa 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2023_11_30_132952) do +ActiveRecord::Schema[7.1].define(version: 2023_12_08_123041) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -118,12 +118,12 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_30_132952) do create_table "communication_extranet_connections", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.uuid "university_id", null: false t.uuid "extranet_id", null: false - t.string "object_type" - t.uuid "object_id" + t.string "about_type" + t.uuid "about_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.index ["about_type", "about_id"], name: "index_communication_extranet_connections_on_object" t.index ["extranet_id"], name: "index_communication_extranet_connections_on_extranet_id" - t.index ["object_type", "object_id"], name: "index_communication_extranet_connections_on_object" t.index ["university_id"], name: "index_communication_extranet_connections_on_university_id" end diff --git a/package.json b/package.json index 11690b06ba4a455b22621bafeea7231ffad8c7e0..df5127b101659a126221cc4c52c5ac20ba2def1a 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "codemirror": "5", "cropperjs": "^1.5.12", "jquery-cropper": "^1.0.1", + "leaflet": "^1.9.4", "notyf": "^3.10.0", "slug": "^5.1.0", "sortablejs": "^1.14.0", diff --git a/test/controllers/extranet/account_controller_test.rb b/test/controllers/extranet/account_controller_test.rb index 058100d742c8be09e59726de228462d0a0beaae2..dacce1be173e7ae06f6c42c50543414c7fa49a74 100644 --- a/test/controllers/extranet/account_controller_test.rb +++ b/test/controllers/extranet/account_controller_test.rb @@ -22,6 +22,6 @@ class Extranet::AccountControllerTest < ActionDispatch::IntegrationTest def test_update_password patch account_path, params: { user: { password: "NewPassw0rd!" } } - assert_redirected_to(account_path) + assert_response(:unprocessable_entity) end end diff --git a/test/fixtures/communication/extranets.yml b/test/fixtures/communication/extranets.yml index c4a72564511a4905256b44eff1b90d9ad95d76db..25967d83f8867a11d1eb0dd3553bcd918c4b285e 100644 --- a/test/fixtures/communication/extranets.yml +++ b/test/fixtures/communication/extranets.yml @@ -45,4 +45,5 @@ default_extranet: name: Extranet de test host: extranet.osuny.test about: default_program (Education::Program) + feature_alumni: true university: default_university diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 936741c8f2ca3149279d765c3a5494e31554020d..2012d80e38e93c1b4f53b5f537acc322a2e558ec 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -61,7 +61,7 @@ alumnus: email: alumnus@osuny.org first_name: Alumnus last_name: Osuny - role: user + role: visitor language: fr university: default_university diff --git a/yarn.lock b/yarn.lock index fd10b729c4d949633b45a9b8949776f393257ade..19e102a7f59441a3797dc7cb142a96a06c65c711 100644 --- a/yarn.lock +++ b/yarn.lock @@ -137,6 +137,11 @@ jquery-cropper@^1.0.1: resolved "https://registry.yarnpkg.com/jquery-cropper/-/jquery-cropper-1.0.1.tgz#6ba9faf1c2c86c0ac3c648d40554ba53673113cf" integrity sha512-KGlY8b0IJQi2Bxe3lqMKmd5Z2Ce4GrnDE5O8Iciza9xCzXISkL6EqX/jFHwnLL1H6Q4FGjoRguuv3lxezsbKJQ== +leaflet@^1.9.4: + version "1.9.4" + resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.9.4.tgz#23fae724e282fa25745aff82ca4d394748db7d8d" + integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA== + magic-string@^0.30.0: version "0.30.4" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.4.tgz#c2c683265fc18dda49b56fc7318d33ca0332c98c"