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.
@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. –
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. –
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. –