РСализация Π½Π° роловия ΠΌΠΎΠ΄Π΅Π» Π·Π° Π΄ΠΎΡΡ‚ΡŠΠΏ с ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π°Π½Π΅ Π½Π° Row Level Security Π² PostgreSQL

Π Π°Π·Π²ΠΈΡ‚ΠΈΠ΅ Π½Π° Ρ‚Π΅ΠΌΠ°Ρ‚Π° ЕсСнно ΠΈΠ·Π»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π½Π° рСализацията Π½Π° Row Level Security Π² PostgreSQL ΠΈ Π·Π° Ρ€Π°Π·ΡˆΠΈΡ€Π΅Π½ ΠΎΡ‚Π³ΠΎΠ²ΠΎΡ€ Π½Π° ΠΊΠΎΠΌΠ΅Π½Ρ‚Π°Ρ€.

Π˜Π·ΠΏΠΎΠ»Π·Π²Π°Π½Π°Ρ‚Π° стратСгия ΠΏΡ€Π΅Π΄Π²ΠΈΠΆΠ΄Π° концСпцията β€žΠ‘ΠΈΠ·Π½Π΅Ρ Π»ΠΎΠ³ΠΈΠΊΠ° Π² Π‘Π”β€œ, ΠΊΠΎΠ΅Ρ‚ΠΎ бСшС описано ΠΏΠΎ-ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎ Ρ‚ΡƒΠΊ β€” ЕсСнно ΠΈΠ·Π»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π½Π° Ρ€Π΅Π°Π»ΠΈΠ·ΠΈΡ€Π°Π½Π΅ Π½Π° бизнСс Π»ΠΎΠ³ΠΈΠΊΠ° Π½Π° Π½ΠΈΠ²ΠΎ Ρ…Ρ€Π°Π½ΠΈΠΌ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ PostgreSQL

Π’Π΅ΠΎΡ€Π΅Ρ‚ΠΈΡ‡Π½Π°Ρ‚Π° част Π΅ ΠΎΡ‚Π»ΠΈΡ‡Π½ΠΎ описана Π² докумСнтацията Postgres Pro β€” ΠŸΠΎΠ»ΠΈΡ‚ΠΈΠΊΠΈ Π·Π° Π·Π°Ρ‰ΠΈΡ‚Π° Π½Π° Ρ€Π΅Π΄ΠΎΠ²Π΅Ρ‚Π΅. По-Π΄ΠΎΠ»Ρƒ Π΅ Ρ€Π°Π·Π³Π»Π΅Π΄Π°Π½Π° практичСската рСализация Π½Π° ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Π° бизнСс Π·Π°Π΄Π°Ρ‡Π° β€” Ρ€ΠΎΠ»Π΅Π²Π°Ρ‚Π° ΠΌΠΎΠ΄Π΅Π» Π½Π° Π΄ΠΎΡΡ‚ΡŠΠΏ Π΄ΠΎ Π΄Π°Π½Π½ΠΈ.

РСализация Π½Π° роловия ΠΌΠΎΠ΄Π΅Π» Π·Π° Π΄ΠΎΡΡ‚ΡŠΠΏ с ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π°Π½Π΅ Π½Π° Row Level Security Π² PostgreSQL

Π’ статията няма Π½ΠΈΡ‰ΠΎ Π½ΠΎΠ²ΠΎ, няма скрито Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΈ Ρ‚Π°ΠΉΠ½ΠΈ знания. ΠŸΡ€ΠΎΡΡ‚ΠΎ скица Π½Π° практичСската рСализация Π½Π° Ρ‚Π΅ΠΎΡ€Π΅Ρ‚ΠΈΡ‡Π½Π° идСя. Ако някой сС интСрСсува β€” Ρ‡Π΅Ρ‚Π΅. Ако Π½Π΅ сС интСрСсува β€” Π½Π΅ Π³ΡƒΠ±Π΅Ρ‚Π΅ Π²Ρ€Π΅ΠΌΠ΅Ρ‚ΠΎ си Π½Π°ΠΏΡ€Π°Π·Π½ΠΎ.

Π€ΠΎΡ€ΠΌΡƒΠ»ΠΈΡ€Π°Π½Π΅ Π½Π° Π·Π°Π΄Π°Ρ‡Π°Ρ‚Π°

НСобходимо Π΅ Π΄Π° сС Ρ€Π°Π·Π³Ρ€Π°Π½ΠΈΡ‡Π°Ρ‚ ΠΏΡ€Π°Π²Π°Ρ‚Π° Π·Π° ΠΏΡ€Π΅Π³Π»Π΅Π΄/вмъкванС/промяна/ΠΈΠ·Ρ‚Ρ€ΠΈΠ²Π°Π½Π΅ Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π° Π² ΡΡŠΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΠΈΠ΅ с ролята Π½Π° потрСбитСля Π½Π° ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅Ρ‚ΠΎ. Под ролята сС Ρ€Π°Π·Π±ΠΈΡ€Π° записа Π² Ρ‚Π°Π±Π»ΠΈΡ†Π°Ρ‚Π° roles ΡΠ²ΡŠΡ€Π·Π°Π½Π° с ΠΎΡ‚Π½ΠΎΡˆΠ΅Π½ΠΈΠ΅ ΠΌΠ½ΠΎΠ³ΠΎ-към-ΠΌΠ½ΠΎΠ³ΠΎ с Ρ‚Π°Π±Π»ΠΈΡ†Π°Ρ‚Π° users. Π”Π΅Ρ‚Π°ΠΉΠ»ΠΈΡ‚Π΅ ΠΎΡ‚ рСализацията Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†ΠΈΡ‚Π΅, ΠΏΠΎΡ€Π°Π΄ΠΈ тривиалността ΠΈΠΌ, са опуснати. Π‘ΡŠΡ‰ΠΎ Ρ‚Π°ΠΊΠ° са опуснати ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΈ Π΄Π΅Ρ‚Π°ΠΉΠ»ΠΈ ΠΎΡ‚ рСализацията, ΡΠ²ΡŠΡ€Π·Π°Π½ΠΈ с ΠΏΡ€Π΅Π΄ΠΌΠ΅Ρ‚Π½Π°Ρ‚Π° област.

РСализация

БъздавамС Ρ€ΠΎΠ»ΠΈ, схСми, Ρ‚Π°Π±Π»ΠΈΡ†Π°

БъздаванС Π½Π° ΠΎΠ±Π΅ΠΊΡ‚ΠΈ Π½Π° Π‘Π”

CREATE ROLE store;
CREATE SCHEMA store AUTHORIZATION store;
CREATE TABLE store.docs
(
  id integer ,         --id Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°
  man_id integer , --id Π½Π° ΠΌΠ΅Π½ΠΈΠ΄ΠΆΡŠΡ€Π° Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°
  stat_id integer ,  --id Π½Π° статуса Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°
  ...
  is_del BOOLEAN DEFAULT FALSE 
);
ALTER TABLE store.docs ADD CONSTRAINT doc_pk PRIMARY KEY (id);
ALTER TABLE store.docs OWNER TO store ;

БъздавамС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Π·Π° рСализация Π½Π° RLS

ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Π½Π° Π²ΡŠΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚Ρ‚Π° Π·Π° изпълнСниС Π½Π° SELECT Ρ€Π΅Π΄

check_select

CREATE OR REPLACE FUNCTION store.check_select ( current_id store.docs.id%TYPE ) RETURNS boolean AS $$
DECLARE
  result boolean ;
  curr_pid integer ;
  curr_stat_id integer ;
  doc_man_id integer ;
BEGIN 
  -- DBA ΠΈΠΌΠ° Π΄ΠΎΡΡ‚ΡŠΠΏ Π΄ΠΎ всички Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΈ
  IF SESSION_USER = 'curr_dba'
  THEN
    RETURN TRUE ;
  END IF ;
  --------------------------------

  --Ако Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΡŠΡ‚ ΠΈΠΌΠ° ΠΌΠ°Ρ€ΠΊΠ° 'ΠΈΠ·Ρ‚Ρ€ΠΈΡ‚' - Π½Π΅ ΠΏΠΎΠΊΠ°Π·Π²Π°ΠΉΡ‚Π΅ Π² ΠΈΠ·Π±ΠΎΡ€Π°
  SELECT
    is_del
  INTO
    result
  FROM
    store.docs
  WHERE
    id = current_id ;
 IF result = TRUE
 THEN
   RETURN FALSE ;
 END IF ;
 --------------------------------

 --ΠŸΠΎΠ»ΡƒΡ‡Π΅Ρ‚Π΅ id Π½Π° тСкущия ΠΏΠΎΡ‚Ρ€Π΅Π±ΠΈΡ‚Π΅Π»
 SELECT
   service_function.get_curr_pid ()
 INTO
   curr_pid ;
 --------------------------------

 --ΠŸΠΎΠ»ΡƒΡ‡Π΅Ρ‚Π΅ id Π½Π° ΠΌΠ΅Π½ΠΈΠ΄ΠΆΡŠΡ€Π° Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°
 SELECT
   man_id
 INTO
   doc_man_id
 FROM
   store.docs
 WHERE
   id = current_id ;
 --------------------------------

 --Ако ΠΌΠ΅Π½ΠΈΠ΄ΠΆΡŠΡ€ΡŠΡ‚ Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π° Π½Π΅ Π΅ тСкущият ΠΏΠΎΡ‚Ρ€Π΅Π±ΠΈΡ‚Π΅Π» ΠΈΠ»ΠΈ ΠΌΠ΅Π½ΠΈΠ΄ΠΆΡŠΡ€ΡŠΡ‚ Π½Π΅ Π΅ Π½Π°Π·Π½Π°Ρ‡Π΅Π½
 --Π΄ΠΎΠ±Π°Π²Π΅Ρ‚Π΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π° Π² ΠΈΠ·Π±ΠΎΡ€Π°
 IF doc_man_id != curr_pid OR doc_man_id IS NULL
 THEN
   RETURN TRUE  ;
 ELSE
   --ΠŸΠΎΠ»ΡƒΡ‡Π΅Ρ‚Π΅ тСкущия статус Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°
   SELECT
     stat_id                                         
   INTO
     curr_statid
   FROM
     store.docs
   WHERE
     id = current_id ;
    
   --Ако ΡΡ‚Π°Ρ‚ΡƒΡΡŠΡ‚ позволява видимост Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π° - Π΄ΠΎΠ±Π°Π²Π΅Ρ‚Π΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π° Π² ΠΈΠ·Π±ΠΎΡ€Π°                     
   IF curr_statid = 4 OR curr_statid = 9
   THEN
     RETURN TRUE ;
   ELSE
   --Π˜Π½Π°Ρ‡Π΅ - ΠΈΠ·ΠΊΠ»ΡŽΡ‡Π΅Ρ‚Π΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π° ΠΎΡ‚ ΠΈΠ·Π±ΠΎΡ€Π°
     RETURN FALSE ;
    END IF ;
  END IF ;
  --------------------------------

 RETURN FALSE ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.check_select( store.docs.id%TYPE  ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.check_select( store.docs.id%TYPE  ) FROM public; 
GRANT EXECUTE ON FUNCTION store.check_select( store.docs.id%TYPE  ) TO service_functions; 

ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Π½Π° Π²ΡŠΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚Ρ‚Π° Π·Π° ΠΈΠ·Π²ΡŠΡ€ΡˆΠ²Π°Π½Π΅ Π½Π° INSERT Π½Π° Ρ€Π΅Π΄

check_insert

CREATE OR REPLACE FUNCTION store.check_insert ( current_id store.docs.id%TYPE ) RETURNS boolean AS $$
DECLARE
  curr_role_id integer ;
BEGIN
  --DBA ΠΌΠΎΠΆΠ΅ Π΄Π° добавя Ρ€Π΅Π΄ във всСки случай
  IF SESSION_USER = 'curr_dba'
  THEN
    RETURN TRUE ;
  END IF ;
  --------------------------------

 --ΠŸΠΎΠ»ΡƒΡ‡Π΅Ρ‚Π΅ id Π½Π° ролята Π½Π° тСкущия ΠΏΠΎΡ‚Ρ€Π΅Π±ΠΈΡ‚Π΅Π» 
 SELECT
   service_functions.current_rid()
  INTO
    curr_role_id ;
 --------------------------------

--Ако ролята допуска Π²ΡŠΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚Ρ‚Π° Π·Π° създаванС Π½Π° Π½ΠΎΠ² Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚
--Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Ρ‚Π΅
IF curr_role_id = 3 OR curr_role_id = 5     
THEN
  RETURN TRUE ;
END IF ;
--------------------------------
RETURN FALSE  ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.check_insert( store.docs.id%TYPE  ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.check_insert( store.docs.id%TYPE  ) FROM public;
GRANT EXECUTE ON FUNCTION store.check_insert( store.docs.id%TYPE  ) TO service_functions; 

ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Π½Π° Π²ΡŠΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚Ρ‚Π° Π·Π° ΠΈΠ·Π²ΡŠΡ€ΡˆΠ²Π°Π½Π΅ Π½Π° DELETE Π½Π° Ρ€Π΅Π΄

check_delete

CREATE OR REPLACE FUNCTION store.check_delete ( current_id store.docs.id%TYPE )
RETURNS boolean AS $$
BEGIN  
  --Π‘Π°ΠΌΠΎ DBA ΠΌΠΎΠΆΠ΅ Π΄Π° ΠΈΠ·Ρ‚Ρ€ΠΈΠ²Π° Ρ€Π΅Π΄ 
  IF SESSION_USER = 'curr_dba'
  THEN
    RETURN TRUE ;
  END IF ;
  --------------------------------

  RETURN FALSE ;
END
$$ LANGUAGE plpgsql
SECURITY DEFINER;
ALTER FUNCTION store.check_delete( store.docs.id%TYPE  ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.check_delete( store.docs.id%TYPE  ) FROM public;

ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Π½Π° Π²ΡŠΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚Ρ‚Π° Π·Π° ΠΈΠ·Π²ΡŠΡ€ΡˆΠ²Π°Π½Π΅ Π½Π° UPDATE Π½Π° Ρ€Π΅Π΄.

update_using

CREATE OR REPLACE FUNCTION store.update_using ( current_id store.docs.id%TYPE , is_del boolean  )
RETURNS boolean AS $$
BEGIN  
   --Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΈΡ‚Π΅ със статус 'ΠΈΠ·Ρ‚Ρ€ΠΈΡ‚' Π½Π΅ ΠΌΠΎΠ³Π°Ρ‚ Π΄Π° Π±ΡŠΠ΄Π°Ρ‚ Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€Π°Π½ΠΈ
   IF is_del 
   THEN
     RETURN FALSE ;
 ELSE
    RETURN TRUE ;
  END IF ;

END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.update_using(  store.docs.id%TYPE ,  boolean  ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.update_using(  store.docs.id%TYPE ,  boolean  ) FROM public;
GRANT EXECUTE ON FUNCTION store.update_using( store.docs.id%TYPE  ) TO service_functions;

update_check

CREATE OR REPLACE FUNCTION store.update_with_check ( current_id store.docs.id%TYPE , is_del boolean )
RETURNS boolean AS $$
DECLARE
  current_rid integer ;
  current_statid integer ;
BEGIN                

  --DBA ΠΌΠΎΠΆΠ΅ Π΄Π° ΠΏΡ€Π΅Π³Π»Π΅ΠΆΠ΄Π° Ρ€Π΅Π΄Π° 
  IF SESSION_USER = 'curr_dba'
  THEN
    RETURN TRUE ;
  END IF ;
  --------------------------------

 --ΠŸΠΎΠ»ΡƒΡ‡Π°Π²Π°Π½Π΅ Π½Π° id Π½Π° ролята Π½Π° тСкущия ΠΏΠΎΡ‚Ρ€Π΅Π±ΠΈΡ‚Π΅Π» 
 SELECT
   service_functions.current_rid()
  INTO
    curr_role_id ;
 --------------------------------                            

 --Π˜Π·Ρ‚Ρ€ΠΈΠ²Π°Π½Π΅ Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π° - промяна Π½Π° ΠΏΡ€ΠΈΠ·Π½Π°ΠΊΠ° 
 IF is_deleted
 THEN
   --Ако ролята Π½Π° потрСбитСля ***
   IF current_role_id = 3        
   THEN
      SELECT
        stat_id                                          
      INTO
        curr_statid
      FROM
        store.docs
      WHERE
        id = current_id ;

      --Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ Π² статус *** Π½Π΅ ΠΌΠΎΠΆΠ΅ Π΄Π° бъдС ΠΈΠ·Ρ‚Ρ€ΠΈΡ‚ 
      IF current_status_id = 11
      THEN
         RETURN FALSE ;
      ELSE
      --МоТС Π΄Π° сС ΠΈΠ·Ρ‚Ρ€ΠΈΠ΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ Π² Π΄Ρ€ΡƒΠ³ΠΈ статуси
        RETURN TRUE ;
      END IF ;

    --Π’ ΠΏΡ€ΠΎΡ‚ΠΈΠ²Π΅Π½ случай, Π°ΠΊΠΎ ролята Π½Π° потрСбитСля ***
    ELSIF current_role_id = 5            
    THEN
      --Всички статути Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π° 
      RETURN TRUE ;
    ELSE
      --Π”Ρ€ΡƒΠ³ΠΈ ΠΏΠΎΡ‚Ρ€Π΅Π±ΠΈΡ‚Π΅Π»ΠΈ Π½Π΅ ΠΌΠΎΠ³Π°Ρ‚ Π΄Π° ΠΈΠ·Ρ‚Ρ€ΠΈΠ²Π°Ρ‚ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΈ
      RETURN FALSE ;
    END IF ;
 ELSE      
   --ΠžΠ±Π½ΠΎΠ²ΡΠ²Π°Π½Π΅Ρ‚ΠΎ Π½Π° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π° Π΅ Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΎ
    RETURN TRUE ;
END IF ;

RETURN FALSE ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.update_with_check( store.docs.id%TYPE ,  boolean   ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.update_with_check( store.docs.id%TYPE ,  boolean   )  FROM public;
GRANT EXECUTE ON FUNCTION store.update_with_check( store.docs.id%TYPE  ) TO service_functions;

Π’ΠΊΠ»ΡŽΡ‡Π²Π°Π½Π΅ Π½Π° ΠΏΠΎΠ»ΠΈΡ‚ΠΈΠΊΠ°Ρ‚Π° Row Level Security Π·Π° Ρ‚Π°Π±Π»ΠΈΡ†Π°Ρ‚Π°.

ENABLE ROW LEVEL SECURITY

ALTER TABLE store.docs ENABLE ROW LEVEL SECURITY ;

CREATE POLICY doc_select ON store.docs FOR SELECT TO service_functions USING ( (SELECT store.check_select(id)) );
CREATE POLICY doc_insert ON store.docs FOR INSERT TO service_functions WITH CHECK ( (SELECT store.check_insert(id)) );
CREATE POLICY docs_delete ON store.docs FOR DELETE TO service_functions USING ( (SELECT store.check_delete(id)) );

CREATE POLICY doc_update_using ON store.docs FOR UPDATE TO service_functions USING ( (SELECT store.update_using(id , is_del )) );
CREATE POLICY doc_update_check ON store.docs FOR UPDATE TO service_functions  WITH CHECK ( (SELECT store.update_with_check(id , is_del )) );

РСзюмС

Π’ΠΎΠ²Π° Ρ€Π°Π±ΠΎΡ‚ΠΈ.

ΠŸΡ€Π΅Π΄Π»ΠΎΠΆΠ΅Π½Π°Ρ‚Π° стратСгия ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈ прСмСстванСто Π½Π° рСализацията Π½Π° ΠΌΠΎΠ΄Π΅Π» Π½Π° роля ΠΎΡ‚ Π½ΠΈΠ²ΠΎΡ‚ΠΎ Π½Π° бизнСс Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Π΄ΠΎ Π½ΠΈΠ²ΠΎΡ‚ΠΎ Π½Π° ΡΡŠΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π½Π° Π΄Π°Π½Π½ΠΈ.

Π€ΡƒΠ½ΠΊΡ†ΠΈΠΈΡ‚Π΅ ΠΌΠΎΠ³Π°Ρ‚ Π΄Π° Π±ΡŠΠ΄Π°Ρ‚ ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π°Π½ΠΈ ΠΊΠ°Ρ‚ΠΎ шаблон Π·Π° Ρ€Π΅Π°Π»ΠΈΠ·ΠΈΡ€Π°Π½Π΅ Π½Π° ΠΏΠΎ-слоТни ΠΌΠΎΠ΄Π΅Π»ΠΈ Π½Π° скриванС Π½Π° Π΄Π°Π½Π½ΠΈ, Π°ΠΊΠΎ бизнСс изискванията Π³ΠΎ изискват.

Π˜Π·Ρ‚ΠΎΡ‡Π½ΠΈΠΊ: habr.com

ΠšΡƒΠΏΠ΅Ρ‚Π΅ Π½Π°Π΄Π΅ΠΆΠ΄Π΅Π½ хостинг Π·Π° сайтовС със Π·Π°Ρ‰ΠΈΡ‚Π° ΠΎΡ‚ DDoS, VPS ΠΈ VDS ΡΡŠΡ€Π²ΡŠΡ€ΠΈ πŸ”₯ ΠšΡƒΠΏΠ΅Ρ‚Π΅ Π½Π°Π΄Π΅ΠΆΠ΄Π΅Π½ хостинг Π·Π° сайтовС със Π·Π°Ρ‰ΠΈΡ‚Π° ΠΎΡ‚ DDoS, VPS ΠΈ VDS ΡΡŠΡ€Π²ΡŠΡ€ΠΈ | ProHoster