From 9c45448bdac990f8a020a98369c1a36fdd865532 Mon Sep 17 00:00:00 2001 From: Rob Kaufman Date: Tue, 25 Jul 2023 18:48:54 -0500 Subject: [PATCH 01/12] bump browser tools so that chrome downloads for CI (#1974) * bump browser tools so that chrome downloads * elastic-job moved --- .circleci/config.yml | 27 ++++++++++++++++++--------- Gemfile | 8 ++++---- Gemfile.lock | 26 +++++++++++++------------- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2473c855c..5f08b5735 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,16 +1,19 @@ version: 2.1 orbs: - samvera: samvera/circleci-orb@0 - browser-tools: circleci/browser-tools@1.1 + samvera: samvera/circleci-orb@1 + browser-tools: circleci/browser-tools@1.3 + ruby: circleci/ruby@2 + node: circleci/node@5 + jobs: - bundle_lint_test: + bundle: parameters: ruby_version: type: string - default: 2.7.6 + default: 2.7.8 bundler_version: type: string - default: '2.0.1' + default: 2.4.8 rails_version: type: string default: '5.1.6' @@ -76,7 +79,13 @@ jobs: ruby_version: << parameters.ruby_version >> bundler_version: << parameters.bundler_version >> - samvera/rubocop - - browser-tools/install-browser-tools + - browser-tools/install-chrome + - browser-tools/install-chromedriver + - run: + name: Check Chrome install + command: | + google-chrome --version + chromedriver --version - run: bundle exec rake db:create db:schema:load - run: bin/solrcloud-upload-configset.sh solr/conf - samvera/parallel_rspec @@ -85,7 +94,7 @@ jobs: workflows: ci: jobs: - - bundle_lint_test: - ruby_version: "2.7.6" - name: "ruby2-7-6" + - bundle: + ruby_version: "2.7.8" + name: "ruby2-7-8" solr_config_path: 'solr/conf' diff --git a/Gemfile b/Gemfile index 80ea9047a..3b697e5ef 100644 --- a/Gemfile +++ b/Gemfile @@ -67,9 +67,9 @@ group :test do gem 'rspec-its' gem 'rspec-retry' gem 'rspec_junit_formatter' - gem 'selenium-webdriver' + gem 'selenium-webdriver', '4.8.1' gem 'shoulda-matchers', '~> 4.0' - gem 'webdrivers', '~> 4.0' + gem 'webdrivers', '~> 4.7.0' gem 'webmock' end @@ -119,8 +119,8 @@ group :aws, :test do end group :aws do - gem 'active_elastic_job', git: 'https://github.com/tawan/active-elastic-job.git', - branch: 'ec51c5d9dedc4a1b47f2db41f26d5fceb251e979' + gem 'active_elastic_job', git: 'https://github.com/active-elastic-job/active-elastic-job.git', + ref: 'ec51c5d9dedc4a1b47f2db41f26d5fceb251e979' gem 'aws-sdk-sqs' end diff --git a/Gemfile.lock b/Gemfile.lock index 677576988..cbec47d0f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,12 @@ +GIT + remote: https://github.com/active-elastic-job/active-elastic-job.git + revision: ec51c5d9dedc4a1b47f2db41f26d5fceb251e979 + ref: ec51c5d9dedc4a1b47f2db41f26d5fceb251e979 + specs: + active_elastic_job (2.0.1) + aws-sdk-sqs (~> 1) + rails (>= 4.2) + GIT remote: https://github.com/samvera-labs/hyrax-doi.git revision: d494a50ef8ce3eae594c7ed7148c33b3c977d4a7 @@ -33,15 +42,6 @@ GIT rails (~> 5.0) rdf-vocab (~> 3.0) -GIT - remote: https://github.com/tawan/active-elastic-job.git - revision: ec51c5d9dedc4a1b47f2db41f26d5fceb251e979 - branch: ec51c5d9dedc4a1b47f2db41f26d5fceb251e979 - specs: - active_elastic_job (2.0.1) - aws-sdk-sqs (~> 1) - rails (>= 4.2) - GEM remote: https://rubygems.org/ specs: @@ -746,7 +746,7 @@ GEM mime-types-data (3.2023.0218.1) mini_magick (4.12.0) mini_mime (1.1.2) - mini_portile2 (2.8.2) + mini_portile2 (2.8.4) minitest (5.18.1) mods (2.4.1) edtf @@ -1048,7 +1048,7 @@ GEM sass (~> 3.5, >= 3.5.5) secure_headers (6.5.0) select2-rails (3.5.11) - selenium-webdriver (4.9.0) + selenium-webdriver (4.8.1) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -1268,7 +1268,7 @@ DEPENDENCIES sass-rails (~> 5.0) scss_lint secure_headers - selenium-webdriver + selenium-webdriver (= 4.8.1) shoulda-matchers (~> 4.0) sidekiq (< 7.0) simplecov @@ -1279,7 +1279,7 @@ DEPENDENCIES tether-rails turbolinks (~> 5) web-console (>= 3.3.0) - webdrivers (~> 4.0) + webdrivers (~> 4.7.0) webmock BUNDLED WITH From 151ec87e05a848bacfac2821e6fb31c73cc18a83 Mon Sep 17 00:00:00 2001 From: Summer Cook Date: Tue, 1 Aug 2023 12:53:33 -0300 Subject: [PATCH 02/12] move view file to correct place --- {spec => app}/views/hyrax/contact_form/new.html.erb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {spec => app}/views/hyrax/contact_form/new.html.erb (100%) diff --git a/spec/views/hyrax/contact_form/new.html.erb b/app/views/hyrax/contact_form/new.html.erb similarity index 100% rename from spec/views/hyrax/contact_form/new.html.erb rename to app/views/hyrax/contact_form/new.html.erb From 60a7d27c9d587a31e3715e036f3d7158e6a043f9 Mon Sep 17 00:00:00 2001 From: Kirk Wang Date: Sat, 5 Aug 2023 22:33:44 -0700 Subject: [PATCH 03/12] Make sidebar auto collapse at smaller widths This commit will make the sidebar collapse at smaller widths. To activate the sidebar, the user can hover their mouse over the collapsed sidebar or click the already existing toggle button. --- app/assets/javascripts/hyrax/app.js.erb | 222 ++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 app/assets/javascripts/hyrax/app.js.erb diff --git a/app/assets/javascripts/hyrax/app.js.erb b/app/assets/javascripts/hyrax/app.js.erb new file mode 100644 index 000000000..37372cdcf --- /dev/null +++ b/app/assets/javascripts/hyrax/app.js.erb @@ -0,0 +1,222 @@ +// OVERRIDE Hyrax v3.5.0 to have a sidebar maximize and minimize based on window size + +// Once, javascript is written in a modular format, all initialization +// code should be called from here. +Hyrax = { + initialize: function () { + this.popovers(); + this.permissions(); + this.notifications(); + this.transfers(); + this.workEditor(); + this.fileManager(); + this.selectWorkType(); + this.selectCollectionType(); + this.datatable(); + this.adminSetEditor(); + this.collectionEditor(); + this.collectionsV2(); + this.collectionTypes(); + this.collectionTypeEditor(); + this.collectionUtilities(); + this.adminStatisticsGraphs(); + this.sortAndPerPage(); + this.sidebar(); + this.batchSelect(); + this.internationalizationHelper(); + }, + + // The AdminSet edit page + adminSetEditor: function() { + var AdminSetControls = require('hyrax/admin/admin_set_controls'); + var controls = new AdminSetControls($('#admin-set-controls')); + }, + + // The collectionType edit page + collectionTypeEditor: function() { + var CollectionTypeControls = require('hyrax/admin/collection_type_controls'); + var controls = new CollectionTypeControls($('#collection-types-controls')); + }, + + // The Collection edit page + collectionEditor: function() { + var CollectionControls = require('hyrax/collections/editor'); + var controls = new CollectionControls($('#collection-edit-controls')); + }, + + // Collections v2 - collections related js should (over time) be moved here + // from 'collections.js' to take advantage of shared modules + collectionsV2: function() { + var CollectionsV2 = require('hyrax/collections_v2'); + new CollectionsV2(); + }, + + // Collection types + collectionTypes: function() { + var CollectionTypes = require('hyrax/collection_types'); + var collection_types = new CollectionTypes($('.collection-types-wrapper')) + }, + + collectionUtilities: function() { + var CollectionUtilities = require('hyrax/collections_utils'); + new CollectionUtilities(); + }, + + // Pretty graphs on the dashboard page + adminStatisticsGraphs: function() { + var AdminGraphs = require('hyrax/admin/graphs'); + new AdminGraphs(Hyrax.statistics); + }, + + // Sortable/pageable tables + datatable: function () { + // This keeps the datatable from being added to a table that already has it. + // This is a problem when turbolinks is active. + if ($('.dataTables_wrapper').length === 0) { + $('.datatable').DataTable(); + } + }, + + internationalizationHelper: function () { + var InternationalizationHelper = require('hyrax/i18n_helper'); + new InternationalizationHelper(); + }, + + // The work edit page + workEditor: function () { + var element = $("[data-behavior='work-form']") + if (element.length > 0) { + var Editor = require('hyrax/editor'); + new Editor(element).init(); + } + }, + + // Popover help modals. Used on the user profile page. + popovers: function () { + $("a[data-toggle=popover]").popover({html: true}) + .on("click", function () { + return false; + }); + }, + + // Add access grants for a user/group to a work/fileset/collection + // TODO: This could get moved to workEditor() or similar + permissions: function () { + var PermissionsControl = require('hyrax/permissions/control'); + // On the edit work page + new PermissionsControl($("#share"), 'tmpl-work-grant'); + // On the edit fileset page + new PermissionsControl($("#permission"), 'tmpl-file-set-grant', { with_visibility_component: true }); + // On the batch edit page + new PermissionsControl($("#form_permissions"), 'tmpl-work-grant'); + // On the edit collection page + new PermissionsControl($("#collection_permissions"), 'tmpl-collection-grant'); + }, + + // ActionCable for user notifications. This is displayed in the navbar. + notifications: function() { + // Do not create a consumer if user is not logged in + if ($("meta[name='current-user']").length === 0) + return; + <% if Hyrax.config.realtime_notifications? %> + var consumer = ActionCable.createConsumer("<%= Hyrax::Engine.routes.url_helpers.notifications_endpoint_path %>"); + consumer.subscriptions.create("Hyrax::NotificationsChannel", { + connected: function(data) { + this.perform("update_locale", { locale: $('html').attr('lang') }); + }, + + received: function(data) { + var Notification = require('hyrax/notification'); + new Notification($('.notify-number')).update(data.notifications_count, data.notifications_label); + } + }); + <% end %> + }, + + // Search for a user to transfer a work to + transfers: function () { + $("#proxy_deposit_request_transfer_to").userSearch(); + }, + + // Popover menu to select the type of work when starting a deposit + selectWorkType: function () { + var SelectWorkType = require('hyrax/select_work_type'); + $("[data-behavior=select-work]").each(function () { + new SelectWorkType($(this)); + }); + }, + + // Popover menu to select the type when creating a new collection + selectCollectionType: function () { + var SelectCollectionType = require('hyrax/select_collection_type'); + $("[data-behavior=select-collection]").each(function () { + new SelectCollectionType($(this)); // eslint-disable-line no-new + }); + }, + + // OVERRIDE start + // Commented out the original sidebar function and replaced it with the one below it + // Minimize/maximize the dashboard sidebar + // sidebar: function () { + // $('.sidebar-toggle').on('click', function() { + // $('.sidebar, .main-content').toggleClass('maximized') + // }) + // }, + + // Minimize/maximize the dashboard sidebar + sidebar: function () { + $('.sidebar-toggle').on('click', function() { + $('.sidebar').toggleClass('maximized'); + if ($(window).width() >= 768) { + $('.main-content').toggleClass('maximized'); + } + }); + + $('.sidebar').on('mouseenter', function() { + if ($(window).width() < 768) { + $('.sidebar').addClass('maximized'); + } + }); + + $('.sidebar').on('mouseleave', function() { + if ($(window).width() < 768) { + $('.sidebar').removeClass('maximized'); + } + }); + + $(window).on('resize', function() { + if ($(window).width() >= 768) { + $('.sidebar, .main-content').addClass('maximized'); + } else { + $('.sidebar, .main-content').removeClass('maximized'); + } + }).trigger('resize'); + }, + // OVERRIDE end + + // Add and reorder files attached to works + fileManager: function () { + var FileManager = require('hyrax/file_manager'); + new FileManager(); + }, + + // Per Page select will submit its form to change records shown + sortAndPerPage: function () { + var SortAndPerPage = require('hyrax/sort_and_per_page'); + $('#sort, #per_page').each(function () { + new SortAndPerPage($(this)); + }); + }, + + // Saved so that inline javascript can put data somewhere. + statistics: {}, + + // initialized in hyrax/config.js + config: {}, + + // Adds selected items to the batch before any batch operation is performed + batchSelect: function () { + var BatchSelect = require('hyrax/batch_select'); + BatchSelect.initialize_batch_selected(); + } +}; From 6832cd62ef428ce8629c2b9b0a6a10c1741febcd Mon Sep 17 00:00:00 2001 From: Kirk Wang Date: Sun, 6 Aug 2023 11:36:44 -0700 Subject: [PATCH 04/12] Add title attributes to sidebar links This commit will add title links to the sidebar links in the dashboard so users can hover over the links and see what it says. --- .../hyku/collapsable_section_presenter.rb | 31 ++++++++++++++ app/presenters/hyku/menu_presenter.rb | 13 ++++++ app/views/hyrax/dashboard/_sidebar.html.erb | 2 +- .../dashboard/sidebar/_activity.html.erb | 40 ++++++++++++------- .../dashboard/sidebar/_configuration.html.erb | 35 ++++++++++------ .../sidebar/_repository_content.html.erb | 12 ++++-- .../hyrax/dashboard/sidebar/_tasks.html.erb | 15 ++++--- config/locales/en.yml | 2 + spec/presenters/hyku/menu_presenter_spec.rb | 27 +++++++++++++ 9 files changed, 140 insertions(+), 37 deletions(-) create mode 100644 app/presenters/hyku/collapsable_section_presenter.rb diff --git a/app/presenters/hyku/collapsable_section_presenter.rb b/app/presenters/hyku/collapsable_section_presenter.rb new file mode 100644 index 000000000..f513b56a0 --- /dev/null +++ b/app/presenters/hyku/collapsable_section_presenter.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Hyku + # Draws a collapsable list widget using the Bootstrap 3 / Collapse.js plugin + class CollapsableSectionPresenter < Hyrax::CollapsableSectionPresenter + # Override Hyrax 3.5.0 to pass in html_options + # rubocop:disable Metrics/ParameterLists + def initialize(view_context:, text:, id:, icon_class:, open:, html_options: {}) + # rubocop:enable Metrics/ParameterLists + super(view_context: view_context, text: text, id: id, icon_class: icon_class, open: open) + @html_options = html_options + end + + attr_reader :html_options + + private + + def button_tag + tag.a({ role: 'button', + class: "#{button_class}collapse-toggle", + data: { toggle: 'collapse' }, + href: "##{id}", + onclick: "toggleCollapse(this)", + 'aria-expanded' => open, + 'aria-controls' => id }.merge(html_options)) do + safe_join([tag.span('', class: icon_class, 'aria-hidden': true), + tag.span(text)], ' ') + end + end + end +end diff --git a/app/presenters/hyku/menu_presenter.rb b/app/presenters/hyku/menu_presenter.rb index 28f9790c4..7ed0c9e8f 100644 --- a/app/presenters/hyku/menu_presenter.rb +++ b/app/presenters/hyku/menu_presenter.rb @@ -48,5 +48,18 @@ def show_task? can?(:read, Hyrax::Group) || can?(:read, :admin_dashboard) end + + # Draw a collaspable menu section. The passed block should contain
  • items. + # Override Hyrax 3.5.0 to pass in html_options + # rubocop:disable Metrics/ParameterLists + def collapsable_section(text, id:, icon_class:, open:, **html_options, &block) + # rubocop:enable Metrics/ParameterLists + CollapsableSectionPresenter.new(view_context: view_context, + text: text, + id: id, + icon_class: icon_class, + open: open, + html_options: html_options).render(&block) + end end end diff --git a/app/views/hyrax/dashboard/_sidebar.html.erb b/app/views/hyrax/dashboard/_sidebar.html.erb index 0bb77748f..c91d9f0f4 100644 --- a/app/views/hyrax/dashboard/_sidebar.html.erb +++ b/app/views/hyrax/dashboard/_sidebar.html.erb @@ -13,7 +13,7 @@
  • - <%= link_to hyrax.dashboard_path do %> + <%= link_to hyrax.dashboard_path, title: t('hyrax.admin.sidebar.dashboard') do %> <%= t('hyrax.admin.sidebar.dashboard') %> <% end %>
  • diff --git a/app/views/hyrax/dashboard/sidebar/_activity.html.erb b/app/views/hyrax/dashboard/sidebar/_activity.html.erb index fa9abc0e5..a75c4eb07 100644 --- a/app/views/hyrax/dashboard/sidebar/_activity.html.erb +++ b/app/views/hyrax/dashboard/sidebar/_activity.html.erb @@ -4,12 +4,15 @@ <%= menu.collapsable_section t('hyrax.admin.sidebar.repository_activity'), icon_class: "fa fa-line-chart", id: 'collapseRepositoryActivity', - open: menu.repository_activity_section? do %> - <%= menu.nav_link(hyrax.dashboard_path) do %> + open: menu.repository_activity_section?, + title: t('hyrax.admin.sidebar.repository_activity') do %> + <%= menu.nav_link(hyrax.dashboard_path, + title: t('hyrax.admin.sidebar.activity_summary')) do %> <%= t('hyrax.admin.sidebar.activity_summary') %> <% end %> <% if menu.show_admin_menu_items? %> - <%= menu.nav_link(main_app.status_path) do %> + <%= menu.nav_link(main_app.status_path, + title: t('hyrax.admin.sidebar.system_status')) do %> <%= t('hyrax.admin.sidebar.system_status') %> <% end %> <% end %> @@ -20,22 +23,27 @@ <%= menu.collapsable_section t('hyrax.admin.sidebar.user_activity'), icon_class: "fa fa-line-chart", id: 'collapseUserActivity', - open: menu.user_activity_section? do %> + open: menu.user_activity_section?, + title: t('hyrax.admin.sidebar.user_activity') do %> <%= menu.nav_link(hyrax.dashboard_profile_path(current_user), - also_active_for: hyrax.edit_dashboard_profile_path(current_user)) do %> + also_active_for: hyrax.edit_dashboard_profile_path(current_user), + title: t('hyrax.admin.sidebar.profile')) do %> <%= t('hyrax.admin.sidebar.profile') %> <% end %> - <%= menu.nav_link(hyrax.notifications_path) do %> + <%= menu.nav_link(hyrax.notifications_path, + title: t('hyrax.admin.sidebar.notifications')) do %> <%= t('hyrax.admin.sidebar.notifications') %> <% end %> - <%= menu.nav_link(hyrax.transfers_path) do %> + <%= menu.nav_link(hyrax.transfers_path, + title: t('hyrax.admin.sidebar.transfers')) do %> <%= t('hyrax.admin.sidebar.transfers') %> <% end %> <% if Flipflop.proxy_deposit? %> - <%= menu.nav_link(hyrax.depositors_path) do %> + <%= menu.nav_link(hyrax.depositors_path, + title: t('hyrax.dashboard.manage_proxies')) do %> <%= t('hyrax.dashboard.manage_proxies') %> <% end %> <% end %> @@ -43,7 +51,8 @@ <% if can? :read, :admin_dashboard %> - <%= menu.nav_link(hyrax.admin_stats_path) do %> + <%= menu.nav_link(hyrax.admin_stats_path, + title: t('hyrax.admin.sidebar.statistics')) do %> <%= t('hyrax.admin.sidebar.statistics') %> <% end %> <% end %> @@ -51,18 +60,21 @@ <% if current_ability.can_create_any_work? && Hyrax.config.analytics? %>
  • <%= menu.collapsable_section t('hyrax.admin.sidebar.analytics'), - icon_class: "fa fa-pie-chart", - id: 'collapseAnalytics', - open: menu.analytics_reporting_section? do %> + icon_class: "fa fa-pie-chart", + id: 'collapseAnalytics', + open: menu.analytics_reporting_section?, + title: t('hyrax.admin.sidebar.analytics') do %> <% if can? :read, :admin_dashboard %> <%= menu.nav_link(hyrax.admin_analytics_collection_reports_path, - onclick: "dontChangeAccordion(event);") do %> + onclick: "dontChangeAccordion(event);", + title: t('hyrax.admin.sidebar.collections_report')) do %> <%= t('hyrax.admin.sidebar.collections_report') %> <% end %> <% end %> <%= menu.nav_link(hyrax.admin_analytics_work_reports_path, - onclick: "dontChangeAccordion(event);") do %> + onclick: "dontChangeAccordion(event);", + title: t('hyrax.admin.sidebar.works_report')) do %> <%= t('hyrax.admin.sidebar.works_report') %> <% end %> diff --git a/app/views/hyrax/dashboard/sidebar/_configuration.html.erb b/app/views/hyrax/dashboard/sidebar/_configuration.html.erb index 18dca7795..19423d996 100644 --- a/app/views/hyrax/dashboard/sidebar/_configuration.html.erb +++ b/app/views/hyrax/dashboard/sidebar/_configuration.html.erb @@ -3,45 +3,54 @@ <% if can? :manage, Site %>
  • <%= menu.collapsable_section t('hyrax.admin.sidebar.settings'), - icon_class: "fa fa-cog", - id: 'collapseSettings', - open: menu.settings_section? do %> - - <%= menu.nav_link(main_app.edit_admin_account_path) do %> + icon_class: "fa fa-cog", + id: 'collapseSettings', + open: menu.settings_section?, + title: t('hyrax.admin.sidebar.settings') do %> + <%= menu.nav_link(main_app.edit_admin_account_path, + title: t('hyrax.admin.sidebar.account')) do %> <%= t('hyrax.admin.sidebar.account') %> <% end %> - <%= menu.nav_link(main_app.edit_site_labels_path) do %> + <%= menu.nav_link(main_app.edit_site_labels_path, + title: t('hyrax.admin.sidebar.labels')) do %> <%= t('hyrax.admin.sidebar.labels') %> <% end %> <% if can?(:update, :appearance) %> - <%= menu.nav_link(hyrax.admin_appearance_path) do %> + <%= menu.nav_link(hyrax.admin_appearance_path, + title: t('hyrax.admin.sidebar.appearance')) do %> <%= t('hyrax.admin.sidebar.appearance') %> <% end %> <% end %> <% if can?(:manage, :collection_types) %> - <%= menu.nav_link(hyrax.admin_collection_types_path) do %> + <%= menu.nav_link(hyrax.admin_collection_types_path, + title: t('hyrax.admin.sidebar.collection_types')) do %> <%= t('hyrax.admin.sidebar.collection_types') %> <% end %> <% end %> <% if can?(:manage, Hyrax::Feature) %> - <%= menu.nav_link(hyrax.edit_pages_path) do %> + <%= menu.nav_link(hyrax.edit_pages_path, + title: t('hyrax.admin.sidebar.pages')) do %> <%= t('hyrax.admin.sidebar.pages') %> <% end %> - <%= menu.nav_link(hyrax.edit_content_blocks_path) do %> + <%= menu.nav_link(hyrax.edit_content_blocks_path, + title: t('hyrax.admin.sidebar.content_blocks')) do %> <%= t('hyrax.admin.sidebar.content_blocks') %> <% end %> - <%= menu.nav_link(hyrax.admin_features_path) do %> + <%= menu.nav_link(hyrax.admin_features_path, + title: t('hyrax.admin.sidebar.technical')) do %> <%= t('hyrax.admin.sidebar.technical') %> <% end %> - <%= menu.nav_link('/admin/work_types/edit') do %> + <%= menu.nav_link('/admin/work_types/edit', + title: t('hyku.admin.work_types')) do %> <%= t('hyku.admin.work_types') %> <% end %> <% end %>
  • <% end %> <% if can?(:manage, Sipity::WorkflowResponsibility) %> - <%= menu.nav_link(hyrax.admin_workflow_roles_path) do %> + <%= menu.nav_link(hyrax.admin_workflow_roles_path, + title: t('hyrax.admin.sidebar.workflow_roles')) do %> <%= t('hyrax.admin.sidebar.workflow_roles') %> <% end %> <% end # end of configuration block %> diff --git a/app/views/hyrax/dashboard/sidebar/_repository_content.html.erb b/app/views/hyrax/dashboard/sidebar/_repository_content.html.erb index 3358b78d2..556de0170 100644 --- a/app/views/hyrax/dashboard/sidebar/_repository_content.html.erb +++ b/app/views/hyrax/dashboard/sidebar/_repository_content.html.erb @@ -3,22 +3,26 @@ <%= menu.nav_link(hyrax.my_collections_path, onclick: "dontChangeAccordion(event);", - also_active_for: hyrax.dashboard_collections_path) do %> + also_active_for: hyrax.dashboard_collections_path, + title: t('hyrax.admin.sidebar.collections')) do %> <%= t('hyrax.admin.sidebar.collections') %> <% end %> <%= menu.nav_link(hyrax.my_works_path, onclick: "dontChangeAccordion(event);", - also_active_for: hyrax.dashboard_works_path) do %> + also_active_for: hyrax.dashboard_works_path, + title: t('hyrax.admin.sidebar.works')) do %> <%= t('hyrax.admin.sidebar.works') %> <% end %> <% if ENV.fetch('HYKU_BULKRAX_ENABLED', 'true') == 'true' %> - <%= menu.nav_link(bulkrax.importers_path) do %> + <%= menu.nav_link(bulkrax.importers_path, + title: t('bulkrax.admin.sidebar.importers')) do %> <%= t('bulkrax.admin.sidebar.importers') %> <% end %> - <%= menu.nav_link(bulkrax.exporters_path) do %> + <%= menu.nav_link(bulkrax.exporters_path, + title: t('bulkrax.admin.sidebar.exporters')) do %> <%= t('bulkrax.admin.sidebar.exporters') %> <% end %> <% end %> diff --git a/app/views/hyrax/dashboard/sidebar/_tasks.html.erb b/app/views/hyrax/dashboard/sidebar/_tasks.html.erb index a69b883e3..30a932867 100644 --- a/app/views/hyrax/dashboard/sidebar/_tasks.html.erb +++ b/app/views/hyrax/dashboard/sidebar/_tasks.html.erb @@ -4,32 +4,37 @@ <% if can? :review, :submissions %> <%= menu.nav_link(hyrax.admin_workflows_path, - onclick: "dontChangeAccordion(event);") do %> + onclick: "dontChangeAccordion(event);", + title: t('hyrax.admin.sidebar.workflow_review')) do %> <%= t('hyrax.admin.sidebar.workflow_review') %> <% end %> <% end %> <% if can? :read, User %> <%= menu.nav_link(hyrax.admin_users_path, - onclick: "dontChangeAccordion(event);") do %> + onclick: "dontChangeAccordion(event);", + title: t('hyrax.admin.sidebar.users')) do %> <%= t('hyrax.admin.sidebar.users') %> <% end %> <% end %> <% if can? :read, Hyrax::Group %> - <%= menu.nav_link(main_app.admin_groups_path) do %> + <%= menu.nav_link(main_app.admin_groups_path, + title: t('hyrax.admin.sidebar.manage_groups')) do %> <%= t('hyrax.admin.sidebar.manage_groups') %> <% end %> <% end %> <% if can? :read, :admin_dashboard %> <%= menu.nav_link(hyrax.embargoes_path, - onclick: "dontChangeAccordion(event);") do %> + onclick: "dontChangeAccordion(event);", + title: t('hyrax.embargoes.index.manage_embargoes')) do %> <%= t('hyrax.embargoes.index.manage_embargoes') %> <% end %> <%= menu.nav_link(hyrax.leases_path, - onclick: "dontChangeAccordion(event);") do %> + onclick: "dontChangeAccordion(event);", + title: t('hyrax.leases.index.manage_leases')) do %> <%= t('hyrax.leases.index.manage_leases') %> <% end %> <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index ac880a592..cf6606563 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -194,10 +194,12 @@ en: fonts: "Fonts" themes: "Themes" sidebar: + account: Account accounts: Accounts activity_summary: Activity Summary labels: Labels manage_groups: Manage Groups + repository_activity: Repository Activity system_status: System Status users: destroy: diff --git a/spec/presenters/hyku/menu_presenter_spec.rb b/spec/presenters/hyku/menu_presenter_spec.rb index 912592270..9db639664 100644 --- a/spec/presenters/hyku/menu_presenter_spec.rb +++ b/spec/presenters/hyku/menu_presenter_spec.rb @@ -119,4 +119,31 @@ it { is_expected.to be true } end end + + describe "#collapsable_section" do + let(:text) { "Sample Text" } + let(:id) { "sample_id" } + let(:icon_class) { "sample-icon-class" } + let(:open) { true } + let(:html_options) { { class: "sample-class", data: { test: "test" } } } + let(:block) { proc { "
  • Item
  • " } } + + let(:presenter) { instance_double("CollapsableSectionPresenter") } + + before do + allow(Hyku::CollapsableSectionPresenter).to receive(:new).with( + view_context: context, + text: text, + id: id, + icon_class: icon_class, + open: open, + html_options: html_options + ).and_return(presenter) + end + + it "calls the render method on the CollapsableSectionPresenter with the given block" do + expect(presenter).to receive(:render) + instance.collapsable_section(text, id: id, icon_class: icon_class, open: open, **html_options, &block) + end + end end From f28ede597e96d9ccd12e5c27bba08e39f08b56e2 Mon Sep 17 00:00:00 2001 From: Kirk Wang Date: Sun, 6 Aug 2023 19:55:41 -0700 Subject: [PATCH 05/12] Add custom alt text to images This commit will ensure that the custom alt text that is set in the Dashboard > Settings > Appearance > Default Images section will be used in the partials that display the images. Also a new help text was added to the collection dashboard page to more accurately describe the action. --- app/views/_logo.html.erb | 2 +- .../hyrax/collections/_media_display.html.erb | 4 +- .../collections/_list_collections.html.erb | 80 +++++++++++++++++++ .../dashboard/works/_list_works.html.erb | 39 +++++++++ .../my/collections/_list_collections.html.erb | 79 ++++++++++++++++++ app/views/hyrax/my/works/_list_works.html.erb | 41 ++++++++++ config/locales/hyrax.en.yml | 1 + 7 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 app/views/hyrax/dashboard/collections/_list_collections.html.erb create mode 100644 app/views/hyrax/dashboard/works/_list_works.html.erb create mode 100644 app/views/hyrax/my/collections/_list_collections.html.erb create mode 100644 app/views/hyrax/my/works/_list_works.html.erb diff --git a/app/views/_logo.html.erb b/app/views/_logo.html.erb index 841216770..3776707b4 100644 --- a/app/views/_logo.html.erb +++ b/app/views/_logo.html.erb @@ -1,7 +1,7 @@ <% if logo_image %> <% else %> + + <%# Collection details %> +
    +
    +

    + <%= t("hyrax.dashboard.my.collection_list.description") %> +
    <%= collection_presenter.description&.first %> +

    +

    + <%= t("hyrax.dashboard.my.collection_list.edit_access") %> +
    + <% if collection_presenter.edit_groups.present? %> + <%= t("hyrax.dashboard.my.collection_list.groups") %> <%= collection_presenter.edit_groups.join(', ') %> +
    + <% end %> + <%= t("hyrax.dashboard.my.collection_list.users") %> <%= collection_presenter.edit_people.join(', ') %> +

    +
    +
    + + <% if !current_ability.admin? %> + <%= collection_presenter.managed_access %> + <% end %> + + <%= collection_presenter.collection_type_badge %> + + <%= collection_presenter.modified_date %> + <%= collection_presenter.total_viewable_items %> + <%= collection_presenter.permission_badge %> + + <% if collection_presenter.solr_document.admin_set? %> + <%= render '/hyrax/my/admin_set_action_menu', admin_set_presenter: collection_presenter %> + <% else %> + <%= render '/hyrax/my/collection_action_menu', collection_presenter: collection_presenter %> + <% end %> + + diff --git a/app/views/hyrax/dashboard/works/_list_works.html.erb b/app/views/hyrax/dashboard/works/_list_works.html.erb new file mode 100644 index 000000000..4c0405c59 --- /dev/null +++ b/app/views/hyrax/dashboard/works/_list_works.html.erb @@ -0,0 +1,39 @@ +<%# OVERRIDE Hyrax 3.5.0 to add appropriate alt tag %> + + + + + <%= render 'hyrax/batch_select/add_button', document: document %>  + + +
    + <%= link_to [main_app, document], class: 'media-left' do %> + <%= render_thumbnail_tag document, { class: 'hidden-xs file_listing_thumbnail', alt: block_for(name: 'default_work_image_text') || "#{document.title_or_label} #{t('hyrax.homepage.admin_sets.thumbnail')}" }, { suppress_link: true } %> + <% end %> + +
    +
    + + <%= link_to [main_app, document], id: "src_copy_link#{document.id}", class: 'document-title' do %> + + <%= t("hyrax.dashboard.my.sr.show_label") %> + + <%= document.title_or_label %> + <% end %> + +
    + <%= render_collection_links(document) %> + +
    +
    +
    + + + <%= document.date_modified %> + <%= presenter.workflow.state_label %> + <%= render_visibility_link document %> + + + <%= render 'work_action_menu', document: document %> + + diff --git a/app/views/hyrax/my/collections/_list_collections.html.erb b/app/views/hyrax/my/collections/_list_collections.html.erb new file mode 100644 index 000000000..7450a51e3 --- /dev/null +++ b/app/views/hyrax/my/collections/_list_collections.html.erb @@ -0,0 +1,79 @@ +<%# OVERRIDE Hyrax 3.5.0 to add appropriate alt tag %> + +<% # used by Your Collections tab %> +<% id = collection_presenter.id %> +<%# Data attributes referenced by the javascript for submitting nested forms. %> + + + + <%# OVERRIDE begin %> + + <%# OVERRIDE end %> + <% if collection_presenter.allow_batch? %> + + <% else %> + + <% end %> + + +
    +
    + <% if (collection_presenter.thumbnail_path == nil) %> + + <% else %> + <%# OVERRIDE begin %> + <%= image_tag(collection_presenter.thumbnail_path, alt: block_for(name: 'default_collection_image_text') || "#{collection_presenter.title_or_label} #{t('hyrax.dashboard.my.sr.thumbnail')}") %> + <%# OVERRIDE end %> + <% end %> +
    + <%= link_to collection_presenter.show_path, id: "src_copy_link#{id}" do %> + <%= t("hyrax.dashboard.my.sr.show_label") %> + <%= collection_presenter.title_or_label %> + <% end %> + + <%# Expand arrow %> + + + <%= "#{t("hyrax.dashboard.my.sr.detail_label")} #{collection_presenter.title_or_label}" %> + +
    + + <%# Collection details %> +
    +
    +

    + <%= t("hyrax.dashboard.my.collection_list.description") %> +
    <%= collection_presenter.description&.first %> +

    +

    + <%= t("hyrax.dashboard.my.collection_list.edit_access") %> +
    + <% if collection_presenter.edit_groups.present? %> + <%= t("hyrax.dashboard.my.collection_list.groups") %> <%= collection_presenter.edit_groups.join(', ') %> +
    + <% end %> + <%= t("hyrax.dashboard.my.collection_list.users") %> <%= collection_presenter.edit_people.join(', ') %> +

    +
    +
    + + + <%= collection_presenter.collection_type_badge %> + + <%= collection_presenter.modified_date %> + <%= collection_presenter.total_viewable_items %> + <%= collection_presenter.permission_badge %> + + <% if collection_presenter.solr_document.admin_set? %> + <%= render '/hyrax/my/admin_set_action_menu', admin_set_presenter: collection_presenter %> + <% else %> + <%= render 'hyrax/my/collection_action_menu', collection_presenter: collection_presenter %> + <% end %> + + diff --git a/app/views/hyrax/my/works/_list_works.html.erb b/app/views/hyrax/my/works/_list_works.html.erb new file mode 100644 index 000000000..7b3424bad --- /dev/null +++ b/app/views/hyrax/my/works/_list_works.html.erb @@ -0,0 +1,41 @@ +<%# OVERRIDE Hyrax 3.5.0 to add appropriate alt tag %> + + + + + + <%= render 'hyrax/batch_select/add_button', document: document %>  + + + +
    + <%= link_to [main_app, document], class: 'media-left' do %> + <%= render_thumbnail_tag document, { class: 'hidden-xs file_listing_thumbnail', alt: block_for(name: 'default_work_image_text') || "#{document.title_or_label} #{t('hyrax.homepage.admin_sets.thumbnail')}" }, { suppress_link: true } %> + <% end %> + +
    +
    + + <%= link_to [main_app, document], id: "src_copy_link#{document.id}", class: 'document-title' do %> + + <%= t("hyrax.dashboard.my.sr.show_label") %> + + <%= document.title_or_label %> + <% end %> + +
    + <%= render_collection_links(document) %> + +
    +
    +
    + + <%= document.date_modified %> + + + <%= render_visibility_link document %> + + + <%= render 'work_action_menu', document: document %> + + diff --git a/config/locales/hyrax.en.yml b/config/locales/hyrax.en.yml index 48efb8da8..851d80e94 100644 --- a/config/locales/hyrax.en.yml +++ b/config/locales/hyrax.en.yml @@ -707,6 +707,7 @@ en: shared: Works Shared with Me sr: batch_checkbox: Check to add to a collection or edit list + collections_batch_checkbox: Check to batch delete collections. check_all_label: Select all files to be added to a collection or edited detail_label: Display summary details of listing: Listing of items you have deposited in From 000a4189b61898695e9893fd11cb700d0cbb130e Mon Sep 17 00:00:00 2001 From: Kirk Wang Date: Sun, 6 Aug 2023 20:06:48 -0700 Subject: [PATCH 06/12] Update translations This commit will run the i18n translate-missing task to update from en to all other supported languages. --- config/locales/de.yml | 2 ++ config/locales/es.yml | 3 +++ config/locales/fr.yml | 2 ++ config/locales/hyrax.de.yml | 1 + config/locales/hyrax.es.yml | 1 + config/locales/hyrax.fr.yml | 1 + config/locales/hyrax.it.yml | 1 + config/locales/hyrax.pt-BR.yml | 1 + config/locales/hyrax.zh.yml | 2 ++ config/locales/it.yml | 3 +++ config/locales/pt-BR.yml | 3 +++ config/locales/zh.yml | 3 +++ 12 files changed, 23 insertions(+) diff --git a/config/locales/de.yml b/config/locales/de.yml index 509246c3d..85f989222 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -163,6 +163,7 @@ de: alert: Bitte laden Sie vor dem Absenden mindestens eine Datei hoch. hint: Für Standardbilder sollten Sie ein Bild (JPG, GIF oder PNG) verwenden, das die gleichen Abmessungen für Höhe und Breite aufweist (100 Pixel breit und 100 Pixel hoch). directory_image: + hint: Um ein Bild als Verzeichnisbild zu verwenden, sollten Sie ein Bild (JPG, GIF oder PNG) verwenden, das nicht höher als der Header und nicht breiter als 400 Pixel ist. facet_panel_background_color: hint: Gilt für Facetten und zusätzliche Abschnittsüberschriften auf den Arbeitsseiten einiger Themen. facet_panel_text_color: @@ -197,6 +198,7 @@ de: fonts: Schriftarten themes: Themen sidebar: + account: Konto accounts: Konten activity_summary: Aktivitätsübersicht content_blocks: Inhaltsblöcke diff --git a/config/locales/es.yml b/config/locales/es.yml index c32422801..9135dac39 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -164,6 +164,7 @@ es: alert: Cargue al menos un archivo antes de enviarlo. hint: Para las imágenes predeterminadas, debe usar una imagen (JPG, GIF o PNG) que tenga las mismas dimensiones de alto y ancho (100 píxeles de ancho y 100 píxeles de alto) directory_image: + hint: Para usar una imagen como imagen de directorio, debe usar una imagen (JPG, GIF o PNG) que no sea más alta que el encabezado ni más ancha que 400 píxeles de ancho. facet_panel_background_color: hint: Se aplica a facetas y encabezados de sección adicionales en las páginas de trabajo en algunos temas. facet_panel_text_color: @@ -198,10 +199,12 @@ es: fonts: Fuentes themes: Temas sidebar: + account: Cuenta accounts: Cuentas activity_summary: Resumen de la actividad labels: Etiquetas manage_groups: Administrar Grupos + repository_activity: Actividad del repositorio system_status: Estado del sistema users: destroy: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ef3838b09..0a62d7d8a 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -164,6 +164,7 @@ fr: alert: Veuillez télécharger au moins un fichier avant de soumettre. hint: Pour les images par défaut, vous devez utiliser une image (JPG, GIF ou PNG) qui a des dimensions de hauteur et de largeur égales (100 pixels de large et 100 pixels de haut) directory_image: + hint: Pour utiliser une image comme image de répertoire, vous devez utiliser une image (JPG, GIF ou PNG) qui ne dépasse pas l'en-tête et ne dépasse pas 400 pixels de large. facet_panel_background_color: hint: S'applique aux facettes et aux en-têtes de section supplémentaires sur les pages de travail dans certains thèmes. facet_panel_text_color: @@ -198,6 +199,7 @@ fr: fonts: Les Polices themes: Thèmes sidebar: + account: Compte accounts: Comptes activity_summary: Résumé de l'activité content_blocks: Blocs de contenu diff --git a/config/locales/hyrax.de.yml b/config/locales/hyrax.de.yml index 8deaf5d2d..58d29919a 100644 --- a/config/locales/hyrax.de.yml +++ b/config/locales/hyrax.de.yml @@ -693,6 +693,7 @@ de: sr: batch_checkbox: Aktivieren Sie diese Option, um sie zu einer Sammlung oder Bearbeitungsliste hinzuzufügen check_all_label: Wählen Sie alle Dateien aus, die einer Sammlung hinzugefügt oder bearbeitet werden sollen + collections_batch_checkbox: Aktivieren Sie diese Option, um Sammlungen stapelweise zu löschen. detail_label: Zeigen Sie zusammenfassende Details von an listing: Auflistung der Artikel, in denen Sie hinterlegt haben press_to: drücke um zu diff --git a/config/locales/hyrax.es.yml b/config/locales/hyrax.es.yml index 229d13587..eba264e70 100644 --- a/config/locales/hyrax.es.yml +++ b/config/locales/hyrax.es.yml @@ -694,6 +694,7 @@ es: sr: batch_checkbox: Marque para agregar a una colección o editar lista check_all_label: Seleccione todos los archivos para agregar a una colección o editar + collections_batch_checkbox: Marque para eliminar colecciones por lotes. detail_label: Mostrar detalles de resumen de listing: Listado de artículos que ha depositado en press_to: Presione para diff --git a/config/locales/hyrax.fr.yml b/config/locales/hyrax.fr.yml index 41a1008d9..916364db5 100644 --- a/config/locales/hyrax.fr.yml +++ b/config/locales/hyrax.fr.yml @@ -692,6 +692,7 @@ fr: sr: batch_checkbox: Cocher pour ajouter à une collection ou modifier la liste check_all_label: Sélectionnez tous les fichiers à ajouter à une collection ou à modifier + collections_batch_checkbox: Cochez pour supprimer les collections par lots. detail_label: Afficher les détails du résumé de listing: Liste des articles dans lesquels vous avez déposé press_to: Appuyez sur pour diff --git a/config/locales/hyrax.it.yml b/config/locales/hyrax.it.yml index d55c46559..b2e818d40 100644 --- a/config/locales/hyrax.it.yml +++ b/config/locales/hyrax.it.yml @@ -694,6 +694,7 @@ it: sr: batch_checkbox: Seleziona per aggiungere a una raccolta o modificare l'elenco check_all_label: Seleziona tutti i file da aggiungere a una raccolta o modificati + collections_batch_checkbox: Selezionare per eliminare in batch le raccolte. detail_label: Visualizza i dettagli di riepilogo di listing: Elenco degli oggetti in cui hai depositato press_to: Premere per diff --git a/config/locales/hyrax.pt-BR.yml b/config/locales/hyrax.pt-BR.yml index 66e00b9b5..d84484e71 100644 --- a/config/locales/hyrax.pt-BR.yml +++ b/config/locales/hyrax.pt-BR.yml @@ -694,6 +694,7 @@ pt-BR: sr: batch_checkbox: Marque para adicionar a uma coleção ou editar uma lista check_all_label: Selecione todos os arquivos a serem adicionados a uma coleção ou editados + collections_batch_checkbox: Marque para excluir coleções em lote. detail_label: Exibir detalhes resumidos de listing: Lista de itens nos quais você depositou press_to: Pressione para diff --git a/config/locales/hyrax.zh.yml b/config/locales/hyrax.zh.yml index 14faf7d0e..2d76de1c6 100644 --- a/config/locales/hyrax.zh.yml +++ b/config/locales/hyrax.zh.yml @@ -694,6 +694,7 @@ zh: sr: batch_checkbox: 检查添加到收藏夹或编辑列表 check_all_label: 选择所有要添加到集合或编辑的文件 + collections_batch_checkbox: 勾选批量删除集合。 detail_label: 显示的摘要详细信息 listing: 您存放的物品清单 press_to: 按到 @@ -1164,6 +1165,7 @@ zh: default_button_background_color: 默认按钮背景颜色 default_button_border_color: 默认按钮边框颜色 default_button_text_color: 默认按钮文字颜色 + description: 描述 embargo_release_date: 直到 facet_panel_background_color: 分面面板背景色 facet_panel_text_color: 构面面板文本颜色 diff --git a/config/locales/it.yml b/config/locales/it.yml index 3dac2dc53..b78b1024a 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -164,6 +164,7 @@ it: alert: Carica almeno un file prima di inviarlo. hint: Per le immagini predefinite, è necessario utilizzare un'immagine (JPG, GIF o PNG) che abbia le stesse dimensioni in altezza e larghezza (100 pixel in larghezza e 100 pixel in altezza) directory_image: + hint: Per utilizzare un'immagine come immagine della directory, devi utilizzare un'immagine (JPG, GIF o PNG) non più alta dell'intestazione e non più larga di 400 pixel. facet_panel_background_color: hint: Si applica ai facet e alle intestazioni di sezione aggiuntive nelle pagine di lavoro in alcuni temi. facet_panel_text_color: @@ -198,10 +199,12 @@ it: fonts: Font themes: Temi sidebar: + account: Account accounts: conti activity_summary: Riepilogo attività labels: etichette manage_groups: Gestisci gruppi + repository_activity: Attività di deposito system_status: Stato del sistema users: destroy: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index bc0e4b36d..bd48c908c 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -164,6 +164,7 @@ pt-BR: alert: Faça upload de pelo menos um arquivo antes de enviar. hint: Para imagens padrão, você deve usar uma imagem (JPG, GIF ou PNG) que tenha dimensões iguais de altura e largura (100 pixels de largura e 100 pixels de altura) directory_image: + hint: Para usar uma imagem como imagem do diretório, você deve usar uma imagem (JPG, GIF ou PNG) que não seja mais alta que o cabeçalho e não tenha mais de 400 pixels de largura. facet_panel_background_color: hint: Aplica-se a facetas e cabeçalhos de seção adicionais nas páginas de trabalho em alguns temas. facet_panel_text_color: @@ -198,10 +199,12 @@ pt-BR: fonts: Fontes themes: Temas sidebar: + account: Conta accounts: Contas activity_summary: Resumo da atividade labels: Etiquetas manage_groups: Gerenciar grupos + repository_activity: Atividade do repositório system_status: Status do sistema users: destroy: diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 6664ff588..323279a57 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -164,6 +164,7 @@ zh: alert: 请至少上传一个文件,然后再提交。 hint: 对于默认图像,您应该使用高度和宽度尺寸(宽度为100像素,高度为100像素)的图像(JPG,GIF或PNG) directory_image: + hint: 要将图像用作目录图像,应使用高度不超过标题且宽度不超过 400 像素的图像(JPG、GIF 或 PNG)。 facet_panel_background_color: hint: 适用于某些主题中工作页面上的分面和附加节标题。 facet_panel_text_color: @@ -198,10 +199,12 @@ zh: fonts: 字形 themes: 主题 sidebar: + account: 帐户 accounts: 帐号 activity_summary: 活动摘要 labels: 标签 manage_groups: 管理组 + repository_activity: 存储库活动 system_status: 系统状态 users: destroy: From dfb4dc8e55d8ba5a023b963e010b4c76a28cc409 Mon Sep 17 00:00:00 2001 From: Kirk Wang Date: Sun, 6 Aug 2023 21:34:59 -0700 Subject: [PATCH 07/12] Update IIIF Print This commit will update IIIF Print to use the updated representative_media partial which has the custom thumbnail and alt text. --- Gemfile.lock | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index cbec47d0f..f8e6888a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -31,13 +31,14 @@ GIT GIT remote: https://github.com/scientist-softserv/iiif_print.git - revision: 18e4826bcb0caec6b8ebf74d0b9ec407c398e995 + revision: 9e7837ce4bd08bf8fff9126455d0e0e2602f6018 branch: main specs: iiif_print (1.0.0) blacklight_iiif_search (~> 1.0) + derivative-rodeo (~> 0.5) dry-monads (~> 1.4.0) - hyrax (>= 2.5, < 4.0) + hyrax (>= 2.5, < 4) nokogiri (>= 1.13.2) rails (~> 5.0) rdf-vocab (~> 3.0) @@ -313,6 +314,15 @@ GEM declarative-option (0.1.0) deprecation (1.1.0) activesupport + derivative-rodeo (0.5.0) + activesupport (>= 5) + aws-sdk-s3 + aws-sdk-sqs + httparty + marcel + mime-types + mini_magick + nokogiri devise (4.9.2) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -476,6 +486,9 @@ GEM html_tokenizer (0.0.7) htmlentities (4.3.4) http_logger (0.7.0) + httparty (0.21.0) + mini_mime (>= 1.0.0) + multi_xml (>= 0.5.2) httpclient (2.8.3) hydra-access-controls (11.0.7) active-fedora (>= 10.0.0) From 9b7b8734a7a82af991682ae3f7b6931b5c9c1c21 Mon Sep 17 00:00:00 2001 From: Rob Kaufman Date: Mon, 7 Aug 2023 13:22:36 -0700 Subject: [PATCH 08/12] dont copy files in to docker until build of child --- Dockerfile | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8dcc29ec9..be593ae27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,15 +81,17 @@ COPY --chown=1001:101 ./ops/exiftool_image_to_fits.xslt /app/fits/xml/exiftool/e RUN ln -sf /usr/lib/libmediainfo.so.0 /app/fits/tools/mediainfo/linux/libmediainfo.so.0 && \ ln -sf /usr/lib/libzen.so.0 /app/fits/tools/mediainfo/linux/libzen.so.0 -FROM hyku-base as hyku-web +ONBUILD COPY --chown=1001:101 $APP_PATH/bin/db-migrate-seed.sh /app/samvera/ + +ONBUILD COPY --chown=1001:101 $APP_PATH/Gemfile* /app/samvera/hyrax-webapp/ +ONBUILD RUN bundle install --jobs "$(nproc)" -COPY --chown=1001:101 $APP_PATH/Gemfile* /app/samvera/hyrax-webapp/ -RUN bundle install --jobs "$(nproc)" +ONBUILD COPY --chown=1001:101 $APP_PATH /app/samvera/hyrax-webapp -COPY --chown=1001:101 $APP_PATH/bin/db-migrate-seed.sh /app/samvera/ -COPY --chown=1001:101 $APP_PATH /app/samvera/hyrax-webapp +ONBUILD RUN RAILS_ENV=production SECRET_KEY_BASE=`bin/rake secret` DB_ADAPTER=nulldb DB_URL='postgresql://fake' bundle exec rake assets:precompile && yarn install -RUN RAILS_ENV=production SECRET_KEY_BASE=`bin/rake secret` DB_ADAPTER=nulldb DB_URL='postgresql://fake' bundle exec rake assets:precompile && yarn install + +FROM hyku-base as hyku-web CMD ./bin/web FROM hyku-web as hyku-worker From b1a266b577018496ddbdf7decdffd7db882faf1c Mon Sep 17 00:00:00 2001 From: Alisha Evans Date: Wed, 6 Sep 2023 12:34:49 -0500 Subject: [PATCH 09/12] add instructions for accessing hyku.test on windows machines --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aad9a886b..e1bf078ca 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,15 @@ dory up docker-compose up web ``` -This command starts the whole stack in individual containers allowing Rails to be started or stopped independent of the other services. Once that starts (you'll see the line `Passenger core running in multi-application mode.` to indicate a successful boot), you can view your app in a web browser with at either hyku.test or localhost:3000 (see above). When done `docker-compose stop` shuts down everything. +This command starts the whole stack in individual containers allowing Rails to be started or stopped independent of the other services. Once that starts (you'll see the line `Passenger core running in multi-application mode.` or `Listening on tcp://0.0.0.0:3000` to indicate a successful boot), you can view your app in a web browser at either hyku.test or localhost:3000 (see above). When done `docker-compose stop` shuts down everything. + +_NOTE: if you're on a Windows machine, dory is running but you're unable to access hyku.test, try the steps below:_ + - Run this in the terminal: `ip addr | grep eth0 | grep inet` + - Copy the first IP address from the result in your terminal + - Use the steps under "Change the File Manually" at [this link](https://www.hostinger.co.uk/tutorials/how-to-edit-hosts-file#:~:text=Change%20the%20File%20Manually,-Press%20Start%20and&text=Once%20in%20Notepad%2C%20go%20to,space%2C%20then%20your%20domain%20name) to open your host file + - At the bottom of the host file add this line: ` hyku.test` + - Save + _You may or may not need to restart your server_ #### Tests in Docker From b83495c8c168bbb7d517c828b469591dc4729e62 Mon Sep 17 00:00:00 2001 From: Alisha Evans Date: Wed, 6 Sep 2023 13:06:22 -0500 Subject: [PATCH 10/12] refctor the windows readme comment into its own section --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e1bf078ca..83b7e2218 100644 --- a/README.md +++ b/README.md @@ -59,13 +59,13 @@ docker-compose up web This command starts the whole stack in individual containers allowing Rails to be started or stopped independent of the other services. Once that starts (you'll see the line `Passenger core running in multi-application mode.` or `Listening on tcp://0.0.0.0:3000` to indicate a successful boot), you can view your app in a web browser at either hyku.test or localhost:3000 (see above). When done `docker-compose stop` shuts down everything. -_NOTE: if you're on a Windows machine, dory is running but you're unable to access hyku.test, try the steps below:_ - - Run this in the terminal: `ip addr | grep eth0 | grep inet` - - Copy the first IP address from the result in your terminal - - Use the steps under "Change the File Manually" at [this link](https://www.hostinger.co.uk/tutorials/how-to-edit-hosts-file#:~:text=Change%20the%20File%20Manually,-Press%20Start%20and&text=Once%20in%20Notepad%2C%20go%20to,space%2C%20then%20your%20domain%20name) to open your host file - - At the bottom of the host file add this line: ` hyku.test` - - Save - _You may or may not need to restart your server_ +#### Troubleshooting on Windows +1. Dory is running but you're unable to access hyku.test: + - Run this in the terminal: `ip addr | grep eth0 | grep inet` + - Copy the first IP address from the result in your terminal + - Use the steps under "Change the File Manually" at [this link](https://www.hostinger.co.uk/tutorials/how-to-edit-hosts-file#:~:text=Change%20the%20File%20Manually,-Press%20Start%20and&text=Once%20in%20Notepad%2C%20go%20to,space%2C%20then%20your%20domain%20name) to open your host file + - At the bottom of the host file add this line: ` hyku.test` + - Save (_You may or may not need to restart your server_) #### Tests in Docker From 6bfdc32af886e0ca569a07994af1fa0e130c21db Mon Sep 17 00:00:00 2001 From: Alisha Evans Date: Wed, 6 Sep 2023 13:07:19 -0500 Subject: [PATCH 11/12] add note on adding files to a new work on windows quote> quote> co-authored-by: summer --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 83b7e2218..1be23b504 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,9 @@ This command starts the whole stack in individual containers allowing Rails to b - Use the steps under "Change the File Manually" at [this link](https://www.hostinger.co.uk/tutorials/how-to-edit-hosts-file#:~:text=Change%20the%20File%20Manually,-Press%20Start%20and&text=Once%20in%20Notepad%2C%20go%20to,space%2C%20then%20your%20domain%20name) to open your host file - At the bottom of the host file add this line: ` hyku.test` - Save (_You may or may not need to restart your server_) - +2. When creating a work and adding a file, you get an internal server error due to ownership/permissions issues of the tmp directory: + - Gain root access to the container (in a slightly hacky way, check_volumes container runs from root): `docker compose run check_volumes bash` + - Change ownership to app: `chown -R app:app /app/samvera/hyrax-webapp` #### Tests in Docker The full spec suite can be run in docker locally. There are several ways to do this, but one way is to run the following: From f0a54ed6600e755a48b3f57a3880e3d9a60af3a7 Mon Sep 17 00:00:00 2001 From: Rob Kaufman Date: Mon, 11 Sep 2023 14:42:32 -0500 Subject: [PATCH 12/12] SSO Interface and Setup for SAML, OpenID Connect and CAS (#1976) * working saml, cas, probably openid_connect merge all the omniauth related code together solve saml metadata issue, put in saml redirect loading page, put in choose your sso page remove shibboleth since it can be done with saml and saml is more configurable --------- Co-authored-by: Alisha Evans --- .gitguardian.yaml | 5 + Gemfile | 181 +++++++----------- Gemfile.lock | 77 +++++++- app/assets/stylesheets/application.css | 1 + app/assets/stylesheets/single_signon.scss | 44 +++++ .../identity_providers_controller.rb | 98 ++++++++++ app/controllers/single_signon_controller.rb | 9 + .../users/omniauth_callbacks_controller.rb | 40 ++++ app/models/concerns/account_settings.rb | 1 + app/models/identity_provider.rb | 19 ++ app/models/user.rb | 15 +- app/uploaders/logo_uploader.rb | 18 ++ app/views/_user_util_links.html.erb | 2 +- .../omniauth_callbacks/complete.html.erb | 9 + app/views/devise/shared/_links.html.erb | 8 +- .../dashboard/sidebar/_configuration.html.erb | 4 + app/views/identity_providers/_form.html.erb | 59 ++++++ app/views/identity_providers/edit.html.erb | 5 + app/views/identity_providers/index.html.erb | 37 ++++ app/views/identity_providers/new.html.erb | 5 + app/views/shared/_footer.html.erb | 2 +- app/views/single_signon/index.html.erb | 22 +++ config/application.rb | 9 +- config/initializers/apartment.rb | 3 +- config/initializers/devise.rb | 18 +- config/initializers/is_it_working.rb | 4 - config/locales/en.yml | 11 ++ config/locales/hyrax.en.yml | 1 + config/routes.rb | 55 ++++-- ..._print_pending_relationships.iiif_print.rb | 14 +- .../20230727180717_add_omniauth_to_users.rb | 6 + ...0230804073106_create_identity_providers.rb | 12 ++ db/schema.rb | 14 +- docker-compose.yml | 2 + lib/omni_auth/strategies/saml_decorator.rb | 54 ++++++ spec/factories/identity_providers.rb | 9 + spec/models/identity_provider_spec.rb | 28 +++ spec/requests/single_signon_request_spec.rb | 25 +++ 38 files changed, 770 insertions(+), 156 deletions(-) create mode 100644 .gitguardian.yaml create mode 100644 app/assets/stylesheets/single_signon.scss create mode 100644 app/controllers/identity_providers_controller.rb create mode 100644 app/controllers/single_signon_controller.rb create mode 100644 app/controllers/users/omniauth_callbacks_controller.rb create mode 100644 app/models/identity_provider.rb create mode 100644 app/uploaders/logo_uploader.rb create mode 100644 app/views/devise/omniauth_callbacks/complete.html.erb create mode 100644 app/views/identity_providers/_form.html.erb create mode 100644 app/views/identity_providers/edit.html.erb create mode 100644 app/views/identity_providers/index.html.erb create mode 100644 app/views/identity_providers/new.html.erb create mode 100644 app/views/single_signon/index.html.erb delete mode 100644 config/initializers/is_it_working.rb create mode 100644 db/migrate/20230727180717_add_omniauth_to_users.rb create mode 100644 db/migrate/20230804073106_create_identity_providers.rb create mode 100644 lib/omni_auth/strategies/saml_decorator.rb create mode 100644 spec/factories/identity_providers.rb create mode 100644 spec/models/identity_provider_spec.rb create mode 100644 spec/requests/single_signon_request_spec.rb diff --git a/.gitguardian.yaml b/.gitguardian.yaml new file mode 100644 index 000000000..6169cdf7d --- /dev/null +++ b/.gitguardian.yaml @@ -0,0 +1,5 @@ +secret: + ignored-matches: + - match: 2ace7433e96955aeed1a310d7dcc61f8761d05fbff91b92d79d860e307d6ea6a + name: Generic High Entropy Secret - .env +version: 2 diff --git a/Gemfile b/Gemfile index 3b697e5ef..3f8a743f1 100644 --- a/Gemfile +++ b/Gemfile @@ -1,144 +1,93 @@ # frozen_string_literal: true +# rubocop:disable Metrics/LineLength source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.2.5' +gem 'active-fedora', '>= 11.1.4' +gem 'active_elastic_job', github: 'active-elastic-job/active-elastic-job', ref: 'ec51c5d9dedc4a1b47f2db41f26d5fceb251e979', group: %i[aws] gem 'activerecord-nulldb-adapter' gem 'addressable', '2.8.1' # remove once https://github.com/postrank-labs/postrank-uri/issues/49 is fixed -# Use sqlite3 as the database for Active Record -gem 'pg' -# Use Puma as the app server -gem 'puma', '~> 4.3' -# Use SCSS for stylesheets -gem 'sass-rails', '~> 5.0' -# Use CoffeeScript for .coffee assets and views -gem 'coffee-rails', '~> 4.2' -# See https://github.com/rails/execjs#readme for more supported runtimes -# gem 'therubyracer', platforms: :ruby - -# Use jquery as the JavaScript library -gem 'jquery-rails' -# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks -gem 'turbolinks', '~> 5' -# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem 'jbuilder', '~> 2.5' -# bundle exec rake doc:rails generates the API under doc/api. -# gem 'sdoc', '~> 0.4.0', group: :doc - -# Use ActiveModel has_secure_password -# gem 'bcrypt', '~> 3.1.7' - -gem 'active-fedora', '>= 11.1.4' -gem 'flutie' -# Use Capistrano for deployment -# gem 'capistrano-rails', group: :development - -group :development, :test do - # Call 'byebug' anywhere in the code to stop execution and get a debugger console - gem 'byebug' - gem 'pry-byebug' - - gem 'i18n-debug', require: false - gem 'i18n-tasks' - gem 'rspec' - gem 'rspec-rails', '>= 3.6.0' - - gem 'simplecov', require: false - - gem 'fcrepo_wrapper', '~> 0.4' - gem 'solr_wrapper', '~> 2.0' - - gem 'rubocop', '~> 0.50', '<= 0.52.1' - gem 'rubocop-rspec', '~> 1.22', '<= 1.22.2' -end - -group :test do - gem 'capybara' - gem 'capybara-screenshot', '~> 1.0' - gem 'database_cleaner' - gem 'factory_bot_rails' - gem 'launchy' - # rack-test >= 0.71 does not work with older Capybara versions (< 2.17). See #214 for more details - gem 'rack-test', '0.7.0' - gem 'rails-controller-testing' - gem 'rspec-activemodel-mocks' - gem 'rspec-its' - gem 'rspec-retry' - gem 'rspec_junit_formatter' - gem 'selenium-webdriver', '4.8.1' - gem 'shoulda-matchers', '~> 4.0' - gem 'webdrivers', '~> 4.7.0' - gem 'webmock' -end - -group :development do - # Access an IRB console on exception pages or by using <%= console %> in views - gem 'web-console', '>= 3.3.0' - - gem 'listen', '>= 3.0.5', '< 3.2' - # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring - gem 'easy_translate' - gem 'scss_lint', require: false - gem 'spring', '~> 1.7' - gem 'spring-watcher-listen', '~> 2.0.0' -end - -gem 'bulkrax', '~> 5.0' - +gem 'apartment' +gem 'aws-sdk-sqs', group: %i[aws] gem 'blacklight', '~> 6.7' gem 'blacklight_oai_provider', '~> 6.1', '>= 6.1.1' - -gem 'hyrax', '~> 3.5.0' - gem 'bolognese', '>= 1.9.10' -gem 'hyrax-doi', git: 'https://github.com/samvera-labs/hyrax-doi.git', branch: 'main' -gem 'hyrax-iiif_av', git: 'https://github.com/samvera-labs/hyrax-iiif_av.git', branch: 'main' -gem 'iiif_print', git: 'https://github.com/scientist-softserv/iiif_print.git', branch: 'main' -gem 'postrank-uri', '>= 1.0.24' -gem 'redlock', '>= 0.1.2', '< 2.0' # lock redlock per https://github.com/samvera/hyrax/pull/5961 -gem 'rsolr', '~> 2.0' - +gem 'bootstrap-datepicker-rails' +gem 'bulkrax', '~> 5.0' +gem 'byebug', group: %i[development test] +gem 'capybara', group: %i[test] +gem 'capybara-screenshot', '~> 1.0', group: %i[test] +gem 'carrierwave-aws', '~> 1.3', group: %i[aws test] +gem 'cocoon' +gem 'codemirror-rails' +gem 'coffee-rails', '~> 4.2' # Use CoffeeScript for .coffee assets and views +gem 'database_cleaner', group: %i[test] gem 'devise' gem 'devise-guests', '~> 0.3' gem 'devise-i18n' gem 'devise_invitable', '~> 1.6' - -gem 'apartment' -gem 'is_it_working' -gem 'rolify' - +gem 'dry-monads', '~> 1.4.0' # Locked because 1.5.0 was not compatible with Hyrax v.3.4.2 +gem 'easy_translate', group: %i[development] +gem 'factory_bot_rails', group: %i[test] +gem 'fcrepo_wrapper', '~> 0.4', group: %i[development test] gem 'flipflop', '~> 2.6.0' # waiting for hyrax 4 upgrade +gem 'flutie' +gem 'hyrax', '~> 3.5.0' +gem 'hyrax-doi', github: 'samvera-labs/hyrax-doi', branch: 'main' +gem 'hyrax-iiif_av', github: 'samvera-labs/hyrax-iiif_av', branch: 'main' +gem 'i18n-debug', require: false, group: %i[development test] +gem 'i18n-tasks', group: %i[development test] +gem 'iiif_print', github: 'scientist-softserv/iiif_print', branch: 'main' +gem 'jbuilder', '~> 2.5' +gem 'jquery-rails' # Use jquery as the JavaScript library +gem 'launchy', group: %i[test] +gem 'listen', '>= 3.0.5', '< 3.2', group: %i[development] gem 'lograge' - gem 'mods', '~> 2.4' - -group :aws, :test do - gem 'carrierwave-aws', '~> 1.3' -end - -group :aws do - gem 'active_elastic_job', git: 'https://github.com/active-elastic-job/active-elastic-job.git', - ref: 'ec51c5d9dedc4a1b47f2db41f26d5fceb251e979' - gem 'aws-sdk-sqs' -end - -gem 'bootstrap-datepicker-rails' -gem "cocoon" -gem 'codemirror-rails' gem 'negative_captcha' gem 'okcomputer' +gem 'omniauth-cas', github: 'stanhu/omniauth-cas', ref: '4211e6d05941b4a981f9a36b49ec166cecd0e271' +gem 'omniauth-multi-provider' +gem 'omniauth-rails_csrf_protection', '~> 1.0' +gem 'omniauth-saml', '~> 2.1' +gem 'omniauth_openid_connect' gem 'parser', '~> 2.5.3' +gem 'pg' +gem 'postrank-uri', '>= 1.0.24' +gem 'pry-byebug', group: %i[development test] +gem 'puma', '~> 4.3' # Use Puma as the app server +gem 'rack-test', '0.7.0', group: %i[test] # rack-test >= 0.71 does not work with older Capybara versions (< 2.17). See #214 for more details +gem 'rails-controller-testing', group: %i[test] gem 'rdf', '~> 3.1.15' # rdf 3.2.0 removed SerializedTransaction which ldp requires +gem 'redlock', '>= 0.1.2', '< 2.0' # lock redlock per https://github.com/samvera/hyrax/pull/5961 gem 'riiif', '~> 1.1' +gem 'rolify' +gem 'rsolr', '~> 2.0' +gem 'rspec', group: %i[development test] +gem 'rspec-activemodel-mocks', group: %i[test] +gem 'rspec-its', group: %i[test] +gem 'rspec-rails', '>= 3.6.0', group: %i[development test] +gem 'rspec-retry', group: %i[test] +gem 'rspec_junit_formatter', group: %i[test] +gem 'rubocop', '~> 0.50', '<= 0.52.1', group: %i[development test] +gem 'rubocop-rspec', '~> 1.22', '<= 1.22.2', group: %i[development test] +gem 'sass-rails', '~> 5.0' # Use SCSS for stylesheets +gem 'scss_lint', require: false, group: %i[development] gem 'secure_headers' +gem 'selenium-webdriver', '4.8.1', group: %i[test] +gem 'shoulda-matchers', '~> 4.0', group: %i[test] gem 'sidekiq', "< 7.0" # sidekiq 7 requires upgrade to redis 6 +gem 'simplecov', require: false, group: %i[development test] +gem 'solr_wrapper', '~> 2.0', group: %i[development test] +gem 'spring', '~> 1.7', group: %i[development] +gem 'spring-watcher-listen', '~> 2.0.0', group: %i[development] gem 'terser' # to support the Safe Navigation / Optional Chaining operator (?.) and avoid uglifier precompile issue gem 'tether-rails' - -# When first attempting to upgrade to Hyrax v3.4.2, this dry-monads gem was upgraded to v1.5.0. -# This version threw the following error: -# NameError: uninitialized constant Dry::Monads::Result::Transformer -# Locking it to v1.4.x does not throw an error. -gem 'dry-monads', '~> 1.4.0' +gem 'turbolinks', '~> 5' +gem 'web-console', '>= 3.3.0', group: %i[development] # <%= console %> in views +gem 'webdrivers', '~> 4.7.0', group: %i[test] +gem 'webmock', group: %i[test] +# rubocop:enable Metrics/LineLength diff --git a/Gemfile.lock b/Gemfile.lock index f8e6888a6..20fec42e2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -43,6 +43,16 @@ GIT rails (~> 5.0) rdf-vocab (~> 3.0) +GIT + remote: https://github.com/stanhu/omniauth-cas.git + revision: 4211e6d05941b4a981f9a36b49ec166cecd0e271 + ref: 4211e6d05941b4a981f9a36b49ec166cecd0e271 + specs: + omniauth-cas (2.0.0) + addressable (~> 2.3) + nokogiri (~> 1.5) + omniauth (>= 1.2, < 3) + GEM remote: https://rubygems.org/ specs: @@ -115,6 +125,7 @@ GEM tzinfo (~> 1.1) addressable (2.8.1) public_suffix (>= 2.0.2, < 6.0) + aes_key_wrap (1.1.0) almond-rails (0.3.0) rails (>= 4.2) amazing_print (1.4.0) @@ -125,6 +136,7 @@ GEM rack (>= 1.3.6) arel (9.0.0) ast (2.4.2) + attr_required (1.0.1) autoprefixer-rails (10.4.13.0) execjs (~> 2) awesome_nested_set (3.5.0) @@ -169,6 +181,7 @@ GEM smart_properties bibtex-ruby (6.0.0) latex-decode (~> 0.0) + bindata (2.4.15) bindex (0.8.1) blacklight (6.25.0) bootstrap-sass (~> 3.2) @@ -612,7 +625,6 @@ GEM json iiif_manifest (1.3.1) activesupport (>= 4) - is_it_working (1.1.0) iso-639 (0.3.6) iso8601 (0.9.1) jbuilder (2.11.5) @@ -632,6 +644,11 @@ GEM railties (>= 3.2.16) json (2.6.3) json-canonicalization (0.3.2) + json-jwt (1.15.3) + activesupport (>= 4.2) + aes_key_wrap + bindata + httpclient json-ld (3.1.10) htmlentities (~> 4.3) json-canonicalization (~> 0.2) @@ -816,6 +833,32 @@ GEM oj (3.14.3) oj_mimic_json (1.0.1) okcomputer (1.18.4) + omniauth (2.1.1) + hashie (>= 3.4.6) + rack (>= 2.2.3) + rack-protection + omniauth-multi-provider (0.4.0) + omniauth + omniauth-rails_csrf_protection (1.0.1) + actionpack (>= 4.2) + omniauth (~> 2.0) + omniauth-saml (2.1.0) + omniauth (~> 2.0) + ruby-saml (~> 1.12) + omniauth_openid_connect (0.6.1) + omniauth (>= 1.9, < 3) + openid_connect (~> 1.1) + openid_connect (1.4.2) + activemodel + attr_required (>= 1.0.0) + json-jwt (>= 1.15.0) + net-smtp + rack-oauth2 (~> 1.21) + swd (~> 1.3) + tzinfo + validate_email + validate_url + webfinger (~> 1.2) openseadragon (0.6.0) rails (> 3.2.0) optimist (3.0.1) @@ -853,7 +896,15 @@ GEM rails (>= 5.0, < 7.1) rdf racc (1.7.1) - rack (2.2.7) + rack (2.2.8) + rack-oauth2 (1.21.3) + activesupport + attr_required + httpclient + json-jwt (>= 1.11.0) + rack (>= 2.1.0) + rack-protection (3.0.6) + rack rack-test (0.7.0) rack (>= 1.0, < 3) rails (5.2.8.1) @@ -1038,6 +1089,9 @@ GEM multipart-post oauth2 ruby-progressbar (1.13.0) + ruby-saml (1.15.0) + nokogiri (>= 1.13.10) + rexml ruby2_keywords (0.0.5) ruby_dep (1.5.0) rubyzip (2.3.2) @@ -1137,6 +1191,10 @@ GEM activesupport (>= 5.2) sprockets (>= 3.0.0) ssrf_filter (1.0.8) + swd (1.3.0) + activesupport (>= 3) + attr_required (>= 0.0.5) + httpclient (>= 2.4) sxp (1.1.0) rdf (~> 3.1) temple (0.10.0) @@ -1169,6 +1227,12 @@ GEM unicode-types (1.8.0) unicode_utils (1.4.0) validatable (1.6.7) + validate_email (0.1.6) + activemodel (>= 3.0) + mail (>= 2.2.5) + validate_url (1.0.15) + activemodel (>= 3.0.0) + public_suffix valkyrie (2.2.0) activemodel activesupport @@ -1196,6 +1260,9 @@ GEM nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (> 3.141, < 5.0) + webfinger (1.2.0) + activesupport + httpclient (>= 2.4) webmock (3.18.1) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -1248,7 +1315,6 @@ DEPENDENCIES i18n-debug i18n-tasks iiif_print! - is_it_working jbuilder (~> 2.5) jquery-rails launchy @@ -1257,6 +1323,11 @@ DEPENDENCIES mods (~> 2.4) negative_captcha okcomputer + omniauth-cas! + omniauth-multi-provider + omniauth-rails_csrf_protection (~> 1.0) + omniauth-saml (~> 2.1) + omniauth_openid_connect parser (~> 2.5.3) pg postrank-uri (>= 1.0.24) diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index c90e7eba7..8b66c7d5d 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -17,5 +17,6 @@ *= require hyrax *= require dataTables/bootstrap/3/jquery.dataTables.bootstrap *= require bootstrap-datepicker + *= require single_signon *= require_self */ diff --git a/app/assets/stylesheets/single_signon.scss b/app/assets/stylesheets/single_signon.scss new file mode 100644 index 000000000..b5cd1fd62 --- /dev/null +++ b/app/assets/stylesheets/single_signon.scss @@ -0,0 +1,44 @@ +// Place all the styles related to the SingleSignon controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + +.col-centered { + background-color: #ff0000; + margin: 80px auto; + text-align: center; +} + +.sso-button { + background-color: #ffffff; + border: 1px solid; + margin: 100px 0 200px; + min-height: 300px; + padding: 20px 50px; +} + +.sso-button-fake { + margin-top: 100px; +} + +.loader { + -webkit-animation: spin 2s linear infinite; // Safari + animation: spin 2s linear infinite; + border-radius: 50%; + border-top: 16px solid #0a1f61; + border: 16px solid #f3f3f3; + color: #f3f3f3; + font-size: 11px; + height: 120px; + margin: 55px auto 150px; + text-indent: -99999em; + width: 120px; +} +// Safari +@-webkit-keyframes spin { + 0% { -webkit-transform: rotate(0deg); } + 100% { -webkit-transform: rotate(360deg); } +} +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/app/controllers/identity_providers_controller.rb b/app/controllers/identity_providers_controller.rb new file mode 100644 index 000000000..28774a0bc --- /dev/null +++ b/app/controllers/identity_providers_controller.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +class IdentityProvidersController < ApplicationController + layout 'hyrax/dashboard' + + before_action :ensure_admin! + before_action :set_identity_provider, only: %i[edit update destroy] + + def index + @identity_providers = IdentityProvider.all + end + + # GET /identity_providers/new + def new + add_breadcrumbs + @identity_provider = IdentityProvider.new + end + + # GET /identity_providers/1/edit + def edit + add_breadcrumbs + end + + # POST /identity_providers or /identity_providers.json + def create + @identity_provider = IdentityProvider.new(identity_provider_params) + respond_to do |format| + if @identity_provider.save + format.html do + redirect_to edit_identity_provider_url(@identity_provider), + notice: "Auth provider was successfully created." + end + format.json { render :show, status: :created, location: @identity_provider } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @identity_provider.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /identity_providers/1 or /identity_providers/1.json + def update + respond_to do |format| + if @identity_provider.update(identity_provider_params) + format.html do + redirect_to edit_identity_provider_url(@identity_provider), + notice: "Auth provider was successfully updated." + end + format.json { render :show, status: :ok, location: @identity_provider } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @identity_provider.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /identity_providers/1 or /identity_providers/1.json + def destroy + @identity_provider.destroy + respond_to do |format| + format.html { redirect_to new_identity_provider_url, notice: "Auth provider was successfully destroyed." } + format.json { head :no_content } + end + end + + def add_breadcrumbs + add_breadcrumb t(:'hyrax.controls.home'), root_path + add_breadcrumb t(:'hyrax.dashboard.breadcrumbs.admin'), hyrax.dashboard_path + add_breadcrumb t(:'hyrax.admin.sidebar.configuration'), '#' + add_breadcrumb t(:'hyrax.admin.sidebar.identity_provider'), request.path + end + + private + + # Use callbacks to share common setup or constraints between actions. + def set_identity_provider + @identity_provider = IdentityProvider.find(params[:id]) + end + + def ensure_admin! + authorize! :read, :admin_dashboard + end + + # Only allow a list of trusted parameters through. + def identity_provider_params + return @identity_provider_params if @identity_provider_params + @identity_provider_params = params.require(:identity_provider).permit( + :name, + :provider, + :options, + :logo_image, + :logo_image_text + ) + @identity_provider_params['options'].presence && + @identity_provider_params['options'] = JSON.parse(@identity_provider_params['options']) + @identity_provider_params + end +end diff --git a/app/controllers/single_signon_controller.rb b/app/controllers/single_signon_controller.rb new file mode 100644 index 000000000..a17818a56 --- /dev/null +++ b/app/controllers/single_signon_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class SingleSignonController < DeviseController + def index + @identity_providers = IdentityProvider.all + render && return unless @identity_providers.count.zero? + redirect_to main_app.new_user_session_path + end +end diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb new file mode 100644 index 000000000..f5cff3c0f --- /dev/null +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Users + class OmniauthCallbacksController < Devise::OmniauthCallbacksController + skip_before_action :verify_authenticity_token + + def callback + # Here you will need to implement your logic for processing the callback + # for example, finding or creating a user + @user = User.from_omniauth(request.env['omniauth.auth']) + + if @user.persisted? + # We need to render a loading page here just to set the sesion properly + # otherwise the logged in session gets lost during the redirect + if params[:action] == 'saml' + set_flash_message(:notice, :success, kind: params[:action]) if is_navigational_format? + sign_in @user, event: :authentication # this will throw if @user is not activated + render 'complete' + else + sign_in_and_redirect @user, event: :authentication # this will throw if @user is not activated + set_flash_message(:notice, :success, kind: params[:action]) if is_navigational_format? + end + else + session['devise.user_attributes'] = @user.attributes + redirect_to new_user_registration_url + end + end + alias cas callback + alias openid_connect callback + alias saml callback + + def passthru + render status: 404, plain: 'Not found. Authentication passthru.' + end + + # def failure + # #redirect_to root_path + # end + end +end diff --git a/app/models/concerns/account_settings.rb b/app/models/concerns/account_settings.rb index 5eae7cd94..5cd2458c0 100644 --- a/app/models/concerns/account_settings.rb +++ b/app/models/concerns/account_settings.rb @@ -22,6 +22,7 @@ module AccountSettings setting :doi_reader, type: 'boolean', default: false setting :doi_writer, type: 'boolean', default: false setting :file_acl, type: 'boolean', default: true, private: true + setting :email_domain, type: 'string', default: 'example.com' setting :email_format, type: 'array' setting :email_subject_prefix, type: 'string' setting :enable_oai_metadata, type: 'string', disabled: true diff --git a/app/models/identity_provider.rb b/app/models/identity_provider.rb new file mode 100644 index 000000000..c9ba7148c --- /dev/null +++ b/app/models/identity_provider.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class IdentityProvider < ApplicationRecord + validates :name, presence: true + validates :provider, presence: true + + mount_uploader :logo_image, LogoUploader + + def parsed_options(rack_env = nil) + @parsed_options = options.with_indifferent_access + return @parsed_options unless provider == 'saml' + url = "#{rack_env['HTTP_X_FORWARDED_PROTO']}://#{rack_env['HTTP_HOST']}/users/auth/saml/#{id}/callback" + @parsed_options['assertion_consumer_service_url'] = url + return @parsed_options unless @parsed_options['idp_metadata_url'] + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_parser.parse_remote_to_hash(@parsed_options['idp_metadata_url']) + @parsed_options = idp_metadata.merge(@parsed_options) + end +end diff --git a/app/models/user.rb b/app/models/user.rb index ed7165aaf..3889ee494 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -15,7 +15,8 @@ class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :invitable, :registerable, - :recoverable, :rememberable, :trackable, :validatable + :recoverable, :rememberable, :trackable, :validatable, + :omniauthable, omniauth_providers: %i[saml openid_connect cas] after_create :add_default_group_membership! @@ -30,6 +31,18 @@ def self.default_scope scope :registered, -> { for_repository.group(:id).where(guest: false) } + def self.from_omniauth(auth) + find_or_create_by(provider: auth.provider, uid: auth.uid) do |user| + user.email = auth&.info&.email || [auth.uid, '@', Site.instance.account.email_domain].join if user.email.blank? + user.password = Devise.friendly_token[0, 20] + user.display_name = auth&.info&.name # assuming the user model has a name + # user.image = auth.info.image # assuming the user model has an image + # If you are using confirmable and the provider(s) you use validate emails, + # uncomment the line below to skip the confirmation emails. + # user.skip_confirmation! + end + end + # Method added by Blacklight; Blacklight uses #to_s on your # user class to get a user-displayable login/identifier. def to_s diff --git a/app/uploaders/logo_uploader.rb b/app/uploaders/logo_uploader.rb new file mode 100644 index 000000000..25a56c3e0 --- /dev/null +++ b/app/uploaders/logo_uploader.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class LogoUploader < CarrierWave::Uploader::Base + include CarrierWave::MiniMagick + include CarrierWave::Compatibility::Paperclip + + version :medium do + process resize_to_fill: [300, 300] + end + + version :thumb do + process resize_to_fill: [100, 100] + end + + def extension_whitelist + %w[jpg jpeg png gif bmp tif tiff] + end +end diff --git a/app/views/_user_util_links.html.erb b/app/views/_user_util_links.html.erb index 5ba8fbbbe..782fcb5f1 100644 --- a/app/views/_user_util_links.html.erb +++ b/app/views/_user_util_links.html.erb @@ -24,7 +24,7 @@ <% else %>
  • - <%= link_to main_app.new_user_session_path do %> + <%= link_to main_app.single_signon_index_path do %> <%= t("hyrax.toolbar.profile.login") %> <% end %>
  • diff --git a/app/views/devise/omniauth_callbacks/complete.html.erb b/app/views/devise/omniauth_callbacks/complete.html.erb new file mode 100644 index 000000000..87a3625dc --- /dev/null +++ b/app/views/devise/omniauth_callbacks/complete.html.erb @@ -0,0 +1,9 @@ +

    Thank you for signing in. Your dashboard is loading

    +
    Loading...
    + diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index 86c366f64..caeb8b523 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -19,7 +19,7 @@ <% end -%> <%- if devise_mapping.omniauthable? %> - <%- resource_class.omniauth_providers.each do |provider| %> - <%= link_to t('.sign_in_with_provider', provider: provider.to_s.titleize), omniauth_authorize_path(resource_name, provider) %>
    - <% end -%> -<% end -%> + <%- IdentityProvider.all.each do |ip| %> +

    <%= button_to t('.sign_in_with_provider', provider: ip.provider.to_s.titleize), omniauth_authorize_path(resource_name, ip.provider, ip.id), data: { turbo: false } %>

    + <% end %> +<% end %> diff --git a/app/views/hyrax/dashboard/sidebar/_configuration.html.erb b/app/views/hyrax/dashboard/sidebar/_configuration.html.erb index 19423d996..ec2bce02c 100644 --- a/app/views/hyrax/dashboard/sidebar/_configuration.html.erb +++ b/app/views/hyrax/dashboard/sidebar/_configuration.html.erb @@ -12,6 +12,10 @@ <%= t('hyrax.admin.sidebar.account') %> <% end %> + <%= menu.nav_link(main_app.identity_providers_path) do %> + <%= t('hyrax.admin.sidebar.identity_providers') %> + <% end %> + <%= menu.nav_link(main_app.edit_site_labels_path, title: t('hyrax.admin.sidebar.labels')) do %> <%= t('hyrax.admin.sidebar.labels') %> diff --git a/app/views/identity_providers/_form.html.erb b/app/views/identity_providers/_form.html.erb new file mode 100644 index 000000000..fd2d41430 --- /dev/null +++ b/app/views/identity_providers/_form.html.erb @@ -0,0 +1,59 @@ +
    + <%= simple_form_for(@identity_provider) do |f| %> +
    + <% if @identity_provider.errors.any? %> +
    +

    <%= pluralize(@identity_provider.errors.count, "error") %> prohibited this authentication provider from being saved:

    +
      + <% @identity_provider.errors.messages.each do |key, messages| %> +
    • <%= key %> "<%= @identity_provider.errors.details[key].first[:value] %>" <%= messages.join(' and ') %>
    • + <% end %> +
    +
    + <% end %> + <%= f.input :name %> + <%= f.input :provider, + collection: Devise.omniauth_providers.map {|o| [o, o.upcase]}, + label_method: :second, + value_method: :first, + label: t('hyku.identity_provider.label.provider'), + required: true %> +

    Documentation for each identity provider type can be found in its associated adapter documentation.

    +
      +
    • <%= link_to 'SAML', 'https://github.com/omniauth/omniauth-saml' %>
    • +
    • <%= link_to 'CAS', 'https://github.com/dlindahl/omniauth-cas' %>
    • +
    • <%= link_to 'Openid Connect', 'https://github.com/omniauth/omniauth_openid_connect' %>
    • +
    + +

    We use an additional paramater for SAML - `idp_metadata_url`. If you provide that URL, it will be parsed as shown in <%= link_to 'the SAML docs', 'https://github.com/omniauth/omniauth-saml#idp-metadata' %>

    + <% if @identity_provider.new_record? %> +

    SAML assertion_consumer_service_url will be displayed after record is saved

    + <% else %> +

    These are the assertion consumer service urls or redirect urls that need to be allowed by your IdP. Do not specify the assertion_consumer_service_url in your options.

    +
      + <% @current_account.domain_names.each do |dn| %> +
    • <%= dn.cname %>/users/auth/saml/<%= @identity_provider.id %>/callback
    • + <% end %> +
    +

    Metadata is available <%= link_to 'here', "/users/auth/saml/#{@identity_provider.id}/metadata", data: { turbolinks: false } %>

    + <% end %> + + <%= f.input :options, input_html: {value: @identity_provider.options&.to_json } %> + + + <%# Upload Logo Image %> + <%= f.input :logo_image, as: :file, wrapper: :vertical_file_input, hint: t('hyrax.admin.appearances.show.forms.logo_image.hint') %> + <%= f.input :logo_image_text, as: :text %> + <%= image_tag f.object.logo_image.url(:medium), class: "img-responsive", alt: f.object.logo_image_text if f.object.logo_image? %> + +
    + + + <% end %> +
    diff --git a/app/views/identity_providers/edit.html.erb b/app/views/identity_providers/edit.html.erb new file mode 100644 index 000000000..3e463430c --- /dev/null +++ b/app/views/identity_providers/edit.html.erb @@ -0,0 +1,5 @@ +<% provide :page_header do %> +

    <%= t('hyku.identity_provider.header') %>

    +<% end %> + +<%= render 'form', identity_provider: @identity_provider %> diff --git a/app/views/identity_providers/index.html.erb b/app/views/identity_providers/index.html.erb new file mode 100644 index 000000000..594fe23d5 --- /dev/null +++ b/app/views/identity_providers/index.html.erb @@ -0,0 +1,37 @@ +<% content_for :page_header do %> +

    <%= t(:'hyrax.admin.sidebar.identity_providers_and_permissions') %>

    +<% end %> + +
    +
    +
    +
    + + + + + + + + + + + + <% @identity_providers.each do |u| %> + + + + + + + + <% end %> + +
    NameProviderUpdated AtLogo
    <%= u.name %><%= u.provider %><%= u.updated_at %><%= image_tag u.logo_image.url(:thumb), class: "img-responsive", alt: u.logo_image_text if u.logo_image? %><%= link_to t('.edit'), edit_identity_provider_path(u) %> | <%= link_to t('helpers.action.delete'), identity_provider_path(u), method: :delete, data: { confirm: t('.confirm_delete') } %>
    + <%= link_to new_identity_provider_path, class: 'btn btn-primary' do %> + <%= t('.create_new') %> + <% end %> +
    +
    +
    +
    diff --git a/app/views/identity_providers/new.html.erb b/app/views/identity_providers/new.html.erb new file mode 100644 index 000000000..3e463430c --- /dev/null +++ b/app/views/identity_providers/new.html.erb @@ -0,0 +1,5 @@ +<% provide :page_header do %> +

    <%= t('hyku.identity_provider.header') %>

    +<% end %> + +<%= render 'form', identity_provider: @identity_provider %> diff --git a/app/views/shared/_footer.html.erb b/app/views/shared/_footer.html.erb index 2c4cdf422..b05feab29 100644 --- a/app/views/shared/_footer.html.erb +++ b/app/views/shared/_footer.html.erb @@ -9,7 +9,7 @@ diff --git a/app/views/single_signon/index.html.erb b/app/views/single_signon/index.html.erb new file mode 100644 index 000000000..32d52b7b1 --- /dev/null +++ b/app/views/single_signon/index.html.erb @@ -0,0 +1,22 @@ +

    Select a Single Sign On Provider

    + +<%- if devise_mapping.omniauthable? %> + <%- @identity_providers.each do |ip| %> +
    + <%= button_to omniauth_authorize_path(resource_name, ip.provider, ip.id), form_class: 'button_to col-centered', class: 'sso-button' do %> +
    + <%= image_tag ip.logo_image.url(:medium), class: "img-responsive", alt: ip.logo_image_text if ip.logo_image? %> +
    + <%= t('.sign_in_with_provider', provider: ip.name) %>
    +
    + Sign In + <% end %> +
    + <% end -%> +<% end -%> + +<% if @identity_providers.count == 1 %> + +<% end %> diff --git a/config/application.rb b/config/application.rb index 736de7721..850a2b515 100644 --- a/config/application.rb +++ b/config/application.rb @@ -31,7 +31,7 @@ class Application < Rails::Application config.active_elastic_job.secret_key_base = Rails.application.secrets[:secret_key_base] end end - + config.to_prepare do # Allows us to use decorator files in the app directory Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator*.rb")).sort.each do |c| @@ -39,6 +39,13 @@ class Application < Rails::Application end end + config.to_prepare do + # Allows us to use decorator files in the app directory + Dir.glob(File.join(File.dirname(__FILE__), "../lib/**/*_decorator*.rb")).sort.each do |c| + Rails.configuration.cache_classes ? require(c) : load(c) + end + end + # OAI additions Dir.glob(File.join(File.dirname(__FILE__), "../lib/oai/**/*.rb")).sort.each do |c| Rails.configuration.cache_classes ? require(c) : load(c) diff --git a/config/initializers/apartment.rb b/config/initializers/apartment.rb index e937e69d9..e523b4cdf 100644 --- a/config/initializers/apartment.rb +++ b/config/initializers/apartment.rb @@ -55,6 +55,5 @@ end end - Rails.application.config.middleware.use AccountElevator - + Rails.application.config.middleware.insert_before Warden::Manager, AccountElevator end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 296e7d928..6658b94b0 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -289,7 +289,23 @@ # ==> OmniAuth # Add a new OmniAuth provider. Check the wiki for more information on setting # up on your models and hooks. - # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + # if statement allows loading the app to call the migration that creates the provider + # setup for multiprovider SAML options + dynamic_options_generator = lambda { |identity_provider_id, rack_env| + identity_provider = IdentityProvider.find(identity_provider_id) + identity_provider.parsed_options(rack_env) + } + identity_provider_id_regex = /\d+/ + + [:cas, :openid_connect, :saml].each do |provider| + path_prefix = "/users/auth/#{provider}" + handler = OmniAuth::MultiProvider::Handler.new(path_prefix: path_prefix, + identity_provider_id_regex: identity_provider_id_regex, + &dynamic_options_generator) + static_options = { path_prefix: path_prefix } + + config.omniauth provider, static_options.merge(handler.provider_options) + end # ==> Warden configuration # If you want to use other strategies, that are not supported by Devise, or diff --git a/config/initializers/is_it_working.rb b/config/initializers/is_it_working.rb deleted file mode 100644 index 4f17bcefc..000000000 --- a/config/initializers/is_it_working.rb +++ /dev/null @@ -1,4 +0,0 @@ -Rails.configuration.middleware.use(IsItWorking::Handler) do |h| - # Check the ActiveRecord database connection without spawning a new thread - h.check :active_record, async: false -end diff --git a/config/locales/en.yml b/config/locales/en.yml index cf6606563..aab903113 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,5 +1,8 @@ --- en: + single_signon: + index: + sign_in_with_provider: Sign in with %{provider} activerecord: attributes: site: @@ -114,6 +117,14 @@ en: user_reader: Can read any User in this tenant title: Administration work_types: Available Work Types + identity_provider: + header: Authentication Identity Provider + label: + name: Name or Description + provider: Provider + optoins: Options + logo_image: Image for SSO Page + logo_image_alt_text: Alt Text for Image footer: admin_login: Administrator login proprietor: diff --git a/config/locales/hyrax.en.yml b/config/locales/hyrax.en.yml index 851d80e94..137ddfef0 100644 --- a/config/locales/hyrax.en.yml +++ b/config/locales/hyrax.en.yml @@ -207,6 +207,7 @@ en: sidebar: activity: Activity appearance: Appearance + identity_provider: Identity Provider collection_types: Collection Types collections: Collections configuration: Configuration diff --git a/config/routes.rb b/config/routes.rb index f08a9431b..2690d3ee5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,16 +1,18 @@ +# frozen_string_literal: true + # OVERRIDE Hyrax 2.9.0 to add featured collection routes require 'sidekiq/web' -Rails.application.routes.draw do - +Rails.application.routes.draw do # rubocop:disable Metrics/BlockLength + resources :identity_providers concern :iiif_search, BlacklightIiifSearch::Routes.new concern :oai_provider, BlacklightOaiProvider::Routes.new - + mount Hyrax::IiifAv::Engine, at: '/' mount Riiif::Engine => 'images', as: :riiif if Hyrax.config.iiif_image_server? - authenticate :user, lambda { |u| u.is_superadmin } do + authenticate :user, ->(u) { u.is_superadmin } do mount Sidekiq::Web => '/sidekiq' end @@ -34,20 +36,42 @@ mount BrowseEverything::Engine => '/browse' resource :site, only: [:update] do - resources :roles, only: [:index, :update] - resource :labels, only: [:edit, :update] + resources :roles, only: %i[index update] + resource :labels, only: %i[edit update] end root 'hyrax/homepage#index' - devise_for :users, controllers: { invitations: 'hyku/invitations', registrations: 'hyku/registrations' } + devise_for :users, skip: [:omniauth_callbacks], controllers: { invitations: 'hyku/invitations', + registrations: 'hyku/registrations', + omniauth_callbacks: 'users/omniauth_callbacks' } + as :user do + resources :single_signon, only: [:index] + + Devise.omniauth_providers.each do |provider| + path_prefix = '/users/auth' + match "#{path_prefix}/#{provider}/:id", + to: "users/omniauth_callbacks#passthru", + as: "user_#{provider}_omniauth_authorize", + via: OmniAuth.config.allowed_request_methods + + match "#{path_prefix}/#{provider}/:id/metadata", + to: "users/omniauth_callbacks#passthru", + as: "user_#{provider}_omniauth_metadata", + via: [:get] + + match "#{path_prefix}/#{provider}/:id/callback", + to: "users/omniauth_callbacks##{provider}", + as: "user_#{provider}_omniauth_callback", + via: [:get, :post] + end + end + mount Qa::Engine => '/authorities' mount Blacklight::Engine => '/' mount Hyrax::Engine, at: '/' - if ENV.fetch('HYKU_BULKRAX_ENABLED', 'true') == 'true' - mount Bulkrax::Engine, at: '/' - end + mount Bulkrax::Engine, at: '/' if ENV.fetch('HYKU_BULKRAX_ENABLED', 'true') == 'true' concern :searchable, Blacklight::Routes::Searchable.new concern :exportable, Blacklight::Routes::Exportable.new @@ -74,8 +98,8 @@ end namespace :admin do - resource :account, only: [:edit, :update] - resource :work_types, only: [:edit, :update] + resource :account, only: %i[edit update] + resource :work_types, only: %i[edit update] resources :users, only: [:destroy] resources :groups do member do @@ -92,7 +116,7 @@ # Generic collection routes resources :collections, only: [] do member do - resource :featured_collection, only: [:create, :destroy] + resource :featured_collection, only: %i[create destroy] end end resources :featured_collection_lists, path: 'featured_collections', only: :create @@ -101,6 +125,7 @@ get 'all_collections' => 'hyrax/homepage#all_collections', as: :all_collections # Upload a collection thumbnail - post "/dashboard/collections/:id/delete_uploaded_thumbnail", to: "hyrax/dashboard/collections#delete_uploaded_thumbnail", as: :delete_uploaded_thumbnail - + post "/dashboard/collections/:id/delete_uploaded_thumbnail", + to: "hyrax/dashboard/collections#delete_uploaded_thumbnail", + as: :delete_uploaded_thumbnail end diff --git a/db/migrate/20230131202855_create_iiif_print_pending_relationships.iiif_print.rb b/db/migrate/20230131202855_create_iiif_print_pending_relationships.iiif_print.rb index 9a8f29cb9..163917cb1 100644 --- a/db/migrate/20230131202855_create_iiif_print_pending_relationships.iiif_print.rb +++ b/db/migrate/20230131202855_create_iiif_print_pending_relationships.iiif_print.rb @@ -1,12 +1,14 @@ # This migration comes from iiif_print (originally 20230109000000) class CreateIiifPrintPendingRelationships < ActiveRecord::Migration[5.1] def change - create_table :iiif_print_pending_relationships do |t| - t.string :child_title, null: false - t.string :parent_id, null: false - t.string :child_order, null: false - t.timestamps + unless table_exists?(:iiif_print_pending_relationships) + create_table :iiif_print_pending_relationships do |t| + t.string :child_title, null: false + t.string :parent_id, null: false + t.string :child_order, null: false + t.timestamps + end + add_index :iiif_print_pending_relationships, :parent_id end - add_index :iiif_print_pending_relationships, :parent_id end end diff --git a/db/migrate/20230727180717_add_omniauth_to_users.rb b/db/migrate/20230727180717_add_omniauth_to_users.rb new file mode 100644 index 000000000..c17782ae4 --- /dev/null +++ b/db/migrate/20230727180717_add_omniauth_to_users.rb @@ -0,0 +1,6 @@ +class AddOmniauthToUsers < ActiveRecord::Migration[5.2] + def change + add_column :users, :provider, :string + add_column :users, :uid, :string + end +end diff --git a/db/migrate/20230804073106_create_identity_providers.rb b/db/migrate/20230804073106_create_identity_providers.rb new file mode 100644 index 000000000..0cc2dd5ba --- /dev/null +++ b/db/migrate/20230804073106_create_identity_providers.rb @@ -0,0 +1,12 @@ +class CreateIdentityProviders < ActiveRecord::Migration[5.2] + def change + create_table :identity_providers do |t| + t.string :name + t.string :provider + t.jsonb :options + t.string :logo_image + t.string :logo_image_text + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 68cb24bcd..ae4a677ae 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.define(version: 2023_01_31_202855) do +ActiveRecord::Schema.define(version: 2023_08_04_073106) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -351,6 +351,16 @@ t.string "humanized_name" end + create_table "identity_providers", force: :cascade do |t| + t.string "name" + t.string "provider" + t.jsonb "options" + t.string "logo_image" + t.string "logo_image_text" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "iiif_print_derivative_attachments", id: :serial, force: :cascade do |t| t.string "fileset_id" t.string "path" @@ -822,6 +832,8 @@ t.integer "invited_by_id" t.string "invited_by_type" t.string "preferred_locale" + t.string "provider" + t.string "uid" t.index ["email"], name: "index_users_on_email", unique: true t.index ["invitation_token"], name: "index_users_on_invitation_token", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true diff --git a/docker-compose.yml b/docker-compose.yml index 482acb98f..1484393c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -153,6 +153,8 @@ services: condition: service_started initialize_app: condition: service_completed_successfully + # ports: + # - 3000:3000 expose: - 3000 diff --git a/lib/omni_auth/strategies/saml_decorator.rb b/lib/omni_auth/strategies/saml_decorator.rb new file mode 100644 index 000000000..f4f07ce93 --- /dev/null +++ b/lib/omni_auth/strategies/saml_decorator.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# monkey patch to support metadata paths - hacked version of: +# https://github.com/salsify/omniauth-multi-provider/issues/4#issuecomment-366452170 +# +# This patches omn-auth-saml to ensure setup_phase is called at the beginning of other_phase +# (which is consistent with how it handles request_phase and callback_phase). +module OmniAuthSamlOtherPhaseSetupPatch + def on_auth_path? + # Override this to ensure initialization happens properly in OmniAuth::Strategies::SAML for "other" + # requests + current_path.start_with?(options.path_prefix) + end + + def on_other_path? + # Override this to ensure initialization happens properly in OmniAuth::Strategies::SAML for "other" + # requests + current_path.match(%r{/(?:metadata|spslo|slo)\z}) + end + + def other_phase + # Override the other_phase method to call setup_phase before checking to see if the request + # is on an "other" request path. This ensures omniauth-multi-provider has setup the path + # prefix properly for the given identity provider. By default omniauth won't call setup_phase until + # after checking the path. + @callback_path = nil + setup_phase if on_auth_path? && on_other_path? + super + end + + def request_path + super + @request_path = @request_path.gsub('saml/saml', 'saml') + end + + def callback_path + super + @callback_path = @callback_path.gsub('saml/saml', 'saml') + end + + def setup_path + super + @setup_path = @setup_path.gsub('saml/saml', 'saml') + end + + def setup_phase + # Make sure we only perform setup once since this method will be called twice during the other phase + return if @setup # TODO: always false due to the calling class being created anew each time? + super + @setup = true + end +end + +OmniAuth::Strategies::SAML.prepend(OmniAuthSamlOtherPhaseSetupPatch) diff --git a/spec/factories/identity_providers.rb b/spec/factories/identity_providers.rb new file mode 100644 index 000000000..ef0d6cb56 --- /dev/null +++ b/spec/factories/identity_providers.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :identity_provider, class: 'IdentityProvider' do + name { 'SAML Test' } + provider { 'saml' } + options { {} } + end +end diff --git a/spec/models/identity_provider_spec.rb b/spec/models/identity_provider_spec.rb new file mode 100644 index 000000000..d2a13fab6 --- /dev/null +++ b/spec/models/identity_provider_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe IdentityProvider, type: :model do + subject do + described_class.new( + name: 'SAML Test', + provider: 'saml' + ) + end + + context 'attributes and validations' do + it 'is valid with valid attributes' do + expect(subject).to be_valid + end + + it 'is not valid without a name' do + subject.name = nil + expect(subject).not_to be_valid + end + + it 'is not valid without a provider' do + subject.provider = nil + expect(subject).not_to be_valid + end + end +end diff --git a/spec/requests/single_signon_request_spec.rb b/spec/requests/single_signon_request_spec.rb new file mode 100644 index 000000000..f03da32a8 --- /dev/null +++ b/spec/requests/single_signon_request_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe "SingleSignons", type: :request do + describe "GET single_signon#index" do + describe "with no IdentityProviders" do + it "redirects to sign in" do + get "/single_signon" + expect(response).to have_http_status(:redirect) + end + end + + describe "with an IdentityProvider" do + before do + IdentityProvider.create(name: 'fake', provider: 'saml') + end + + it "renders succes" do + get "/single_signon" + expect(response).to have_http_status(:success) + end + end + end +end