-- ════════════════════════════════════════════════════════════════
-- SECURITY HARDENING: Stored Procedure Ownership Enforcement
-- ════════════════════════════════════════════════════════════════
--
-- PURPOSE: Add WHERE clientId checks to all stored procedures
-- that accept initiativeId, channelCampaignId, or wizardId.
--
-- The Gateway now passes clientId in all JSON requests.
-- These proc changes enforce ownership at the database level
-- as a SECOND layer of defense (the Gateway guard is the first).
--
-- APPLY: Run against your AdPlatform SQL Server database.
-- TEST FIRST in dev/staging before production.
-- ════════════════════════════════════════════════════════════════

-- ──────────────────────────────────────────────────
-- PATTERN: Inside each proc's @Action handler, add:
--
--   DECLARE @clientId NVARCHAR(100) = JSON_VALUE(@Rqst, '$.clientId');
--
--   -- Then in every SELECT/UPDATE/DELETE that references an initiative:
--   WHERE i.initiativeId = @initiativeId
--     AND i.clientId = @clientId           -- ← ADD THIS
--
--   -- If the WHERE filters out the row, return "not found"
--   -- (same response as non-existent ID — prevents enumeration)
-- ──────────────────────────────────────────────────

PRINT '=== Security Hardening Migration ==='
PRINT ''

-- ──────────────────────────────────────────────────
-- 1. spInitiative — get, update, updateStatus, delete
-- ──────────────────────────────────────────────────

PRINT 'Hardening spInitiative...'

-- Example pattern for the "get" action:
-- (Apply this pattern to get, update, updateStatus, delete actions)
--
-- Current:
--   SELECT ... FROM tbInitiative WHERE initiativeId = @initiativeId
--
-- Hardened:
--   DECLARE @clientId NVARCHAR(100) = JSON_VALUE(@Rqst, '$.clientId')
--   SELECT ... FROM tbInitiative 
--   WHERE initiativeId = @initiativeId
--     AND (@clientId IS NULL OR clientId = @clientId)
--
-- The @clientId IS NULL fallback allows internal/system calls
-- (like InitiativeLaunchService) that don't pass clientId to still work.

-- IMPORTANT: Apply to each action in spInitiative:
--   'get'          → WHERE initiativeId = @id AND (@clientId IS NULL OR clientId = @clientId)
--   'update'       → same
--   'updateStatus' → same
--   'delete'       → same
--   'list'         → already scoped by clientId (verify it uses = not LIKE)

GO

-- ──────────────────────────────────────────────────
-- 2. spChannelCampaign — get, sync
-- ──────────────────────────────────────────────────

PRINT 'Hardening spChannelCampaign...'

-- Channel campaigns link to initiatives, so ownership requires a JOIN:
--
-- Current:
--   SELECT cc.* FROM tbChannelCampaign cc WHERE cc.channelCampaignId = @id
--
-- Hardened:
--   SELECT cc.* 
--   FROM tbChannelCampaign cc
--   JOIN tbInitiative i ON cc.initiativeId = i.initiativeId
--   WHERE cc.channelCampaignId = @id
--     AND (@clientId IS NULL OR i.clientId = @clientId)
--
-- For 'sync' action: This is now admin-only in the Gateway,
-- but add the JOIN anyway for defense in depth.

GO

-- ──────────────────────────────────────────────────
-- 3. spCampaignWizard — get, updateStep, setStep, submit, updateStatus, delete
-- ──────────────────────────────────────────────────

PRINT 'Hardening spCampaignWizard...'

-- Current:
--   SELECT ... FROM tbCampaignWizard WHERE wizardId = @wizardId
--
-- Hardened:
--   DECLARE @clientId NVARCHAR(100) = JSON_VALUE(@Rqst, '$.clientId')
--   SELECT ... FROM tbCampaignWizard 
--   WHERE wizardId = @wizardId
--     AND (@clientId IS NULL OR clientId = @clientId)

GO

-- ──────────────────────────────────────────────────
-- 4. spAllocation — all actions
-- ──────────────────────────────────────────────────

PRINT 'Hardening spAllocation...'

-- Allocations link to initiatives:
--
-- Hardened:
--   JOIN tbInitiative i ON a.initiativeId = i.initiativeId
--   WHERE a.initiativeId = @initiativeId
--     AND (@clientId IS NULL OR i.clientId = @clientId)

GO

-- ──────────────────────────────────────────────────
-- 5. Status transition validation at DB level (optional extra layer)
-- ──────────────────────────────────────────────────

PRINT 'Adding status transition function...'

-- Create a function the procs can call to validate transitions:
IF OBJECT_ID('dbo.fnIsValidStatusTransition', 'FN') IS NOT NULL
    DROP FUNCTION dbo.fnIsValidStatusTransition
GO

CREATE FUNCTION dbo.fnIsValidStatusTransition(
    @currentStatus VARCHAR(20),
    @requestedStatus VARCHAR(20),
    @isSystem BIT = 0       -- 1 = system/admin (broader transitions allowed)
)
RETURNS BIT
AS
BEGIN
    -- System can do anything
    IF @isSystem = 1 RETURN 1

    -- Client-allowed transitions
    IF @currentStatus = 'active'  AND @requestedStatus = 'paused'    RETURN 1
    IF @currentStatus = 'paused'  AND @requestedStatus = 'active'    RETURN 1
    IF @currentStatus IN ('draft','staged','pending','active','paused')
       AND @requestedStatus = 'cancelled' RETURN 1

    RETURN 0
END
GO

-- ──────────────────────────────────────────────────
-- 6. spGoogleAccount — validate
-- ──────────────────────────────────────────────────

PRINT 'Verifying spGoogleAccount...'

-- The validate action should verify that the customerId
-- belongs to the requesting client. Current implementation
-- may not check this — verify and add:
--
--   WHERE a.customerId = @customerId
--     AND a.clientId = @clientId

GO

PRINT ''
PRINT '=== Migration complete ==='
PRINT 'NOTE: This is a TEMPLATE. Review each stored procedure and apply'
PRINT 'the ownership WHERE clauses to match your exact table/column names.'
PRINT ''
PRINT 'After applying, test:'
PRINT '  1. Normal user can only see their own initiatives/wizards'
PRINT '  2. User A cannot access User B resources by guessing IDs'
PRINT '  3. LaunchService (no clientId) can still read initiatives'
PRINT '  4. Admin role can sync channel status'
GO
