2009-12-07 9 views
6

Mam aplikację internetową opartą na Pylonach, która łączy się przez Sqlalchemy (v0.5) z bazą danych Postgres. Ze względów bezpieczeństwa, zamiast podążać za typowym schematem prostych aplikacji internetowych (jak widać w prawie wszystkich tutorialach), nie używam ogólnego użytkownika PostgreSQL (np. "Webapp"), ale wymagam, aby użytkownicy wprowadzili swój własny identyfikator użytkownika i hasło PostgreSQL i używam tego do nawiązania połączenia. Oznacza to, że uzyskujemy pełną ochronę Postgres.Z sqlalchemy, jak dynamicznie powiązać silnik bazy danych na żądanie na żądanie

Jeszcze bardziej skomplikowane są dwie oddzielne bazy danych, z którymi można się połączyć. Chociaż obecnie znajdują się w tym samym klastrze Postgres, muszą być w stanie przenieść się na osobne hosty w późniejszym terminie.

Używamy pakietu sqlalchemy, declarative, choć nie widzę, aby miało to jakiekolwiek znaczenie.

Większość przykładów sqlalchemy pokazuje trywialne podejścia, takie jak jednorazowe utworzenie metadanych, przy uruchamianiu aplikacji, z ogólnym identyfikatorem użytkownika bazy danych i hasłem, używanym przez aplikację internetową. Zwykle odbywa się to za pomocą Metadata.bind = create_engine(), czasami nawet na poziomie modułu w plikach modelu bazy danych.

Moje pytanie brzmi, w jaki sposób odroczyć ustanawianie połączeń, dopóki użytkownik się nie zaloguje, a następnie (oczywiście) ponownie użyć tych połączeń lub przywrócić je przy użyciu tych samych poświadczeń dla każdego kolejnego żądania.

Mamy to działa - uważamy - ale nie tylko nie jestem pewien bezpieczeństwa, uważam też, że wygląda niesamowicie ciężko na tę sytuację.

Wewnątrz metody BaseController, pobierającej identyfikator użytkownika i hasło z sesji sieciowej, wywołujemy funkcję sqlalchemy create_engine() jeden raz dla każdej bazy danych, a następnie wywołujemy procedurę, która wywołuje funkcję Session.bind_mapper(), raz dla każdej tabeli, która may należy odnosić się do każdego z tych połączeń, nawet jeśli dowolne żądanie zwykle odnosi się tylko do jednej lub dwóch tabel. Wygląda to mniej więcej tak:

# 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? 

tak więc create_engine() wywołuje wystąpić na każde żądanie ... widzę, że jest to konieczne, a Bilard prawdopodobnie buforuje je i robi rzeczy rozsądnie.

Ale nazywając Session.bind_mapper() raz na każdej tabeli na każdy żądanie? Wygląda na to, że musi być lepszy sposób.

Oczywiście, ponieważ pragnienie silnego bezpieczeństwa leży u podstaw tego wszystkiego, nie chcemy żadnej szansy, że połączenie ustanowione dla użytkownika o wysokim poziomie bezpieczeństwa zostanie nieumyślnie użyte na późniejszym żądanie przez użytkownika o niskim poziomie bezpieczeństwa.

Odpowiedz

3

Powiązanie globalnych obiektów (maperów, metadanych) z konkretnym połączeniem użytkownika nie jest dobrym rozwiązaniem. Poza korzystaniem z sesji z zasięgiem. Proponuję utworzyć nową sesję dla każdego żądania i skonfigurować ją do korzystania z połączeń specyficznych dla użytkownika. W poniższej przykładowej założono, że używasz oddzielnych obiektów metadanych dla każdej bazy danych:

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

@Dis, mamy oddzielne metadane, po jednej dla każdej z dwóch baz danych. Biorąc pod uwagę, że oba są wiążące.Połączenia update() wymagane dla każdej bazy danych, tak jak w twoim przykładzie, czy moglibyśmy uzyskać tylko te, które używają xxx_metadata.sorted_tables? Chyba mam nadzieję, że znajdę coś, co wiąże wiązanie z metadanymi, ale na zasadzie "na żądanie" ... Powinienem był o tym wspomnieć. –

+0

Metadane są globalne. Globalne wiązanie zawsze nie jest takie dobre, ale nie prowadzi do problemów, gdy silnik jest stały. Używanie zmiennej silnika globalnego będzie wymagało brzydkich hacków podobnych do 'threading.local()', które są bardzo złe i podatne na błędy. Korzystanie z sesji, która przetrwa żądanie, potencjalnie może spowodować wyciek niektórych obiektów z jednego użytkownika do drugiego. Chociaż możesz napisać jakiś kod, aby go chronić, lepiej iść drogą, która nie ma takiej dziury według projektu. –

+0

Nie mam na myśli ustawiania metadanych. Oczywiście, ponieważ, jak pan twierdzi, jest globalna. Mam na myśli wykorzystanie faktu, że obiekt metadanych już wie o wszystkich powiązanych tabelach. Dlaczego musimy jawnie wymieniać [Pracownik, Klient, Faktura] itd., Zamiast po prostu wypowiadać (pseudo-kod) "Sessionmaker (binds = {finance_metadata: finance_engine, staff_metadata: staff_ending})". Pomyślałbym, że będzie pewne wsparcie dla używania tego poziomu dwukierunkowości, zamiast konieczności indywidualnego określania każdej tabeli. –

-1

Chciałbym przyjrzeć się puli połączeń i sprawdzić, czy nie można znaleźć sposób na jedną pulę na użytkownika. Możesz dispose() puli, gdy sesja użytkownika wygasła

Powiązane problemy