Personal tools
Update Security Settings: time consuming? Non per forza!

Update Security Settings, dietro le quinte!

Jun 04, 2012

Update Security Settings: time consuming? Non per forza!

Analisi di un processo tedioso per un manutentore di siti con molti oggetti

Nella mia carriera di tartaruga plonista, mi è capitato diverse volte di lavorare con siti di dimensioni notevoli. Quello a cui più sono affezionato a oggi conta quasi 400K oggetti in catalogo.

Quando il cliente ti chiede di cambiare una piccola impostazione di sicurezza in un workflow associato a 600 o 700 oggetti, tu plonista programmatore e manutentore sai che la to-do list sarà:

  1. Cambiare il workflow associato all'oggetto;
  2. Aggiornare il prodotto nell'installazione e ricaricare il profilo legato ai workflow;
  3. Premere in ZMI Update Security Settings e, se il sito è molto grosso, andare a casa! Ci si pensa la mattina seguente... (se la notte è stata sufficientemente lunga)

Ma non si può proprio fare meglio?

Si! Si può e ora vediamo come.

 

Quando cambiamo un workflow in plone, è necessario rendere noto ai vari oggetti coinvolti che il loro workflow è cambiato; è necessario aggiornarne il mapping della security. Il modo per farlo in plone è quello di eseguire l'Update security settings dalla ZMI, nel portal_workflow. Questa operazione in siti di grosse dimensioni  è abbastanza lunga.

Cosa succede quando si invoca questo procedura?

Il bottone in ZMI rimanda al metodo updateRoleMappings in Products.CMFCore nel WorkflowTool:
446     security.declareProtected(ManagePortal, 'updateRoleMappings')
447     @postonly
448     def updateRoleMappings(self, REQUEST=None):
449         """ Allow workflows to update the role-permission mappings.
450         """
451         wfs = {}
452         for id in self.objectIds():
453             wf = self.getWorkflowById(id)
454             if hasattr(aq_base(wf), 'updateRoleMappingsFor'):
455                 wfs[id] = wf
456         portal = aq_parent(aq_inner(self))
457         count = self._recursiveUpdateRoleMappings(portal, wfs)
458         if REQUEST is not None:
459             return self.manage_selectWorkflows(REQUEST, manage_tabs_message=
460                                                '%d object(s) updated.' % count)
461         else:
462             return count

Da qui come vediamo si richiama il metodo _recursiveUpdateRoleMappings passando gli oggetti portale e i workflow collegati; cosa succede all'interno di questo metodo?

603         .......
604         if hasattr(aq_base(ob), 'objectItems'):
605             obs = ob.objectItems()
606             if obs:
607                 for k, v in obs:
608                     changed = getattr(v, '_p_changed', 0)
609                     count = count + self._recursiveUpdateRoleMappings(v, wfs)
610                     if changed is None:
611                         # Re-ghostify.
612                         v._p_deactivate()
613         return count

Al suo interno, il metodo, aggiorna la security dell'oggetto e, alla fine, chiama ricorsivamente se stesso su tutti gli oggetti che possono contenere altri oggetti. Si tratta di un algoritmo di visita ricorsiva che visita ogni oggetto del portale!

Questa non può certo essere una procedura veloce quando abbiamo un portale con un grande numero di oggetti. Si pensi poi che fra gli oggetti presi in analisi c'è di tutto:

  • front-page
  • news
  • syndication_information
  • crit__Type_ATPortalTypeCriterion
  • events
  • aggregator
  • crit__review_state_ATSimpleStringCriterion
  • crit__start_ATFriendlyDateCriteria
  • Members

Se quello che vogliamo fare è cambiare il workflow a un tipo di oggetto che conta qualche decina di istanziazioni, questo approccio sembra oltre modo dispendioso.

Quindi... che fare?

Prima di tutto è necessario aver bene in mente cosa si deve fare. I casi possono essere due:

  1. Voglio cambiare il tipo di workflow a un oggetto;
  2. Voglio cambiare una serie di permessi, e quindi cambiare mapping, a un workflow già associato a un tipo di oggetto;
Nel primo caso, Plone ti è amico:  nel pannello di controllo, all'indirizzo nome-del-sito/@@types-controlpanel si possono impostare alcune proprietà per i vari tipi di oggetto, fra cui il workflow:
Cambio workflow
Cambio di workflow di un oggetto da pannello di controllo

Qui possiamo scegliere che workflow associare ad un certo portal type, e Plone stesso ci viene incontro in un'operazione delicata come può essere qualla di rimappare gli stati correnti su quelli che prevede il workflow che andiamo ad assegnare.

Nel secondo caso, dopo aver modificato i permessi di un workflow, abbiamo necessità di aggiornare tutte le istanze di quel tipo di oggetto, ma come fare? Beh, in questo caso è grep a essere tuo amico.

Ogni volta che si sviluppa con Plone, il modo più semplice per fare qualcosa è chiedersi e capire come fa Plone a farlo. Il caso 1, visto poco sopra, cambia completamente workflow a un oggetto e per terminare, dovrà applicare l'update della sicurezza agli oggetti. Il caso 2 sembra essere un subset di operazioni del caso 1.

Se andiamo a vedere in plone/app/controlpanel/types.py, nel codice utilizzato dal pannello di controllo di plone, vediamo che, dopo aver fatto tutta una serie di operazioni riguardanti il cambio di stati fra il vecchio e il nuovo workflow, quello che viene fatto è chiamare:

remap_workflow(context, 
type_ids=type_ids,
chain=chain,
state_map=state_map)

Ci siamo quasi! remap_workflow è un metodo in plone/app/workflow/remap.py. Fa un sacco di cose, ed è normale. Non dimentichiamo che deve essere utilizzato da una procedura che cambia completamente il workflow di un'oggetto da un tipo a un altro.

Scorrendo il codice possiamo vedere che ad un certo punto sul nuovo workflow assegnato all'oggetto viene richiamato il metodo updateRoleMappingsFor, che viene utilizzato anche quando si fa l'Update Security settings dalla ZMI.

Ok... Abbiamo trovato il path da seguire!!

Bene, testiamolo!

Consideriamo l'oggetto front-page e il suo workflow, il simple_pubblication_workflow. Nello stato privato, il simple_pubblication_workflow dice che il permesso di View ce l'hanno Contributor, Editor, Manager, Owner, Reader e Site Administrator.

Simple pubblication workflow private state

Security mapping dello stato privato del simple pubblication workflow

Infatti se guardiamo in ZMI in nome-sito/front-page/manage_access, abbiamo:

Manage access front page

Security matrix di default applicata all'oggetto front-page

Modifichiamo il workflow facendo in modo che nello stato privato nessuno di questi ruoli sia direttamente associato al permesso di View, ma si usi l'Acquire permission settings.

Dopodiché possiamo procedere direttamente tramite pdb:

(STX Next pdb) self.context
<ATDocument at /test/front-page>
(STX Next pdb) wf = self.context.portal_workflow.getWorkflowsFor(self.context.portal_type)
(STX Next pdb) wf
[<DCWorkflowDefinition at /test/portal_workflow/simple_publication_workflow>]
(STX Next pdb) wf[0].updateRoleMappingsFor(self.context)
1
(STX Next pdb) from transaction import commit
(STX Next pdb) commit()
None

A questo punto possiamo tornare a vedere direttamente in ZMI cos'è successo. Se andiamo in nome-sito/front-page/manage_access, possiamo vedere che il permesso di View, ora rispecchia fedelmente le modifiche che abbiamo applicato al workflow:

Manage access front page modificato

Security matrix di default applicata all'oggetto front-page

Finito?

Non proprio, se leggiamo il codice utilizzato da plone, possiamo vedere che alla fine del metodo viene eseguito anche un:

   obj.reindexObject(idxs=['allowedRolesAndUsers', 'review_state'])

Guardando il catalog, vediamo che l'indice allowedRolesAndUsers associato al documento front-page contiene questi valori:

['Contributor', 'Editor', 'Manager', 'Reader', 'Site Administrator', '_View_Permission', 'user:admin']

Noi non abbiamo cambiato il workflow o gli stati dell'oggetto, solo la sua security, per cui un semplice:

 obj.reindexObject(idxs=['allowedRolesAndUser']) 

sarà sufficiente per sistemare la situazione; e infatti ora in catalogo abbiamo l'indice associato all'unico valore ['Anonymous'].

Finalmente abbiamo tutti gli elementi che ci servono. A questo punto non resta che fare un piccolo script che cerca tutti i brain di un certo tipo in catalogo. Così fare l'update della sicurezza è un'attività semplice e molto più veloce rispetto all'Update security settings fatto dal portal_workflow in ZMI.

Chiaramente, se si modifica un workflow associato a qualche centinaio di migliaia di oggetti... beh... ci aggiorniamo domattina! : )

 

Filed under: , , ,
comments powered by Disqus