mirror of https://github.com/docusealco/docuseal
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
5.7 KiB
175 lines
5.7 KiB
# frozen_string_literal: true
|
|
|
|
module Api
|
|
module V1
|
|
# InstitutionsController
|
|
# Handles CRUD operations for institutions
|
|
# Implements Winston's 4-layer security architecture
|
|
class InstitutionsController < ApiBaseController
|
|
# Layer 3: Authorization - check CanCanCan abilities
|
|
authorize_resource
|
|
|
|
# Layer 3: Security verification
|
|
before_action :verify_institution_access, except: [:index, :create]
|
|
before_action :set_institution, only: [:show, :update, :destroy]
|
|
|
|
# GET /api/v1/institutions
|
|
def index
|
|
# Layer 1: Database-level security via scope
|
|
@institutions = Institution.for_user(current_user)
|
|
|
|
# Layer 4: Add role information for UI
|
|
@institutions_with_roles = @institutions.map do |inst|
|
|
{
|
|
id: inst.id,
|
|
name: inst.name,
|
|
registration_number: inst.registration_number,
|
|
contact_email: inst.contact_email,
|
|
role: inst.user_role(current_user),
|
|
is_super_admin: inst.super_admin?(current_user),
|
|
created_at: inst.created_at
|
|
}
|
|
end
|
|
|
|
render json: { institutions: @institutions_with_roles }
|
|
end
|
|
|
|
# GET /api/v1/institutions/:id
|
|
def show
|
|
# Layer 1: Scoped query (already verified by before_action)
|
|
# Layer 2: CanCanCan ability check
|
|
# Layer 3: verify_institution_access already ran
|
|
|
|
render json: {
|
|
institution: @institution,
|
|
role: @institution.user_role(current_user),
|
|
is_super_admin: @institution.super_admin?(current_user),
|
|
settings: @institution.settings_with_defaults
|
|
}
|
|
end
|
|
|
|
# POST /api/v1/institutions
|
|
def create
|
|
# Layer 3: Authorization check (super admin only)
|
|
unless current_user.cohort_super_admin?
|
|
log_security_event(:insufficient_privileges, {
|
|
action: 'create_institution',
|
|
required_role: 'cohort_super_admin'
|
|
})
|
|
return render json: { error: 'Super admin access required' }, status: :forbidden
|
|
end
|
|
|
|
# Layer 4: Strong parameters validation
|
|
@institution = Institution.new(institution_params)
|
|
@institution.account = current_user.account
|
|
@institution.super_admin = current_user
|
|
|
|
if @institution.save
|
|
# Create initial account access for super admin
|
|
AccountAccess.create!(
|
|
account: current_user.account,
|
|
user: current_user,
|
|
institution: @institution,
|
|
role: 'cohort_super_admin'
|
|
)
|
|
|
|
log_security_event(:institution_created, {
|
|
institution_id: @institution.id,
|
|
name: @institution.name
|
|
})
|
|
|
|
render json: {
|
|
institution: @institution,
|
|
message: 'Institution created successfully'
|
|
}, status: :created
|
|
else
|
|
render json: { errors: @institution.errors.full_messages }, status: :unprocessable_entity
|
|
end
|
|
end
|
|
|
|
# PATCH/PUT /api/v1/institutions/:id
|
|
def update
|
|
# Layer 3: Authorization (super admin only for updates)
|
|
unless @institution.super_admin?(current_user)
|
|
log_security_event(:insufficient_privileges, {
|
|
action: 'update_institution',
|
|
institution_id: @institution.id,
|
|
required_role: 'super_admin'
|
|
})
|
|
return render json: { error: 'Only super admins can update institutions' }, status: :forbidden
|
|
end
|
|
|
|
if @institution.update(institution_params)
|
|
log_security_event(:institution_updated, {
|
|
institution_id: @institution.id,
|
|
changes: institution_params.keys
|
|
})
|
|
|
|
render json: { institution: @institution, message: 'Updated successfully' }
|
|
else
|
|
render json: { errors: @institution.errors.full_messages }, status: :unprocessable_entity
|
|
end
|
|
end
|
|
|
|
# DELETE /api/v1/institutions/:id
|
|
def destroy
|
|
# Layer 3: Authorization (super admin only)
|
|
unless @institution.super_admin?(current_user)
|
|
log_security_event(:insufficient_privileges, {
|
|
action: 'destroy_institution',
|
|
institution_id: @institution.id,
|
|
required_role: 'super_admin'
|
|
})
|
|
return render json: { error: 'Only super admins can delete institutions' }, status: :forbidden
|
|
end
|
|
|
|
# Safety check: Don't delete if has active cohorts
|
|
if @institution.cohorts.exists?
|
|
return render json: {
|
|
error: 'Cannot delete institution with active cohorts. Archive cohorts first.'
|
|
}, status: :unprocessable_entity
|
|
end
|
|
|
|
institution_name = @institution.name
|
|
@institution.destroy!
|
|
|
|
log_security_event(:institution_deleted, {
|
|
institution_name: institution_name
|
|
})
|
|
|
|
render json: { message: "Institution '#{institution_name}' deleted successfully" }
|
|
end
|
|
|
|
private
|
|
|
|
def set_institution
|
|
# Layer 1: Scoped query for security
|
|
@institution = Institution.for_user(current_user).find_by(id: params[:id])
|
|
|
|
unless @institution
|
|
log_security_event(:unauthorized_institution_access, {
|
|
attempted_institution_id: params[:id]
|
|
})
|
|
render json: { error: 'Institution not found or access denied' }, status: :not_found
|
|
end
|
|
end
|
|
|
|
def institution_params
|
|
# Layer 4: Strong parameters
|
|
params.require(:institution).permit(
|
|
:name,
|
|
:registration_number,
|
|
:address,
|
|
:contact_email,
|
|
:contact_phone,
|
|
settings: [
|
|
:allow_student_enrollment,
|
|
:require_verification,
|
|
:auto_finalize,
|
|
:email_notifications
|
|
]
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end |