API de acceso a proyectos
Cada proyecto almacena su propio modo de acceso — public o private — como una columna del propio
proyecto; ya no se infiere a partir de los datos de membresía. public (el modo por defecto con el que
empieza todo proyecto) significa que cualquier miembro activo de la organización puede alcanzarlo, salvo
una exclusión individual revoked. private significa que solo los miembros con una fila granted — más
el bypass de propietario/administrador de la organización — pueden alcanzarlo, y esa lista de acceso puede
legítimamente estar vacía: un proyecto privado con cero miembros otorgados sigue siendo privado, no
retrocede a público. Este recurso es dedicado: el estado de acceso nunca se agrega al objeto general
del proyecto que devuelve GET /api/v1/projects/{projectId} — esa forma permanece sin cambios.
{projectId} es el código del proyecto (por ejemplo ACME) en todo lo siguiente.
El propietario de un proyecto se asigna al crearlo: el usuario que lo crea queda como
propietario. Un proyecto creado con un token de API no tiene usuario a quien atribuirlo, así que
nace sin asignar (ownerUserUlid es null) y necesita un PATCH explícito para tener
propietario.
Obtener el estado de acceso
Sección titulada «Obtener el estado de acceso»/api/v1/projects/{projectId}/accessRespuesta
Sección titulada «Respuesta»200 con:
| Campo | Notas |
|---|---|
mode | El modo de acceso "public" o "private" almacenado del proyecto |
ownerUserUlid | El ULID del propietario del proyecto, o null cuando no está asignado (“Sin asignar”) |
grantedCount | Tamaño de la lista de acceso. No es la señal del modo — un proyecto privado PUEDE reportar grantedCount: 0; nunca infieras el modo a partir de este número en ningún sentido. |
{ "mode": "private", "ownerUserUlid": "01J...OWNER", "grantedCount": 2}Cambiar el estado de acceso
Sección titulada «Cambiar el estado de acceso»/api/v1/projects/{projectId}/accessAcepta uno o ambos de:
| Campo | Notas |
|---|---|
mode | "public" o "private" — alterna el modo de acceso |
ownerUserUlid | ULID del nuevo propietario, o null para dejarlo sin asignar |
Se requiere al menos un campo; un cuerpo vacío devuelve 422 validation_failed.
Autorización
Sección titulada «Autorización»Cambiar el estado de acceso es una pregunta distinta a la de leer un proyecto. Solo un propietario u
administrador de la organización, o el propietario actual del proyecto, pueden invocar este endpoint
con éxito — un miembro que solo tiene una fila granted (de lectura) en un proyecto privado no está
automáticamente autorizado a alternar el modo, y recibe 403 forbidden.
Transferir la propiedad sigue la misma regla: quien llama debe ser propietario/administrador de la organización o el propietario actual. El nuevo propietario debe ser un miembro activo de la organización.
Alternar a privado
Sección titulada «Alternar a privado»Cambiar un proyecto de público a privado crea exactamente dos filas granted: el propietario del
proyecto y el usuario que realiza la acción — una sola fila si son la misma persona, y solo la del
actor si el proyecto aún no tiene propietario. Cualquier otro miembro de la organización pierde el acceso
en su siguiente solicitud (salvo que su propio rol de organización tenga bypass de acceso a nivel de
proyecto — ver abajo).
Alternar a público
Sección titulada «Alternar a público»Cambiar un proyecto de privado a público descarta toda la lista de acceso. Si el proyecto vuelve a hacerse privado más tarde, empieza desde cero y vuelve a sembrar solo al propietario y al actor — los accesos anteriores no se restauran.
Lista de acceso de miembros
Sección titulada «Lista de acceso de miembros»Una vez que un proyecto es private, se pueden agregar o quitar miembros individuales de su lista de
acceso sin cambiar nunca el modo del proyecto. Solo PATCH /access { mode } cambia el modo, en cualquier
dirección.
Listar los miembros con acceso
Sección titulada «Listar los miembros con acceso»/api/v1/projects/{projectId}/access/membersPaginado (?page=/?pageSize=, ambos opcionales). Devuelve 200 con cada miembro activo de la
organización que tiene una fila granted para el proyecto o que lo alcanza mediante el bypass de rol
owner/admin de la organización. En un proyecto público devuelve una página vacía (items: [],
total: 0) — nunca responde 404.
{ "items": [ { "userUlid": "01J...MEMBER", "email": "ada@example.com", "firstName": "Ada", "lastName": "Lovelace", "avatarKey": null, "avatarVersion": 0, "role": "member", "positionTitle": "QA Lead" } ], "page": 1, "pageSize": 50, "total": 1}Cada elemento no lleva campo de procedencia de acceso: no puede indicar por qué aparece un miembro (un
otorgamiento directo o el bypass de rol de organización). Combina esta respuesta con GET /access
(grantedCount) y la lista de miembros de la organización para construir una vista de auditoría o
cumplimiento que necesite esa distinción.
Otorgar acceso a un miembro
Sección titulada «Otorgar acceso a un miembro»/api/v1/projects/{projectId}/access/members{ "userUlid": "01J...MEMBER" }Devuelve 201 con el miembro otorgado (misma forma que un elemento de la lista). Requiere el permiso
de gestión de proyectos (que tienen owner/admin), o el propietario actual del proyecto — tener una
fila granted por sí sola no autoriza a otorgar acceso a otros. Se rechaza con 409 conflict en un proyecto público, y con
404 not_found para un miembro que ya no está activo (se fue de la organización). Volver a otorgar
acceso a un miembro previamente revocado sobrescribe esa exclusión voluntaria.
Revocar el acceso de un miembro
Sección titulada «Revocar el acceso de un miembro»/api/v1/projects/{projectId}/access/members/{userUlid}Devuelve 204 si tiene éxito. Misma regla de autorización que otorgar acceso. Se rechaza con
409 conflict en un proyecto público. Revocar la última fila granted restante ahora tiene éxito: el
proyecto permanece private con la lista de acceso vacía — no se publica. Publicar un proyecto conserva
exactamente un punto de entrada, PATCH /access { mode: "public" }. Revocar nunca escribe una fila
revoked; elimina la fila granted directamente.
Lista de acceso de grupos
Sección titulada «Lista de acceso de grupos»Un grupo de usuarios también puede asignarse a la lista de acceso de un proyecto, otorgando acceso a todos los miembros actuales y futuros del grupo en una sola operación — un mecanismo separado y de grano más grueso, junto a la lista de acceso individual de arriba.
Listar los grupos asignados
Sección titulada «Listar los grupos asignados»/api/v1/projects/{projectId}/access/groupsBasado en páginas (?page=/?pageSize=, ambos opcionales). Refleja el plano, la protección y la
forma de los elementos de /access/members. A diferencia de la subcolección de miembros, este
endpoint de listado nunca da 404 en un proyecto público — una asignación dormida es un estado
válido y visible en cualquier modo del proyecto.
{ "items": [{ "ulid": "01J...GROUP", "name": "QA Squad", "description": null, "userCount": 4 }], "page": 1, "pageSize": 50, "total": 1}Asignar un grupo
Sección titulada «Asignar un grupo»/api/v1/projects/{projectId}/access/groups{ "userGroupUlid": "01J...GROUP" }Devuelve 201 con el grupo asignado (misma forma que un elemento del listado). Misma regla de
autorización que la familia individual: owner/admin de la organización, o el
propietario actual del proyecto. A diferencia de otorgar acceso a un miembro, esto tiene éxito
en un proyecto público — access_mode es un dato almacenado, nunca inferido de una asignación,
así que el otorgamiento queda dormido hasta que el proyecto se privatiza y sobrevive a cualquier
cambio de modo posterior en cualquier dirección. Volver a asignar un grupo ya asignado es una
operación idempotente sin efecto (201 de nuevo, sin duplicar la fila).
Desasignar un grupo
Sección titulada «Desasignar un grupo»/api/v1/projects/{projectId}/access/groups/{userGroupUlid}Devuelve 204 si tiene éxito. Misma regla de autorización que asignar. Nunca 409, ni siquiera
para el último grupo restante — desasignar el último grupo no vuelve a publicar el proyecto;
publicar conserva exactamente un punto de entrada, PATCH /access { mode: "public" }. Devuelve
404 not_found para un ULID de grupo desconocido, un grupo de otra organización, o un grupo que
nunca fue asignado.
La vista inversa
Sección titulada «La vista inversa»GET /api/v1/groups/{userGroupUlid}/projects lista los proyectos a los que está asignado un
grupo dado — el reflejo centrado en el grupo del listado de arriba. Está abierto a lectura para
cualquier miembro de la organización (como los demás GET de
grupos de usuarios), así que, a diferencia del projectCount
del propio grupo, filtra cualquier proyecto que el miembro que llama no pueda descubrir de otra
forma: un proyecto privado al que el grupo está asignado se omite silenciosamente para ese
llamante, mientras que un owner/admin de la organización ve todas las asignaciones.
Ver Listar proyectos asignados para
la forma completa.
Sin revocación a nivel de grupo
Sección titulada «Sin revocación a nivel de grupo»No existe un estado revoked a nivel de grupo. La fila revoked propia de la lista de acceso
individual de un miembro — establecida al eliminarlo o mediante una revocación explícita —
siempre prevalece sobre un otorgamiento por grupo al que ese miembro pudiera acceder de otra
forma: un grupo otorga acceso, nunca anula el veto individual.
Bypass de propietario/administrador de la organización
Sección titulada «Bypass de propietario/administrador de la organización»Quien llama con rol de organización owner o admin siempre alcanza todos los proyectos de esa
organización, sean privados o no, y siempre está autorizado a alternar el modo o transferir la propiedad.
La aplicación sigue a los datos, no a la forma de la URL
Sección titulada «La aplicación sigue a los datos, no a la forma de la URL»El control de acceso no se limita a la familia de rutas /projects/{projectId}/*. Alcanzar el contenido de
un proyecto de forma transitiva a través de un ULID de entidad — un caso de prueba, una ejecución, un
defecto, un plan, un hito, un entorno, una configuración o un comentario — produce la misma decisión de
acceso que dirigirse al proyecto directamente. Un miembro sin acceso otorgado en un proyecto privado recibe
403 por cualquiera de las dos vías; un miembro con acceso otorgado, y un owner/admin de la
organización, alcanzan ambas de la misma forma.
Ejemplo
Sección titulada «Ejemplo»curl -sS -X PATCH \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"mode":"private"}' \ "https://app.probara.net/api/v1/projects/ACME/access"Limitaciones conocidas
Sección titulada «Limitaciones conocidas»Son huecos intencionales y documentados, no descuidos:
- Los tokens de API no se evalúan contra la lista de acceso. Una solicitud autenticada con un token Bearer no lleva identidad por usuario para este propósito y sigue alcanzando todos los proyectos de su organización, sean privados o no — “privado” aplica solo a quienes llaman como humanos (sesión/OAuth).
- La interfaz web cubre el modo, el propietario y los miembros individuales. La pestaña Control de acceso en la configuración del proyecto muestra el modo almacenado, permite a quien esté autorizado alternar público/privado, transferir la propiedad, y otorgar/revocar miembros individuales desde la lista de miembros individuales. Los grupos todavía no están disponibles — la pestaña Grupos es un diseño terminado sin funcionalidad; el acceso por grupos queda diferido a una versión futura.