mirror of
https://github.com/efrainsosa27/cotizador-helgen.git
synced 2026-10-24 05:16:48 +00:00
Cobranza: restringir acceso con la API de Drive (DriveApp.setSharing da Access denied)
En helgen.tech DriveApp.setSharing falla con "Access denied: DriveApp." aunque el script es dueño, con Permission.NONE y con VIEW. privado_() ahora lista los permisos con la API REST de Drive y borra los de tipo domain/anyone, con el token del script (sin permisos nuevos). Si no puede, lanza error. Simulacro de la API en las pruebas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7e3256415a
commit
bfe4661968
@ -385,7 +385,9 @@ Cobranza: `ROL_COBRANZA` (correos separados por coma: efrain@, tania@, leo@), `A
|
||||
con "helgen.tech: lector" aunque el código no compartía nada). Por eso todo lo de cobranza (carpeta, libro, subcarpetas de
|
||||
año y proyecto, comprobantes) pasa por `privado_()`, que lo deja en "Restringido" y conserva a las personas agregadas una por
|
||||
una. `prepararCobranza()` repara carpeta y libro si alguien los vuelve a compartir. Si se crean archivos en otra área privada,
|
||||
hacer lo mismo.
|
||||
hacer lo mismo. **`DriveApp.setSharing` no sirve aquí** (da "Access denied: DriveApp." aun siendo dueño, con `Permission.NONE`
|
||||
o `VIEW`): `privado_()` borra los permisos `domain`/`anyone` con la API REST de Drive (`UrlFetchApp` + `ScriptApp.getOAuthToken()`,
|
||||
sin permisos nuevos).
|
||||
- **Permisos en el servidor:** `ROL_COBRANZA` (Script Properties, no en el código) para ver y cambiar todo lo de cobranza;
|
||||
`ADMIN_DESBLOQUEO` solo para desbloquear. Los demás reciben `codigo: "PERMISO"` y `getDatosIniciales` no les manda nada de
|
||||
cobranza (solo `permisos` en falso para ocultar la pestaña).
|
||||
|
||||
21
Code.js
21
Code.js
@ -1050,12 +1050,23 @@ function exigirCobranza_() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deja un archivo o carpeta de cobranza en "Restringido": quita el acceso por enlace o de todo el dominio y conserva a
|
||||
* Deja un archivo o carpeta de cobranza en "Restringido": quita el acceso de todo el dominio o por enlace y conserva a
|
||||
* las personas agregadas una por una (tania@, leo@). Workspace comparte con todo helgen.tech lo que se crea (§6.11).
|
||||
* Usa la API de Drive con el token del script: DriveApp.setSharing da "Access denied: DriveApp." en helgen.tech
|
||||
* (20/10/2026). No pide permisos nuevos (el de Drive ya lo da DriveApp). Si no puede, lanza error: nada queda abierto.
|
||||
*/
|
||||
function privado_(item) {
|
||||
// Permission.NONE no está soportado (da "Access denied: DriveApp"); con PRIVATE el permiso se ignora.
|
||||
if (item.getSharingAccess() !== DriveApp.Access.PRIVATE) item.setSharing(DriveApp.Access.PRIVATE, DriveApp.Permission.VIEW);
|
||||
const url = 'https://www.googleapis.com/drive/v3/files/' + encodeURIComponent(item.getId()) + '/permissions';
|
||||
const opciones = { headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() }, muteHttpExceptions: true };
|
||||
const r = UrlFetchApp.fetch(url + '?supportsAllDrives=true&fields=permissions(id,type)', opciones);
|
||||
if (r.getResponseCode() !== 200) throw new Error('No se pudo revisar el acceso en Drive (' + r.getResponseCode() + '): ' + r.getContentText());
|
||||
(JSON.parse(r.getContentText()).permissions || [])
|
||||
.filter(p => p.type === 'domain' || p.type === 'anyone')
|
||||
.forEach(p => {
|
||||
const d = UrlFetchApp.fetch(url + '/' + encodeURIComponent(p.id) + '?supportsAllDrives=true',
|
||||
Object.assign({ method: 'delete' }, opciones));
|
||||
if (d.getResponseCode() >= 300) throw new Error('No se pudo restringir el acceso en Drive (' + d.getResponseCode() + '): ' + d.getContentText());
|
||||
});
|
||||
return item;
|
||||
}
|
||||
|
||||
@ -1528,8 +1539,8 @@ function prepararCobranza() {
|
||||
const libro = libroCobranza_();
|
||||
const archivoLibro = privado_(DriveApp.getFileById(libro.getId()));
|
||||
const carpeta = carpetaCobranza_();
|
||||
Logger.log('Carpeta de cobranza: ' + carpeta.getUrl() + ' (acceso: ' + carpeta.getSharingAccess() + ')');
|
||||
Logger.log('Libro de cobranza: ' + libro.getUrl() + ' (acceso: ' + archivoLibro.getSharingAccess() + ')');
|
||||
Logger.log('Carpeta de cobranza: ' + carpeta.getUrl() + ' (restringida)');
|
||||
Logger.log('Libro de cobranza: ' + archivoLibro.getUrl() + ' (restringido)');
|
||||
Logger.log('ROL_COBRANZA: ' + (PropertiesService.getScriptProperties().getProperty('ROL_COBRANZA') || '(vacío: nadie tiene acceso)'));
|
||||
}
|
||||
|
||||
|
||||
@ -35,7 +35,15 @@ function libro(id,nombre,pestanas){const L={id,nombre,pest:pestanas||{},getId:()
|
||||
// ---- Drive simulado ----
|
||||
let nId=0; const carpetas={}, archivos={};
|
||||
// Como Workspace de helgen.tech: todo lo nuevo nace compartido con el dominio (acceso 'DOMAIN')
|
||||
const compartible=o=>Object.assign(o,{acceso:'DOMAIN',getSharingAccess:()=>o.acceso,setSharing:(a,p)=>{if(p==='NONE')throw new Error('Access denied: DriveApp.');o.acceso=a;o.permiso=p}}); // NONE no está soportado en Apps Script
|
||||
// DriveApp.setSharing da "Access denied" en helgen.tech: el código usa la API de Drive (permissions) con el token del script
|
||||
const compartible=o=>Object.assign(o,{acceso:'DOMAIN',setSharing:()=>{throw new Error('Access denied: DriveApp.')}});
|
||||
function apiDrive(url,op){const m=url.match(/drive\/v3\/files\/([^/?]+)\/permissions(?:\/([^?]+))?/);
|
||||
const it=carpetas[decodeURIComponent(m[1])]||archivos[decodeURIComponent(m[1])];
|
||||
assert(op&&op.headers&&op.headers.Authorization==='Bearer TOKEN-SCRIPT','API de Drive con el token del script');
|
||||
const resp=(c,b)=>({getResponseCode:()=>c,getContentText:()=>JSON.stringify(b||{})});
|
||||
if(!it)return resp(404);
|
||||
if(op.method==='delete'){assert.equal(m[2],'dom');it.acceso='PRIVATE';return resp(204)}
|
||||
return resp(200,{permissions:[{id:'own',type:'user'}].concat(it.acceso==='DOMAIN'?[{id:'dom',type:'domain'}]:[])})}
|
||||
function carpeta(nombre,padre){const f=compartible({id:'F'+(++nId),nombre,padre,hijos:[],archivos:[],trashed:false}); Object.assign(f,{
|
||||
getId:()=>f.id,getName:()=>f.nombre,getUrl:()=>'https://drive/'+f.id,isTrashed:()=>f.trashed,
|
||||
getFoldersByName:n=>{const l=f.hijos.filter(h=>h.nombre===n);let i=0;return{hasNext:()=>i<l.length,next:()=>l[i++]}},
|
||||
@ -69,7 +77,8 @@ const ctx={console:{...console,warn(){}},Date,JSON,
|
||||
Utilities:{formatDate:fmt,newBlob:(a,b,c)=>typeof a==='string'?{getAs:()=>({setName:n=>({name:n,html:a})})}:{bytes:a,mime:b,name:c},
|
||||
base64Encode:b=>Buffer.from(b).toString('base64'),base64Decode:s=>firmados(Buffer.from(s,'base64')),
|
||||
DigestAlgorithm:{SHA_256:'sha256'},computeDigest:(a,s)=>[...require('crypto').createHash('sha256').update(s).digest()]},
|
||||
UrlFetchApp:{fetch:url=>{const t=decodeURIComponent(url.split('id_token=')[1]);const c=tokens[t];
|
||||
ScriptApp:{getOAuthToken:()=>'TOKEN-SCRIPT'},
|
||||
UrlFetchApp:{fetch:(url,op)=>{if(/googleapis\.com\/drive\//.test(url))return apiDrive(url,op);const t=decodeURIComponent(url.split('id_token=')[1]);const c=tokens[t];
|
||||
return{getResponseCode:()=>c?200:400,getContentText:()=>JSON.stringify(c||{error:'invalid_token'})}}},
|
||||
ContentService:{MimeType:{JSON:'json'},createTextOutput:t=>({t,setMimeType(){return this}})},
|
||||
SpreadsheetApp:{flush(){},openById:id=>{if(!libros[id])throw new Error('No encontrado');return libros[id]},
|
||||
@ -78,8 +87,7 @@ const ctx={console:{...console,warn(){}},Date,JSON,
|
||||
DriveApp:{getFolderById:id=>{if(!carpetas[id])throw new Error('No se encontró');return carpetas[id]},
|
||||
createFolder:n=>miUnidad.createFolder(n),getRootFolder:()=>miUnidad,
|
||||
getFileById:id=>({getParents:()=>{let d=false;return{hasNext:()=>!d,next:()=>{d=true;return archivos[id].padre}}},
|
||||
moveTo:f=>{archivos[id].padre=f},getSharingAccess:()=>archivos[id].getSharingAccess(),
|
||||
setSharing:(a,p)=>archivos[id].setSharing(a,p),getId:()=>id}),
|
||||
moveTo:f=>{archivos[id].padre=f},setSharing:()=>archivos[id].setSharing(),getId:()=>id,getUrl:()=>'https://drive/file/'+id}),
|
||||
Access:{PRIVATE:'PRIVATE',DOMAIN:'DOMAIN'},Permission:{NONE:'NONE',VIEW:'VIEW'}},
|
||||
};
|
||||
vm.createContext(ctx); vm.runInContext(src,ctx);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user