diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index b8b357820..d1c858952 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -18,7 +18,32 @@ @import "font-awesome-sprockets"; @import "font-awesome"; @import "mixins/mixins"; -@import "*"; + +// Local +@import "about"; +@import "account"; +@import "activities"; +@import "autocomplete"; +@import "bootstrap-tweaks"; +@import "collaborations"; +@import "collection"; +@import "curators"; +@import "events"; +@import "external-resources"; +@import "footer"; +@import "forms"; +@import "headers"; +@import "helpers"; +@import "home"; +@import "icons"; +@import "learning-paths"; +@import "masonry"; +@import "nodes"; +@import "showcase"; +@import "toggle"; +@import "workflows"; + +// Vendor @import "cytoscape-panzoom"; @import "jquery.simplecolorpicker"; @import "eonasdan-bootstrap-datetimepicker"; @@ -1049,6 +1074,24 @@ td.day .calendar-text { } } +.theme-selector { + display: flex; + gap: 10px; +} + +.theme-select-option { + flex-grow: 1; + label { + border: 5px solid; + padding: 10px 20px; + border-radius: 5px; + display: block; + + hr { + border-width: 10px; + } + } +} .btn-oidc-only { font-size: 36px; padding: 4px; diff --git a/app/assets/stylesheets/learning-paths.scss b/app/assets/stylesheets/learning-paths.scss index 692c64557..f90730a63 100644 --- a/app/assets/stylesheets/learning-paths.scss +++ b/app/assets/stylesheets/learning-paths.scss @@ -38,7 +38,7 @@ } } -.learning-path-item { +.block-item { text-align: center; border-image-slice: 80; border-image-width: 80px; diff --git a/app/controllers/collections_controller.rb b/app/controllers/collections_controller.rb index 99a394bfd..7809339e9 100644 --- a/app/controllers/collections_controller.rb +++ b/app/controllers/collections_controller.rb @@ -114,7 +114,7 @@ def destroy @collection.create_activity :destroy, owner: current_user @collection.destroy respond_to do |format| - format.html { redirect_to collections_url, notice: 'Collection was successfully destroyed.' } + format.html { redirect_to collections_path, notice: 'Collection was successfully destroyed.' } format.json { head :no_content } end end diff --git a/app/controllers/content_providers_controller.rb b/app/controllers/content_providers_controller.rb index 854f29053..4897919bc 100644 --- a/app/controllers/content_providers_controller.rb +++ b/app/controllers/content_providers_controller.rb @@ -86,7 +86,7 @@ def destroy @content_provider.create_activity :destroy, owner: current_user @content_provider.destroy respond_to do |format| - format.html { redirect_to content_providers_url, notice: 'Content Provider was successfully destroyed.' } + format.html { redirect_to content_providers_path, notice: 'Content Provider was successfully destroyed.' } format.json { head :no_content } end end diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index cf1a0e811..413e78523 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -187,7 +187,7 @@ def destroy @event.create_activity :destroy, owner: current_user @event.destroy respond_to do |format| - format.html { redirect_to events_url, notice: 'Event was successfully destroyed.' } + format.html { redirect_to events_path, notice: 'Event was successfully destroyed.' } format.json { head :no_content } end end diff --git a/app/controllers/learning_path_topics_controller.rb b/app/controllers/learning_path_topics_controller.rb index 5fdf24169..7489e14fc 100644 --- a/app/controllers/learning_path_topics_controller.rb +++ b/app/controllers/learning_path_topics_controller.rb @@ -68,7 +68,7 @@ def destroy @learning_path_topic.create_activity :destroy, owner: current_user @learning_path_topic.destroy respond_to do |format| - format.html { redirect_to learning_path_topics_url, notice: 'Topic was successfully destroyed.' } + format.html { redirect_to learning_path_topics_path, notice: 'Topic was successfully destroyed.' } # format.json { head :no_content } end end diff --git a/app/controllers/learning_paths_controller.rb b/app/controllers/learning_paths_controller.rb index 83d91005a..a99105e8d 100644 --- a/app/controllers/learning_paths_controller.rb +++ b/app/controllers/learning_paths_controller.rb @@ -85,7 +85,7 @@ def destroy @learning_path.create_activity :destroy, owner: current_user @learning_path.destroy respond_to do |format| - format.html { redirect_to learning_paths_url, notice: 'Learning path was successfully destroyed.' } + format.html { redirect_to learning_paths_path, notice: 'Learning path was successfully destroyed.' } format.json { head :no_content } end end diff --git a/app/controllers/materials_controller.rb b/app/controllers/materials_controller.rb index 4d9a54723..0322f4adc 100644 --- a/app/controllers/materials_controller.rb +++ b/app/controllers/materials_controller.rb @@ -132,7 +132,7 @@ def destroy @material.create_activity :destroy, owner: current_user @material.destroy respond_to do |format| - format.html { redirect_to materials_url, notice: 'Material was successfully destroyed.' } + format.html { redirect_to materials_path, notice: 'Material was successfully destroyed.' } format.json { head :no_content } end end diff --git a/app/controllers/nodes_controller.rb b/app/controllers/nodes_controller.rb index cf942a5cb..331a4a8dc 100644 --- a/app/controllers/nodes_controller.rb +++ b/app/controllers/nodes_controller.rb @@ -79,7 +79,7 @@ def destroy @node.create_activity :destroy, owner: current_user @node.destroy respond_to do |format| - format.html { redirect_to nodes_url, notice: 'Node was successfully destroyed.' } + format.html { redirect_to nodes_path, notice: 'Node was successfully destroyed.' } format.json { head :no_content } end end diff --git a/app/controllers/sources_controller.rb b/app/controllers/sources_controller.rb index 33142098e..cd35bf435 100644 --- a/app/controllers/sources_controller.rb +++ b/app/controllers/sources_controller.rb @@ -24,7 +24,7 @@ def show # GET /sources/new def new - authorize Source + authorize @content_provider, :create_source? @source = @content_provider.sources.build end @@ -36,9 +36,10 @@ def edit # POST /sources # POST /sources.json def create - authorize Source + authorize @content_provider, :create_source? @source = @content_provider.sources.build(source_params) @source.user = current_user + @source.space = current_space respond_to do |format| if @source.save @@ -145,13 +146,12 @@ def set_source def set_content_provider @content_provider = @source.content_provider if @source @content_provider ||= ContentProvider.friendly.find(params[:content_provider_id]) - authorize @content_provider, :manage? end # Never trust parameters from the scary internet, only allow the white list through. def source_params permitted = [:url, :method, :token, :default_language, :enabled] - permitted << :approval_status if policy(Source).approve? + permitted << :approval_status if policy(@source || Source).approve? permitted << :content_provider_id if policy(Source).index? params.require(:source).permit(permitted) diff --git a/app/controllers/spaces_controller.rb b/app/controllers/spaces_controller.rb index 5d58c0512..8d118e303 100644 --- a/app/controllers/spaces_controller.rb +++ b/app/controllers/spaces_controller.rb @@ -65,7 +65,7 @@ def destroy @space.create_activity :destroy, owner: current_user @space.destroy respond_to do |format| - format.html { redirect_to spaces_url, notice: 'Space was successfully deleted.' } + format.html { redirect_to spaces_path, notice: 'Space was successfully deleted.' } end end diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index b2b306558..937f1bc2f 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -87,7 +87,7 @@ def destroy @workflow.create_activity :destroy, owner: current_user @workflow.destroy respond_to do |format| - format.html { redirect_to workflows_url, notice: 'Workflow was successfully destroyed.' } + format.html { redirect_to workflows_path, notice: 'Workflow was successfully destroyed.' } format.json { head :no_content } end end diff --git a/app/mailers/curation_mailer.rb b/app/mailers/curation_mailer.rb index 399ad2a70..a8679dbdc 100644 --- a/app/mailers/curation_mailer.rb +++ b/app/mailers/curation_mailer.rb @@ -17,7 +17,8 @@ def source_requires_approval(source, user) @user = user @source = source subject = "#{TeSS::Config.site['title_short']} source \"#{@source.title}\" requires approval" - mail(subject:, to: User.with_role('admin').map(&:email)) do |format| + space = @source.space || Space.default + mail(subject:, to: space.administrators.map(&:email)) do |format| format.html format.text end diff --git a/app/models/default_space.rb b/app/models/default_space.rb index 3f1f8144d..f0680d3d1 100644 --- a/app/models/default_space.rb +++ b/app/models/default_space.rb @@ -56,4 +56,8 @@ def learning_path_topics def default? true end + + def administrators + User.with_role('admin') + end end diff --git a/app/models/source.rb b/app/models/source.rb index 381e5da0a..889bf44a0 100644 --- a/app/models/source.rb +++ b/app/models/source.rb @@ -2,6 +2,7 @@ class Source < ApplicationRecord include LogParameterChanges include HasTestJob include HasAssociatedNodes + include InSpace APPROVAL_STATUS = { 0 => :not_approved, diff --git a/app/models/user.rb b/app/models/user.rb index 44b713043..dfb7df1a4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -381,6 +381,10 @@ def has_space_role?(space, role) space_roles.where(key: role, space: space).any? end + def has_role_in_any_space?(role) + space_roles.where(key: role).any? + end + protected def reassign_resources(new_owner = User.get_default_user) diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index 4bf15e15a..895ab57b3 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -16,6 +16,8 @@ def initialize(context, record) @user = context.user @request = context.request @record = record + @space = nil + @space = record.space if record.respond_to?(:space) end def index? @@ -24,14 +26,10 @@ def index? def show? true - # scope.where(:id => record.id).exists? end def create? - # Only admin, scraper_user or curator roles can create - #@user.has_role?(:admin) or @user.has_role?(:scraper_user) or @user.has_role?(:curator) - # Any registered user user can create - @user && !@user.role.blank? + @user end def new? @@ -52,19 +50,11 @@ def destroy? # "manage" isn't actually an action, but the "destroy?" and "update?" policies delegate to this method. def manage? - @user && @user.is_admin? - end - - def request_is_api?(request) - return false if request.nil? - return ((request.post? or request.put? or request.patch?) and request.format.json?) + @user&.is_admin? end def curators_and_admin - @user && ( - @user.has_role?(:curator) || - @user.has_role?(:admin) || - @user.has_role?(:scraper_user)) + user_has_role?(:curator, :admin, :scraper_user) end def scope @@ -84,4 +74,22 @@ def resolve end end + private + + def request_is_api? + !!@request && ((@request.post? || @request.put? || @request.patch?) && @request.format.json?) + end + + def scraper? + request_is_api? && @user&.has_role?(:scraper_user) + end + + # Check if the user has any of the given roles. + # If we're in a space, also check they have any of those roles in the context of the space. + def user_has_role?(*roles) + return false if @user.nil? + roles.any? { |r| @user.has_role?(r) } || + (@space && roles.any? { |r| @user.has_space_role?(@space, r) }) + end + end diff --git a/app/policies/content_provider_policy.rb b/app/policies/content_provider_policy.rb index 9325015af..0e2cc2dbf 100644 --- a/app/policies/content_provider_policy.rb +++ b/app/policies/content_provider_policy.rb @@ -1,3 +1,6 @@ class ContentProviderPolicy < ScrapedResourcePolicy - + def create_source? + (TeSS::Config.feature['user_source_creation'] && manage?) || + user_has_role?(:admin, :curator) + end end diff --git a/app/policies/learning_path_policy.rb b/app/policies/learning_path_policy.rb index ae6e287c5..ca1a2b210 100644 --- a/app/policies/learning_path_policy.rb +++ b/app/policies/learning_path_policy.rb @@ -9,7 +9,7 @@ def update? end def manage? - curators_and_admin || @user&.has_role?(:learning_path_curator) + curators_and_admin || user_has_role?(:learning_path_curator) end def create? diff --git a/app/policies/learning_path_topic_policy.rb b/app/policies/learning_path_topic_policy.rb index e393948d5..a5a523e94 100644 --- a/app/policies/learning_path_topic_policy.rb +++ b/app/policies/learning_path_topic_policy.rb @@ -5,7 +5,7 @@ def update? end def manage? - curators_and_admin || @user&.has_role?(:learning_path_curator) + curators_and_admin || user_has_role?(:learning_path_curator) end def create? diff --git a/app/policies/node_policy.rb b/app/policies/node_policy.rb index 774006a57..96853b402 100644 --- a/app/policies/node_policy.rb +++ b/app/policies/node_policy.rb @@ -2,26 +2,11 @@ class NodePolicy < ApplicationPolicy def create? # Only admin, scraper_user, curator or node_curator roles can create - @user && (@user.has_role?(:admin) || @user.has_role?(:scraper_user) || @user.has_role?(:curator) || @user.has_role?(:node_curator)) + user_has_role?(:admin, :curator, :node_curator) || scraper? end def manage? - return false unless @user - return true if @user.is_admin? - - if request_is_api?(@request) #is this an API action - allow scraper_user roles only - if @user.has_role?(:scraper_user) #and @user.is_owner?(@record) # check ownership - return true - else - return false - end - end - - if @user.has_role?(:curator) || @user.has_role?(:node_curator) || @user.is_owner?(@record) - return true - else - return false - end + user_has_role?(:admin, :curator, :node_curator) || scraper? || @user&.is_owner?(@record) end end diff --git a/app/policies/resource_policy.rb b/app/policies/resource_policy.rb index 4a36b1789..0de44e133 100644 --- a/app/policies/resource_policy.rb +++ b/app/policies/resource_policy.rb @@ -10,7 +10,7 @@ def create? end def manage? - super || (@user && (@user.is_owner?(@record) || (request_is_api?(@request) && @user.has_role?(:scraper_user)))) + super || (@user&.is_owner?(@record) || scraper?) end end diff --git a/app/policies/scraped_resource_policy.rb b/app/policies/scraped_resource_policy.rb index bd716259e..c28afd262 100644 --- a/app/policies/scraped_resource_policy.rb +++ b/app/policies/scraped_resource_policy.rb @@ -3,7 +3,7 @@ class ScrapedResourcePolicy < ResourcePolicy def manage? - super || (@user && @user.is_curator?) || is_content_provider_editor? + super || (@user&.is_curator?) || is_content_provider_editor? end private diff --git a/app/policies/source_policy.rb b/app/policies/source_policy.rb index b21620790..d56933235 100644 --- a/app/policies/source_policy.rb +++ b/app/policies/source_policy.rb @@ -13,15 +13,11 @@ def index? end def create? - if TeSS::Config.feature['user_source_creation'] - super - else - administration? - end + administration? end def approve? - @user && @user.has_role?(:admin) + user_has_role?(:admin) end def request_approval? diff --git a/app/policies/space_policy.rb b/app/policies/space_policy.rb index 1d5197e12..c2ce876c4 100644 --- a/app/policies/space_policy.rb +++ b/app/policies/space_policy.rb @@ -1,7 +1,7 @@ class SpacePolicy < ApplicationPolicy def create? - @user && @user.has_role?(:admin) + @user&.has_role?(:admin) end def edit? @@ -13,7 +13,7 @@ def update? end def manage? - @user && @user.is_admin? + @user&.is_admin? end end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index 827506f4f..fadaf44d3 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -12,13 +12,13 @@ def show? def create? # Do not allow creations via API and only admin role can create users - !request_is_api?(request) && @user && @user.is_admin? + !request_is_api? && @user&.is_admin? end def manage? # Do not allow updates via API # Only admin role can update other users or the users themselves - !request_is_api?(request) && (@user && (@user == @record || @user.is_admin?)) + !request_is_api? && (@user && (@user == @record || @user.is_admin?)) end def change_token? diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index e7120aed2..8051e325f 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -4,7 +4,8 @@ <%= link_to t('devise.links.log_in'), new_session_path(resource_name) %> <% end -%> - <%- if devise_mapping.registerable? && controller_name != 'registrations' %> + <%- if devise_mapping.registerable? && controller_name != 'registrations' && + TeSS::Config.feature['registration'] && !from_blocked_country? %>