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/.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/Dockerfile b/Dockerfile index 0ac55e01e..85ac7137d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -82,16 +82,18 @@ 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/ -COPY --chown=1001:101 $APP_PATH/Gemfile* /app/samvera/hyrax-webapp/ -RUN git config --global --add safe.directory /app/samvera && \ +ONBUILD COPY --chown=1001:101 $APP_PATH/Gemfile* /app/samvera/hyrax-webapp/ +ONBUILD RUN git config --global --add safe.directory /app/samvera && \ bundle install --jobs "$(nproc)" -COPY --chown=1001:101 $APP_PATH/bin/db-migrate-seed.sh /app/samvera/ -COPY --chown=1001:101 $APP_PATH /app/samvera/hyrax-webapp +ONBUILD 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 diff --git a/Gemfile b/Gemfile index f59825db5..3979c2254 100644 --- a/Gemfile +++ b/Gemfile @@ -1,148 +1,98 @@ # 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' - gem 'shoulda-matchers', '~> 4.0' - gem 'webdrivers', '~> 4.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 'bulkrax', git: 'https://github.com/samvera-labs/bulkrax.git', branch: 'no_blank_strings_on_split' - -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', git: 'https://github.com/samvera-labs/bulkrax.git', branch: 'no_blank_strings_on_split' +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/tawan/active-elastic-job.git', - branch: '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] # This gem does nothing by default, but is instead a tool to ease developer flow # and place overrides, themes and deployment code. gem 'hyku_knapsack', github: 'samvera-labs/hyku_knapsack', branch: 'upstream_main' + +# rubocop:enable Metrics/LineLength diff --git a/Gemfile.lock b/Gemfile.lock index 342ca84e5..9218dadfd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -50,25 +50,27 @@ 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) GIT - remote: https://github.com/tawan/active-elastic-job.git - revision: ec51c5d9dedc4a1b47f2db41f26d5fceb251e979 - branch: ec51c5d9dedc4a1b47f2db41f26d5fceb251e979 + remote: https://github.com/stanhu/omniauth-cas.git + revision: 4211e6d05941b4a981f9a36b49ec166cecd0e271 + ref: 4211e6d05941b4a981f9a36b49ec166cecd0e271 specs: - active_elastic_job (2.0.1) - aws-sdk-sqs (~> 1) - rails (>= 4.2) + omniauth-cas (2.0.0) + addressable (~> 2.3) + nokogiri (~> 1.5) + omniauth (>= 1.2, < 3) GEM remote: https://rubygems.org/ @@ -142,6 +144,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) @@ -152,6 +155,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) @@ -196,6 +200,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) @@ -327,6 +332,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) @@ -490,6 +504,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) @@ -613,7 +630,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) @@ -633,6 +649,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) @@ -817,6 +838,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) @@ -854,7 +901,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) @@ -1039,6 +1094,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) @@ -1062,7 +1120,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) @@ -1138,6 +1196,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) @@ -1170,6 +1232,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 @@ -1197,6 +1265,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) @@ -1250,7 +1321,6 @@ DEPENDENCIES i18n-debug i18n-tasks iiif_print! - is_it_working jbuilder (~> 2.5) jquery-rails launchy @@ -1259,6 +1329,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) @@ -1283,7 +1358,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 @@ -1294,7 +1369,7 @@ DEPENDENCIES tether-rails turbolinks (~> 5) web-console (>= 3.3.0) - webdrivers (~> 4.0) + webdrivers (~> 4.7.0) webmock BUNDLED WITH diff --git a/README.md b/README.md index aad9a886b..1be23b504 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,18 @@ 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. + +#### 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_) +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: 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(); + } +}; 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/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/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/_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 %>
  • + + <%= "#{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(', ') %> +

    +
    +
    + + <% 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/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..ec2bce02c 100644 --- a/app/views/hyrax/dashboard/sidebar/_configuration.html.erb +++ b/app/views/hyrax/dashboard/sidebar/_configuration.html.erb @@ -3,45 +3,58 @@ <% 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.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') %> <% 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/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/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/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/en.yml b/config/locales/en.yml index ac880a592..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: @@ -194,10 +205,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/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.en.yml b/config/locales/hyrax.en.yml index 48efb8da8..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 @@ -707,6 +708,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 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: 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 e23a773e5..9501c9c8c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -158,6 +158,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/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 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