From 251b4350af4e0e45a679653179841a223bb0aec6 Mon Sep 17 00:00:00 2001 From: Derek Leadbetter Date: Mon, 26 Jun 2023 13:53:32 -0400 Subject: [PATCH 1/7] BASIRA #209 - Adding "admin" column to users; Updating /api/users API endpoint to accept "admin" attribute (for current user admin only) --- app/controllers/api/users_controller.rb | 9 +++++++++ app/serializers/users_serializer.rb | 4 ++-- db/migrate/20230626153029_add_admin_to_users.rb | 5 +++++ db/schema.rb | 3 ++- 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20230626153029_add_admin_to_users.rb diff --git a/app/controllers/api/users_controller.rb b/app/controllers/api/users_controller.rb index a90d08c..e1dc8ac 100644 --- a/app/controllers/api/users_controller.rb +++ b/app/controllers/api/users_controller.rb @@ -1,3 +1,12 @@ class Api::UsersController < Api::BaseController + # Search attributes search_attributes :name, :email + + protected + + def permitted_params + parameters = super + parameters << :admin if current_user.admin? + parameters + end end diff --git a/app/serializers/users_serializer.rb b/app/serializers/users_serializer.rb index 9621c17..da12947 100644 --- a/app/serializers/users_serializer.rb +++ b/app/serializers/users_serializer.rb @@ -1,4 +1,4 @@ class UsersSerializer < BaseSerializer - index_attributes :id, :name, :email - show_attributes :id, :name, :email + index_attributes :id, :name, :email, :admin + show_attributes :id, :name, :email, :admin end diff --git a/db/migrate/20230626153029_add_admin_to_users.rb b/db/migrate/20230626153029_add_admin_to_users.rb new file mode 100644 index 0000000..892a30e --- /dev/null +++ b/db/migrate/20230626153029_add_admin_to_users.rb @@ -0,0 +1,5 @@ +class AddAdminToUsers < ActiveRecord::Migration[7.0] + def change + add_column :users, :admin, :boolean, default: false, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 6784c74..caee1b2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_04_06_192435) do +ActiveRecord::Schema[7.0].define(version: 2023_06_26_153029) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" @@ -276,6 +276,7 @@ t.text "tokens" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.boolean "admin", default: false, null: false t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true From 2c2b29fb68eb22d198d627fce02df5992e77e207 Mon Sep 17 00:00:00 2001 From: Derek Leadbetter Date: Mon, 26 Jun 2023 13:54:38 -0400 Subject: [PATCH 2/7] BASIRA #209 - Adding "admin" checkbox to users modal --- client/src/components/UserModal.css | 0 client/src/components/UserModal.js | 10 +++++++++- client/src/i18n/en.json | 1 + client/src/services/Session.js | 30 ++++++++++++++++++++++++----- client/src/transforms/User.js | 3 ++- client/src/types/User.js | 3 ++- 6 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 client/src/components/UserModal.css diff --git a/client/src/components/UserModal.css b/client/src/components/UserModal.css new file mode 100644 index 0000000..e69de29 diff --git a/client/src/components/UserModal.js b/client/src/components/UserModal.js index 7a55de6..10a8940 100644 --- a/client/src/components/UserModal.js +++ b/client/src/components/UserModal.js @@ -3,8 +3,9 @@ import React from 'react'; import { withTranslation } from 'react-i18next'; import { Form, Message, Modal } from 'semantic-ui-react'; +import Session from '../services/Session'; -import type { EditContainerProps } from 'react-components/types'; +import type { EditContainerProps } from '@performant-software/shared-components/types'; import type { Translateable } from '../types/Translateable'; import type { User } from '../types/User'; @@ -39,6 +40,13 @@ const UserModal = (props: Props) => ( onChange={props.onTextInputChange.bind(this, 'email')} value={props.item.email || ''} /> + { Session.isAdmin() && ( + + )} Date: Mon, 26 Jun 2023 13:55:06 -0400 Subject: [PATCH 3/7] BASIRA #209 - Updating logout button to redirect to /login --- client/src/hooks/MenuBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/hooks/MenuBar.js b/client/src/hooks/MenuBar.js index 00f8c58..45baf81 100644 --- a/client/src/hooks/MenuBar.js +++ b/client/src/hooks/MenuBar.js @@ -142,7 +142,7 @@ const withMenuBar = (WrappedComponent: ComponentType) => withTranslation()( .logout() .then(() => { Session.destroy(); - props.history.push('/'); + props.history.push('/login'); }) )} /> From 15e95f5bad7874994fbb86ce8275b6759c065980 Mon Sep 17 00:00:00 2001 From: Derek Leadbetter Date: Mon, 26 Jun 2023 14:33:21 -0400 Subject: [PATCH 4/7] BASIRA #209 - Updating base_controller to check user authorization before deleting a record --- app/controllers/api/base_controller.rb | 6 ++++++ config/locales/en.yml | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index ad6ab59..6f28ac1 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -5,6 +5,12 @@ class Api::BaseController < Api::ResourceController # Actions before_action :authenticate_user!, except: :show + def destroy + render json: { errors: I18n.t('errors.unauthorized') }, status: :unauthorized and return unless current_user.admin? + + super + end + protected def prepare_params diff --git a/config/locales/en.yml b/config/locales/en.yml index cf9b342..fc24293 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -30,4 +30,5 @@ # available at https://guides.rubyonrails.org/i18n.html. en: - hello: "Hello world" + errors: + unauthorized: "Unauthorized" From cafd0165b5d501b6d5209159d372d48482a18237 Mon Sep 17 00:00:00 2001 From: Derek Leadbetter Date: Tue, 27 Jun 2023 13:07:18 -0400 Subject: [PATCH 5/7] BASIRA #209 - Adding delete/update validations for non-admin users --- app/controllers/api/base_controller.rb | 38 ++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 6f28ac1..40d58f6 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -4,12 +4,8 @@ class Api::BaseController < Api::ResourceController # Actions before_action :authenticate_user!, except: :show - - def destroy - render json: { errors: I18n.t('errors.unauthorized') }, status: :unauthorized and return unless current_user.admin? - - super - end + before_action :validate_delete_authorization, only: :destroy + before_action :validate_update_authorization, only: :update protected @@ -24,4 +20,34 @@ def prepare_params super end + + private + + def validate_delete_authorization + render json: { errors: [I18n.t('errors.unauthorized')] }, status: :unauthorized unless current_user.admin? + end + + def validate_update_authorization + return if current_user.admin? + + unauthorized = false + + item_class.nested_attributes_options.keys.each do |key| + nested_attributes = params[param_name][key] + next unless nested_attributes.present? + + # Handle JSON and FormData parameters + if nested_attributes.is_a?(Array) + attrs = nested_attributes + elsif nested_attributes.is_a?(ActionController::Parameters) && nested_attributes.keys.all?(&:is_integer?) + attrs = nested_attributes.keys.map{ |index| nested_attributes[index] } + end + + attrs.each do |attr| + unauthorized = true if attr['_destroy'].to_s.to_bool + end + end + + render json: { errors: [{ base: I18n.t('errors.unauthorized') }] }, status: :unauthorized if unauthorized + end end From 999bbb7ab144e7e0aafca453780a74c587db4d15 Mon Sep 17 00:00:00 2001 From: Derek Leadbetter Date: Tue, 27 Jun 2023 14:07:21 -0400 Subject: [PATCH 6/7] BASIRA #209 - Removing delete buttons for non-admin users; Adding error messages as fallback --- client/src/components/AdminArtworkMenu.js | 3 +- client/src/components/Images.js | 14 +++++- client/src/components/ValueListsTable.js | 15 ++++--- client/src/i18n/en.json | 3 +- client/src/pages/admin/AdminNotFound.js | 6 ++- client/src/pages/admin/Artwork.js | 7 +++ client/src/pages/admin/Artworks.js | 10 +++-- client/src/pages/admin/EditPage.js | 7 +-- client/src/pages/admin/People.js | 5 +++ client/src/pages/admin/Person.js | 7 ++- client/src/pages/admin/Place.js | 7 ++- client/src/pages/admin/Places.js | 5 +++ client/src/pages/admin/Users.js | 7 ++- client/src/pages/admin/ValueLists.js | 2 +- client/src/utils/Authorization.js | 55 +++++++++++++++++++++++ 15 files changed, 132 insertions(+), 21 deletions(-) create mode 100644 client/src/utils/Authorization.js diff --git a/client/src/components/AdminArtworkMenu.js b/client/src/components/AdminArtworkMenu.js index 3e15de9..6b422f1 100644 --- a/client/src/components/AdminArtworkMenu.js +++ b/client/src/components/AdminArtworkMenu.js @@ -18,6 +18,7 @@ import DocumentsService from '../services/Documents'; import { getPhysicalComponents, getVisualContexts } from '../utils/Artwork'; import ItemLabel from './ItemLabel'; import PhysicalComponentsService from '../services/PhysicalComponents'; +import Session from '../services/Session'; import VisualContextsService from '../services/VisualContexts'; import './AdminArtworkMenu.css'; @@ -172,7 +173,7 @@ const AdminArtworkMenu = (props: Props) => { to={`/admin${path}`} /> )} - { onDelete && ( + { Session.isAdmin() && onDelete && (