2009-12-07 11 views
6

Tengo una aplicación web basada en Pylons que se conecta a través de Sqlalchemy (v0.5) a una base de datos de Postgres. Por seguridad, en lugar de seguir el patrón típico de aplicaciones web simples (como se ve en casi todos los tutoriales), no estoy usando un usuario genérico de Postgres (por ejemplo, "aplicación web") pero estoy requiriendo que los usuarios ingresen su propio ID de usuario y contraseña de Postgres. y lo estoy usando para establecer la conexión. Eso significa que obtenemos todos los beneficios de la seguridad de Postgres.Con sqlalchemy cómo enlazar dinámicamente al motor de base de datos bajo pedido

Para complicar las cosas aún más, hay dos bases de datos separadas a conectar. Aunque actualmente están en el mismo clúster de Postgres, necesitan poder moverse a hosts separados en una fecha posterior.

Estamos usando el paquete declarative de sqlalchemy, aunque no veo que esto tenga relación con el asunto.

La mayoría de los ejemplos de sqlalchemy muestran enfoques triviales como la configuración de los metadatos una vez, al inicio de la aplicación, con un ID de usuario y una contraseña de base de datos genéricos, que se utiliza a través de la aplicación web. Esto generalmente se hace con Metadata.bind = create_engine(), a veces incluso a nivel de módulo en los archivos del modelo de la base de datos.

Mi pregunta es, ¿cómo podemos diferir el establecimiento de las conexiones hasta que el usuario haya iniciado sesión, y luego (por supuesto) reutilizar esas conexiones, o restablecerlas con las mismas credenciales, para cada solicitud posterior.

Tenemos este trabajo, creemos, pero no estoy solo seguro de su seguridad, también creo que se ve increíblemente pesado para la situación. Método del BaseController recuperamos el ID de usuario y la contraseña de la sesión web, llamamos sqlalchemy create_engine() una vez para cada base de datos, luego llamamos a una rutina que llama Session.bind_mapper() repetidamente, una vez para cada tabla que puede se hace referencia en cada una de esas conexiones, aunque cualquier solicitud dada normalmente hace referencia solo a una o dos tablas. Se ve algo como esto:

# in lib/base.py on the BaseController class 
def __call__(self, environ, start_response): 

    # note: web session contains {'username': XXX, 'password': YYY} 
    url1 = 'postgres://%(username)s:%(password)[email protected]/finance' % session 
    url2 = 'postgres://%(username)s:%(password)[email protected]/staff' % session 

    finance = create_engine(url1) 
    staff = create_engine(url2) 
    db_configure(staff, finance) # see below 
    ... etc 

# in another file 

Session = scoped_session(sessionmaker()) 

def db_configure(staff, finance): 
    s = Session() 

    from db.finance import Employee, Customer, Invoice 
    for c in [ 
     Employee, 
     Customer, 
     Invoice, 
     ]: 
     s.bind_mapper(c, finance) 

    from db.staff import Project, Hour 
    for c in [ 
     Project, 
     Hour, 
     ]: 
     s.bind_mapper(c, staff) 

    s.close() # prevents leaking connections between sessions? 

Así que la create_engine (llamadas) se producen en cada petición ... Puedo ver que se necesita, y el conjunto de conexiones probablemente los cachés y hace cosas con sensatez.

Pero llamar Session.bind_mapper() una vez para cada mesa, en cada solicitud? Parece que tiene que haber una mejor manera.

Obviamente, dado que un deseo de seguridad sólida es la base de todo esto, no queremos ninguna posibilidad de que una conexión establecida para un usuario de alta seguridad se use inadvertidamente en una solicitud posterior por un usuario de baja seguridad.

Respuesta

3

La vinculación de objetos globales (correlacionadores, metadatos) a la conexión específica del usuario no es buena. Además de usar la sesión con alcance. Sugiero crear una nueva sesión para cada solicitud y configurarla para usar conexiones específicas del usuario. El siguiente ejemplo supone que utiliza objetos de metadatos separados para cada base de datos:

binds = {} 

finance_engine = create_engine(url1) 
binds.update(dict.fromkeys(finance_metadata.sorted_tables, finance_engine)) 
# The following line is required when mappings to joint tables are used (e.g. 
# in joint table inheritance) due to bug (or misfeature) in SQLAlchemy 0.5.4. 
# This issue might be fixed in newer versions. 
binds.update(dict.fromkeys([Employee, Customer, Invoice], finance_engine)) 

staff_engine = create_engine(url2) 
binds.update(dict.fromkeys(staff_metadata.sorted_tables, staff_engine)) 
# See comment above. 
binds.update(dict.fromkeys([Project, Hour], staff_engine)) 

session = sessionmaker(binds=binds)() 
+0

@Denis, tenemos metadatas separadas, una para cada una de las dos bases de datos. Teniendo eso en cuenta, ambos son enlaces.las llamadas a update() requeridas para cada base de datos, como en su ejemplo, o podríamos usar solo las que usan xxx_metadata.sorted_tables? Supongo que espero encontrar algo que vincule el enlace a los metadatos, pero por solicitud ... Debería haberlo mencionado. –

+0

Metadata es global. Un enlace global no siempre es tan bueno, pero no genera problemas cuando el motor es constante. El uso de un motor global variable requerirá hacks feos similares a 'threading.local()' que son muy malos y propensos a errores. Usar una sesión que sobreviva a la solicitud puede potencialmente filtrar algunos objetos de un usuario a otro. Si bien puedes escribir algún código para protegerlo, es mejor ir de una forma que no tenga ese agujero por diseño. –

+0

No me refiero a metadata.bind, por supuesto, porque como dices eso es global. Me refiero a utilizar el hecho de que el objeto de metadatos ya conoce todas las tablas asociadas. ¿Por qué tenemos que listar [Empleado, Cliente, Factura] etc. explícitamente, en lugar de simplemente decir (pseudo-código) "sessionmaker (binds = {finance_metadata: finance_engine, staff_metadata: staff_ending})". Hubiera pensado que habría algún soporte para usar ese nivel de indirección, en lugar de tener que especificar cada tabla individualmente. –

-1

me gustaría ver en la agrupación de conexiones y ver si usted no puede encontrar una manera de tener una piscina por usuario. Puede dispose() el grupo cuando la sesión del usuario ha expirado

Cuestiones relacionadas