<?xml version='1.0' ?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
               "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
<!ENTITY % myvars SYSTEM "../variables.ent">
%myvars;
]>
<sect2 lang="es" id="permissions-tediously-explained-es">
 <title>Sistema de Permisos de OpenACS Tediosamente Explicado</title>
	<!-- $Id: permissions-tediously-explained-es.xml,v 1.3 2006/09/25 20:32:37 byronl Exp $ -->
  <para>
    por Vadim Nasardinov. Modificado y convertido a Docbook XML por Roberto Mello. Traducido por David Arroyo.
  </para>

    <sect3 id="permissions-tedious-overview-es">

      <title>Introducci&oacute;n</title>

      <para>En OpenACS 3.x se consigui&oacute; tener un sistema reutilizable de control de permisos y poder contestar a la pregunta clave qui&eacute; puede hacer qu&eacute; en tal objeto. Sin embargo, no se consigui&oacute; que este control de permisos estuviera realmente unificado. En ocasiones, el control de permisos se constru&iacute;a en base a cada m&oacute;dulo/paquete, &oacute; incluso en base a cada p&acute;gina. De este modo, algunos m&oacute;dulos usaban "roles" y otros no. Otros m&oacute;dulos hac&iacute;an todo el control de acceso bas&aacute;ndose en simples reglas de c&oacute;digo.</para>

      <para>Los problemas resultantes de esto fueron sobre todo inconsistencias y c&oacute;digo redundante. De este modo, en OpenACS 4 se busca proporcionar un unificado y consistente sistema de permisos para que tanto programadores y administradores puedan usarlo de manera amigable.</para>

      <para>En OpenACS 4 la pregunta qui&eacute;n puede hacer qu&eacute; en tal objeto, el "qui&eacute;n" se responde a trav&eacute;s de la jerarqu&iacute;a de party (la generalizaci&oacute;n de personas, usuarios, miembros de un grupo, etc.), el "tal objeto" tambi&eacute;n se establece a trav&eacute;s de la jerarqu&iacute;a de objetos y por &uacute;ltimo el "qu&eacute;" se establece a trav&eacute; de una jerarqu&iacute;a de posibles acciones. Ahora iremos aclarando todos estos conceptos.</para>

      <para>En el coraz&oacute;n del sistema de permisos tenemos dos tablas: <computeroutput>acs_privileges</computeroutput> y <computeroutput>acs_permissions</computeroutput>:</para>

      <screen id="acs_privileges" xreflabel="acs_privileges">
  create table acs_privileges (
      privilege           varchar2(100) not null
          constraint acs_privileges_pk primary key,
      pretty_name         varchar2(100),
      pretty_plural       varchar2(100)
  );
      </screen>

      <screen id="acs_permissions" xreflabel="acs_permissions">
   create table acs_permissions (
      object_id
          not null
          constraint acs_permissions_on_what_id_fk references acs_objects (object_id),
      grantee_id
          not null
          constraint acs_permissions_grantee_id_fk references parties (party_id),
      privilege
          not null
          constraint acs_permissions_priv_fk references acs_privileges (privilege),
      constraint acs_permissions_pk
          primary key (object_id, grantee_id, privilege)
  );
      </screen>

      <para>La tabla <computeroutput>acs_privileges</computeroutput> almacena el propio nombre de los privilegios como <emphasis>leer</emphasis>, <emphasis>escribir</emphasis>, <emphasis>borrar</emphasis>, <emphasis>crear</emphasis> y <emphasis>administrar</emphasis>.</para>

      <para>La tabla <computeroutput>acs_permissions</computeroutput> responde a la ya famosa pregunta clave qui&eacute;n (<emphasis>grantee_id</emphasis>) puede hacer qu&eacute; (<emphasis>privilege</emphasis>) en tal objeto (<emphasis>object_id</emphasis>).</para>

      <para>Ahora vamos a profundizar c&oacute;mo funciona el sistema de permisos a trav&eacute;s de la jerarqu&iacute;a de objetos, de parties y de privilegios.</para>

    </sect3>

    <sect3>
      <title>Jerarqu&iacute;a de Objetos</title>

      <para>La tabla <computeroutput>acs_objects</computeroutput> se crea de la siguiente manera:</para>

      <screen id="acs_objects" xreflabel="acs_objects">
   create table acs_objects (
      object_id             integer
          not null
          constraint acs_objects_pk primary key,
      object_type
          not null
          constraint acs_objects_object_type_fk references acs_object_types (object_type),
      context_id
          constraint acs_objects_context_id_fk references acs_objects(object_id),
	  security_inherit_p  char(1) default 't'
          not null,
      constraint acs_objects_sec_inherit_p_ck
          check (security_inherit_p in ('t', 'f')),
      creation_user         integer,
      creation_date         date default sysdate not null,
      creation_ip           varchar2(50),
      last_modified         date default sysdate not null,
      modifying_user        integer,
      modifying_ip          varchar2(50),
      constraint acs_objects_context_object_un
          unique (context_id, object_id) disable
   );
	</screen>

      <para>De este modo, supong&aacute;mos que los objetos A, B, ..., F tienen la siguiente jerarqu&iacute;a:</para>

      <table>
	<title></title>
	<tgroup cols="3" colsep="1">
	  <colspec colname="c1"/>
	    <colspec colname="c2"/>
	    <colspec colname="c3"/>
	    <tbody>
	      <row>
		<entry namest="c1" nameend="c3"><para><emphasis>A</emphasis></para><para>object_id=10</para></entry>
	      </row>
	      <row>
		<entry namest="c1" nameend="c2"><para><emphasis>B</emphasis></para><para>object_id=20</para></entry>
		<entry><para><emphasis>C</emphasis></para><para>object_id=30</para></entry>
	      </row>
	      <row>
		<entry><para><emphasis>D</emphasis></para><para>object_id=40</para></entry>
		<entry><para><emphasis>E</emphasis></para><para>object_id=50</para></entry>
		<entry><para><emphasis>F</emphasis></para><para>object_id=60</para></entry>
	      </row>
	    </tbody>

	  </tgroup>

	</table>

	<para>Esto podr&iacute;a ser representado en <xref linkend="acs_objects"/> de la siguiente manera:</para>

	<table>
	  <title></title>
	  <tgroup cols="2">
	    <thead>
	      <row>
		<entry>object_id</entry>
		<entry>context_id</entry>
	      </row>
	    </thead>
	    <tbody>
	      <row>
		<entry>20</entry>
		<entry>10</entry>
	      </row>
	      <row>
		<entry>30</entry>
		<entry>10</entry>
	      </row>
	      <row>
		<entry>40</entry>
		<entry>20</entry>
	      </row>
	      <row>
		<entry>50</entry>
		<entry>20</entry>
	      </row>
	      <row>
		<entry>60</entry>
		<entry>30</entry>
	      </row>
	    </tbody>
	  </tgroup>
	</table>

	<para>As&iacute; se expresa que el objeto 20 es descendiente del objeto 10 y que el objeto 40 es descendiente del objeto 10, etc. Mediante una consulta <computeroutput>CONNECT BY</computeroutput> es posible computar que el objeto 40 es descendiente de segunda generaci&oacute;n del objeto 10. Con esto en mente si nosotros queremos grabar que Juan tiene permisos de lectura en los objetos A,...,F, solo necesitamos introducir el siguiente registro en la tabla <xref linkend="acs_permissions"/>.</para>

	<table>
	  <title>Instancia en acs_permissions</title>
	  <tgroup cols="3">
	    <thead>
	      <row>
		<entry>object</entry>
		<entry>grantee</entry>
		<entry>privilege</entry>
	      </row>
	    </thead>
	    <tbody>
	      <row>
		<entry>A</entry>
		<entry>Juan</entry>
		<entry>read</entry>
	      </row>
	    </tbody>
	  </tgroup>
	</table>

	<para>El hecho de Juan tambi&eacute;n puede leer B,C,...,F puede ser deducido determinando que estos objetos son hijos de A en la jerarqu&iacute;a de objetos. El coste computacional de estas consultas en la jerarqu&iacute;a es bastante costoso. Una manera de solucionar esto podr&iacute;a ser una delgada vista del &aacute;rbol de contexto como esto:</para>

	<table>
	  <title></title>
	  <tgroup cols="3">
	    <thead>
	      <row>
		<entry>object</entry>
		<entry>ancestor</entry>
		<entry>n_generations</entry>
	      </row>
	    </thead>
	    <tbody>
	      <row>
		<entry>A</entry>
		<entry>A</entry>
		<entry>0</entry>
	      </row>
	      <row>
		<entry>B</entry>
		<entry>B</entry>
		<entry>0</entry>
	      </row>
	      <row>
		<entry>C</entry>
		<entry>C</entry>
		<entry>0</entry>
	      </row>
	      <row>
		<entry>C</entry>
		<entry>A</entry>
		<entry>1</entry>
	      </row>
	      <row>
		<entry>D</entry>
		<entry>D</entry>
		<entry>0</entry>
	      </row>
	      <row>
		<entry>D</entry>
		<entry>B</entry>
		<entry>1</entry>
	      </row>
	      <row>
		<entry>D</entry>
		<entry>A</entry>
		<entry>2</entry>
	      </row>
	      <row>
		<entry>E</entry>
		<entry>E</entry>
		<entry>0</entry>
	      </row>
	      <row>
		<entry>E</entry>
		<entry>B</entry>
		<entry>1</entry>
	      </row>
	      <row>
		<entry>E</entry>
		<entry>A</entry>
		<entry>2</entry>
	      </row>
	      <row>
		<entry>F</entry>
		<entry>F</entry>
		<entry>0</entry>
	      </row>
	      <row>
		<entry>F</entry>
		<entry>C</entry>
		<entry>1</entry>
	      </row>
	      <row>
		<entry>F</entry>
		<entry>A</entry>
		<entry>2</entry>
	      </row>
	    </tbody>
	  </tgroup>
	</table>

	<para>La soluci&oacute;n de crear una vista tampoco es v&aacute;lida debido a que crece exponecialmente con respecto a la profundidad del &aacute;rbol de contexto, dando graves problemas de almacenamiento y mantenimiento.</para>

	<para>Finalmente, el &aacute;rbol de contexto es almacenado en la tabla <computeroutput>acs_object_context_index</computeroutput>:</para>

	<screen id="acs_object_context_index">
create table acs_object_context_index (
      object_id
          not null
          constraint acs_obj_context_idx_obj_id_fk references <xref linkend="acs_objects"/>(object_id),
      ancestor_id
          not null
          constraint acs_obj_context_idx_anc_id_fk references <xref linkend="acs_objects"/>(object_id),
	  n_generations    integer
          not null
          constraint acs_obj_context_idx_n_gen_ck check (n_generations >= 0),
      constraint acs_object_context_index_pk
          primary key (object_id, ancestor_id)
  ) organization index;

	</screen>

	<para>Esta tabla se sincroniza con <xref linkend="acs_objects"/> mediante triggers como este:</para>

	<screen>
create or replace trigger acs_objects_context_id_in_tr
after insert on acs_objects
for each row
begin
    insert into acs_object_context_index
     (object_id, ancestor_id, n_generations)
    values
     (:new.object_id, :new.object_id, 0);

    if :new.context_id is not null and :new.security_inherit_p = 't' then
      insert into acs_object_context_index
       (object_id, ancestor_id,
        n_generations)
      select
       :new.object_id as object_id, ancestor_id,
       n_generations + 1 as n_generations
      from acs_object_context_index
      where object_id = :new.context_id;
    elsif :new.object_id != 0 then
      -- 0 is the id of the security context root object
      insert into acs_object_context_index
       (object_id, ancestor_id, n_generations)
      values
       (:new.object_id, 0, 1);
    end if;
end;
	</screen>

	<para>Para finalizar con <xref linkend="acs_objects"/> tan solo decir que si configuramos un objeto con el campo <computeroutput>security_inherit_p</computeroutput> a 'f', de ese modo no hay herencia de permisos de unos objetos a otros.</para>
      </sect3>

      <sect3>
	<title>Jerarqu&iacute;a de Privilegios</title>

	<para>Los privilegios tambi&eacute;n son organizados jer&aacute;rquicamente. Adem&aacute;s de los cinco principales privilegios de sistema definidos en el ACS Kernel Data Model, los desarrolladores de aplicaciones pueden definir los suyos propios.</para>

	<para>Gracias al modelo de datos de OpenACS es sencillo para los desarrolladores administrar permisos. Por ejemplo, para darle a un usuario privilegios de lectura, escritura, creaci&oacute;n y borrado en un objeto, basta con darle a un usuario el privilegio administraci&oacute;n (<emphasis>admin</emphasis>)y los otros cuatro privilegios le ser&aacute;n dados autom&aacute;ticamente. Por ejemplo, la estructura de privilegios de los foros es la siguiente: </para>

	<table>
	  <title>Jerarqu&iacute;a de privilegios de los foros</title>
	  <tgroup cols="13">
	    <colspec colname='c1'/>
	    <colspec colname='c2'/>
	    <colspec colname='c3'/>
	    <colspec colname='c4'/>
	    <colspec colname='c5'/>
	    <colspec colname='c6'/>
	    <colspec colname='c7'/>
	    <colspec colname='c8'/>
	    <colspec colname='c9'/>
	    <colspec colname='c10'/>
	    <colspec colname='c11'/>
	    <colspec colname='c12'/>
	    <colspec colname='c13'/>
	    <tbody>
	      <row>
		<entry namest="c1" nameend="c13">admin</entry>
	      </row>
	      <row>
		<entry namest="c1" nameend="c3">create</entry>
		<entry namest="c4" nameend="c6">delete</entry>
		<entry namest="c7" nameend="c9">read</entry>
		<entry namest="c9" nameend="c11">write</entry>
		<entry namest="c12" nameend="c13" morerows="1">moderate forum</entry>
	      </row>
	      <row>
		<entry>create category</entry>
		<entry>create forum</entry>
		<entry>create message</entry>
		<entry>delete category</entry>
		<entry>delete forum</entry>
		<entry>delete message</entry>
		<entry>read category</entry>
		<entry>read forum</entry>
		<entry>read message</entry>
		<entry>write category</entry>
		<entry>write forum</entry>
		<entry>write message</entry>
	      </row>
	    </tbody>
	  </tgroup>
	</table>

	<para>Al igual que en la jerarqu&iacute;a de objetos, es bueno tener una representaci&oacute;n integrada de la estructura jer&aacute;rquica. Esto se consigue definiendo la siguiente vista:</para>

	<screen>
  create or replace view acs_privilege_descendant_map
  as
  select
    p1.privilege,
    p2.privilege as descendant
  from
    <xref linkend="acs_privileges"/> p1,
    <xref linkend="acs_privileges"/> p2,
  where
    p2.privilege in 
      (select
         child_privilege
       from
         <xref linkend="acs_privileges"/>
       start with
         privilege = p1.privilege
       connect by
         prior child_privilege = privilege
      )
    or p2.privilege = p1.privilege;

	</screen>

	<para>Como el n&uacute;mero esperado de privilegios en el sistema es razonablemente peque&ntilde;o una vista funciona bien.</para>

      </sect3>

      <sect3>
	<title>Jerarqu&iacute;a de parties</title>

	<para>Veamos ahora la tercera jerarqu&iacute;a que juega un papel importante en el sistema de permisos. El modelo de datos de parties es el siguiente:</para>

	<table>
	  <title></title>
	  <tgroup cols="2">
	    <colspec colname="c1"/>
	    <colspec colname="c2"/>
	    <tbody>
	      <row>
		<entry namest="c1" nameend="c2">parties</entry>
	      </row>
	      <row>
		<entry>persons</entry>
		<entry morerows="1">groups</entry>
	      </row>
	      <row>
		<entry>users</entry>
	      </row>
	    </tbody>
	  </tgroup>
	</table>

	<screen id="parties" xreflabel="parties">
  create table parties (
      party_id
          not null
          constraint parties_party_id_fk references acs_objects (object_id)
          constraint parties_party_id_pk primary key,
      email               varchar2(100)
          constraint parties_email_un unique,
      url                 varchar2(200)
  );
	</screen>

	<screen id="persons" xreflabel="persons">
  create table persons (
      person_id
          not null
          constraint persons_person_id_fk references parties (party_id)
          constraint persons_person_id_pk primary key,
      first_names          varchar2(100)
          not null,
      last_name            varchar2(100)
          not null
  );
	</screen>

	<screen id="users" xreflabel="users">
  create table users (
      user_id
          not null
          constraint users_user_id_fk references persons (person_id)
          constraint users_user_id_pk primary key,
      password        char(40),
      -- other attributes
  );
	</screen>

	<screen id="groups" xreflabel="groups">
  create table groups (
      group_id
          not null
          constraint groups_group_id_fk references parties (party_id)
          constraint groups_group_id_pk primary key,
      group_name           varchar2(100) not null
  );
	</screen>

	<para>Recuerda que el campo <computeroutput>grantee_id</computeroutput> de la tabla <xref linkend="acs_permissions"/> referencia a <computeroutput>parties.party_id</computeroutput>. Esto significa que tu puedes dar privilegios en un objeto a una party, persona, usuario, o grupo. Los grupos representan agregaciones de parties. En general, los grupos son una colecci&oacute;n de usuarios, aunque podemos tener colecciones de personas, grupos, parties, &oacute; cualquier mezcla entre unos y otros.</para>

	<para>Dado que el uso m&aacute;s com&uacute;n de los grupos es para hacer conjuntos de usuarios &iquest;c&oacute;mo construiremos los grupos?. Para entender esto debemos echar un r&aacute;pido vistazo a lo ya explicado en el <xref linkend="objetos-2"/> y recordar que la manera en que se relacionan objetos es mediante <computeroutput>acs_rels</computeroutput>. La relaci&oacute;n que utilizaremos ser&aacute; la de membres&iacute;a. Si tenemos un grupo llamado <emphasis>Papiroflexia</emphasis>, podemos asingnarle los miembros Pedro, Mar&iacute;a y Sara. El hecho de que estos usuarios son miembros de <emphasis>Papiroflexia</emphasis> ser&aacute; almacenada en las tablas <computeroutput>membership_rels</computeroutput> y <computeroutput>acs_rels</computeroutput>:</para>

	<screen id="acs_rels" xreflabel="acs_rels">
  create table acs_rels (
      rel_id
          not null
          constraint acs_rels_rel_id_fk references <xref linkend="acs_objects"/>(object_id)
          constraint acs_rels_pk primary key,
      rel_type
          not null
          constraint acs_rels_rel_type_fk references acs_rel_types (rel_type),
      object_id_one
          not null
          constraint acs_object_rels_one_fk references <xref linkend="acs_objects"/> (object_id),
      object_id_two
          not null
          constraint acs_object_rels_two_fk references <xref linkend="acs_objects"/> (object_id),
      constraint acs_object_rels_un
          unique (rel_type, object_id_one, object_id_two)
  );

	</screen>

	<screen id="membership_rels" xreflabel="membership_rels">
  create table membership_rels (
      rel_id
          constraint membership_rel_rel_id_fk references <xref linkend="acs_rels"/> (rel_id)
          constraint membership_rel_rel_id_pk primary key,
      -- null means waiting for admin approval
      member_state         varchar2(20)
          constraint membership_rel_mem_ck
           check (member_state in ('approved', 'banned', 'rejected', 'deleted'))
  );
	</screen>

	<para>Las entradas en la tabla <xref linkend="acs_rels"/> ser&iacute;an de la siguiente manera:</para>

	<table>
	  <title>Instancia en acs_rel</title>
	  <tgroup cols="3">
	    <thead>
	      <row>
		<entry>rel_type</entry>
		<entry>object_one</entry>
		<entry>object_two</entry>
	      </row>
	    </thead>
	    <tbody>
	      <row>
		<entry>membership_rel</entry>
		<entry>Papiroflexia</entry>
		<entry>Pedro</entry>
	      </row>
	      <row>
		<entry>membership_rel</entry>
		<entry>Papiroflexia</entry>
		<entry>Mar&iacute;a</entry>
	      </row>
	      <row>
		<entry>membership_rel</entry>
		<entry>Papiroflexia</entry>
		<entry>Sara</entry>
	      </row>
	    </tbody>
	  </tgroup>
	</table>

    <para>Otra manera de crear grupos es a&ntilde;adiendo subgrupos. Sup&oacute;n que nosotros definimos <emphasis>Papiroflexia China</emphasis> y <computeroutput>Papiroflexia Japonesa</computeroutput> como subgrupos de <emphasis>Papiroflexia</emphasis>. Esta informaci&oacute;n es guardada en las tablas <xref linkend="acs_rels"/> y <computeroutput>composition_rels</computeroutput>:</para>

    <screen id="composition_rels" xreflabel="composition_rels">
create table composition_rels (
    rel_id
	  constraint composition_rels_rel_id_fk references <xref linkend="acs_rels"/> (rel_id)
        constraint composition_rels_rel_id_pk primary key
);
    </screen>

    <para>Las entradas que nos interesan ser&iacute;an de la siguiente manera:</para>

    <table>
	  <title>Instancia en acs_rel</title>
	  <tgroup cols="3">
	    <thead>
	      <row>
		<entry>rel_type</entry>
		<entry>object_one</entry>
		<entry>object_two</entry>
	      </row>
	    </thead>
	    <tbody>
	      <row>
		<entry>composition_rel</entry>
		<entry>Papiroflexia</entry>
		<entry>Papiroflexia China</entry>
	      </row>
	      <row>
		<entry>composition_rel</entry>
		<entry>Papiroflexia</entry>
		<entry>Papiroflexia Japonesa</entry>
	      </row>
	    </tbody>
	  </tgroup>
    </table>

    <para>El significado de la relaci&oacute;n de composici&oacute;n implica que si a&ntilde;dimos a Marcos, Teresa y Pablo a <emphasis>Papiroflexia China</emphasis> tambi&eacute;n los a&ntilde;dimos a <emphasis>Papiroflexia</emphasis>. As&iacute;, el modo de determinar si un usuario es miembro de un grupo es similar al problema de determinar si un objeto a un determinado contexto en la jerarqu&iacute;a. La relaci&oacute;n de composici&oacute;n puede formar jerarqu&iacute;as entre los grupos debido a que esta es transitiva.</para>

    <para>De nuevo, las b&uacute;squedas jer&aacute;quicas son costosas. En este caso lo mejor es hacer una cache de resultados de consultas mediante una tabla mantenida por <emphasis>triggers</emphasis> de la misma manera que lo hicimos en la jerarqu&iacute;a de objetos y contextos. El modelo de datos de Open ACS 4.X define las siguientes tablas:</para>

    <screen id="group_component_index" xreflabel="group_component_index">
  create table group_component_index (
          group_id        not null
                          constraint group_comp_index_group_id_fk
	                  references <xref linkend="groups"/> (group_id),
          component_id    not null
                          constraint group_comp_index_comp_id_fk
	                  references <xref linkend="groups"/> (group_id),
          rel_id          not null
                          constraint group_comp_index_rel_id_fk
	                  references <xref linkend="composition_rels"/> (rel_id),
          container_id    not null
                          constraint group_comp_index_cont_id_ck
	                  references <xref linkend="groups"/> (group_id),
          constraint group_component_index_ck
          check (group_id != component_id),
          constraint group_component_index_pk
          primary key (group_id, component_id, rel_id)
  ) organization index;

    </screen>

    <screen id="group_member_index" xreflabel="group_member_index">
  create table group_member_index (
      group_id
          not null
          constraint group_member_index_grp_id_fk 
          references <xref linkend="groups"/> (group_id),
      member_id
          not null
          constraint group_member_index_mem_id_fk 
	  references <xref linkend="parties"/> (party_id),
      rel_id
          not null
          constraint group_member_index_rel_id_fk 
	  references <xref linkend="membership_rels"/> (rel_id),
      container_id
          not null
          constraint group_member_index_cont_id_fk 
	  references <xref linkend="groups"/> (group_id),
      constraint group_member_index_pk
          primary key (member_id, group_id, rel_id)
  ) organization index;
    </screen>
  
  </sect3>

</sect2>


