Fixes #710: manage storage backends and implement sql one

WARNING: this commit brake sql migrations
This commit is contained in:
Jérôme Schneider 2013-05-21 11:59:19 +02:00
parent 6b1788299a
commit e0ea574b04
19 changed files with 519 additions and 214 deletions

View File

@ -52,32 +52,29 @@ You must install the following packages to use Mandaye
* sqlalchemy-migrate:: http://pypi.python.org/pypi/sqlalchemy-migrate
You can install all those dependencies quickly using pip::
pip install poster SQLAlchemy Beaker Mako lxml gunicorn sqlalchemy-migrate xtraceback
or easy_install::
easy_install poster SQLAlchemy Beaker Mako lxml gunicorn sqlalchemy-migrate xtraceback
or apt-get (Debian based distributions)::
apt-get install gunicorn python-poster python-sqlalchemy python-beaker python-mako python-lxml python-setuptools
It's recommanded to install the following modules
* PyCrypto >= 2.3:: http://pypi.python.org/pypi/pycrypto
* Static >= 0.4:: http://pypi.python.org/pypi/static
You can install this Python modules with pip::
pip install pycrypto static
Quick installation
------------------
Install at least Python >=2.5 and setuptools or distribute and enter this command in a shell::
Install at least Python >=2.5 and pip in your system.
For example with Debian or a Debian based distribution::
sudo apt-get install python python-pip
Then install virtualenv ::
pip install virtualenv
Create your virtualenv activate it::
virtualenv mandaye
source mandaye/bin/activate
pip install -U pip
Install mandaye::
$ tar xfvz mandaye-VERSION.tar.gz
$ cd mandaye-VERSION
$ pip install -r requirements.txt
$ python setup.py install
If you want to develop use this command line::
@ -88,12 +85,9 @@ If you want to develop use this command line::
Quick Start
-----------
Configure MANDAYE_PATH/mandaye/config.py with your own preferences.
You must configure the database uri and the log file.
First step is to create a mandaye project::
First create your database::
$ mandaye_admin.py --createdb
$ mandaye_admin.py --newproject
Launch mandaye server::

View File

@ -16,19 +16,20 @@ from lxml.html import fromstring
from urlparse import parse_qs
from mandaye import config, VERSION
from mandaye.db import sql_session
from mandaye.exceptions import MandayeException
from mandaye.models import Site, ExtUser, LocalUser
from mandaye.log import logger
from mandaye.http import HTTPResponse, HTTPHeader, HTTPRequest
from mandaye.response import _500, _302, _401
from mandaye.response import template_response
from mandaye.server import get_response
from mandaye.config.backend import ManagerIDPUser, ManagerSPUser,\
ManagerServiceProvider
try:
from Crypto.Cipher import AES
except ImportError:
config.encrypt_ext_password = False
config.encrypt_sp_password = False
class AuthForm(object):
@ -72,7 +73,7 @@ a password_field key if you want to encode a password.")
def _encrypt_pwd(self, post_values):
""" This method allows you to encrypt a password
To use this feature you muste set encrypt_ext_password to True
To use this feature you muste set encrypt_sp_password to True
in your configuration and set a secret in encrypt_secret
post_values: containt the post values
return None and modify post_values
@ -98,7 +99,7 @@ a password_field key if you want to encode a password.")
def _decrypt_pwd(self, post_values):
""" This method allows you to dencrypt a password encrypt with
_encrypt_pwd method. To use this feature you muste set
encrypt_ext_password to True in your configuration and
encrypt_sp_password to True in your configuration and
set a secret in encrypt_secret
post_values: containt the post values
return None and modify post_values
@ -120,7 +121,7 @@ a password_field key if you want to encode a password.")
def _get_password(self, post_values):
if self.form_values.has_key('password_field'):
if config.encrypt_ext_password:
if config.encrypt_sp_password:
return self._encrypt_pwd(
post_values[self.form_values['password_field']]
)
@ -182,54 +183,36 @@ a password_field key if you want to encode a password.")
request = HTTPRequest(cookies, headers, "POST", params)
return get_response(env, request, action, cj)
def _save_association(self, env, local_login, post_values):
def _save_association(self, env, unique_id, post_values):
""" save an association in the database
env: wsgi environment
local_login: the Mandaye login
unique_id: idp uinique id
post_values: dict with the post values
"""
ext_username = post_values[self.form_values['username_field']]
if config.encrypt_ext_password:
sp_login = post_values[self.form_values['username_field']]
if config.encrypt_sp_password:
self._encrypt_pwd(post_values)
site = sql_session().query(Site).\
filter_by(name=self.site_name).first()
if not site:
logger.info('Add %s site in the database' % self.site_name)
site = Site(self.site_name)
sql_session().add(site)
local_user = sql_session().query(LocalUser).\
filter_by(login=local_login).first()
if not local_user:
logger.debug('Add user %s in the database' % local_login)
local_user = LocalUser(login=local_login)
sql_session().add(local_user)
ext_user = sql_session().query(ExtUser).\
join(LocalUser).\
filter(LocalUser.login==local_login).\
filter(ExtUser.login==ext_username).\
first()
if not ext_user:
ext_user = ExtUser()
sql_session().add(ext_user)
logger.info('New association: %s with %s on site %s' % \
(ext_username, local_login, self.site_name))
ext_user.login = ext_username
ext_user.post_values = post_values
ext_user.local_user = local_user
ext_user.last_connection = datetime.now()
ext_user.site = site
sql_session().commit()
env['beaker.session']['login'] = local_login
env['beaker.session'][self.site_name] = ext_user.id
service_provider = ManagerServiceProvider.get_or_create(self.site_name)
idp_user = ManagerIDPUser.get_or_create(unique_id)
sp_user = ManagerSPUser.get_or_create(sp_login, post_values,
idp_user, service_provider)
sp_user.login = sp_login
sp_user.post_values = post_values
sp_user.idp_user = idp_user
sp_user.last_connection = datetime.now()
sp_user.service_provider = service_provider
ManagerSPUser.save()
env['beaker.session']['unique_id'] = unique_id
env['beaker.session'][self.site_name] = sp_user.id
env['beaker.session'].save()
def associate_submit(self, env, values, condition, request, response):
""" Associate your login / password into your database
"""
logger.debug("Trying to associate a user")
login = env['beaker.session'].get('login')
unique_id = env['beaker.session'].get('unique_id')
if request.msg:
if not login:
if not unique_id:
logger.warning("Association failed: user isn't login on Mandaye")
return _302(values.get('connection_url'))
post = parse_qs(request.msg.read(), request)
@ -247,7 +230,7 @@ a password_field key if you want to encode a password.")
response = self.replay(env, post_values)
if eval(condition):
logger.debug("Replay works: save the association")
self._save_association(env, login, post_values)
self._save_association(env, unique_id, post_values)
if qs.has_key('next_url'):
return _302(qs['next_url'], response.cookies)
return response
@ -256,20 +239,20 @@ a password_field key if you want to encode a password.")
qs['type'] = 'badlogin'
return _302(values.get('associate_url') + "?%s" % urllib.urlencode(qs))
def _login_ext_user(self, ext_user, env, condition, values):
""" Log in an external user
def _login_sp_user(self, sp_user, env, condition, values):
""" Log in sp user
"""
if not ext_user.login:
if not sp_user.login:
return _500(env['PATH_INFO'],
'Invalid values for AuthFormDispatcher.login')
post_values = copy.deepcopy(ext_user.post_values)
if config.encrypt_ext_password:
post_values = copy.deepcopy(sp_user.post_values)
if config.encrypt_sp_password:
self._decrypt_pwd(post_values)
response = self.replay(env, post_values)
if condition and eval(condition):
ext_user.last_connection = datetime.now()
sql_session().commit()
env['beaker.session'][self.site_name] = ext_user.id
sp_user.last_connection = datetime.now()
ManagerSPUser.save()
env['beaker.session'][self.site_name] = sp_user.id
env['beaker.session'].save()
return response
else:
@ -279,29 +262,27 @@ a password_field key if you want to encode a password.")
""" Automatic login on a site with a form
"""
logger.debug('Trying to login on Mandaye')
login = self.get_current_login(env)
if not login:
# Specific method to get current idp unique id
unique_id = self.get_current_unique_id(env)
if not unique_id:
return _401('Access denied: invalid token')
# FIXME: hack to force beaker to generate an id
# somtimes beaker doesn't do it by himself
env['beaker.session'].regenerate_id()
env['beaker.session']['login'] = login
env['beaker.session']['unique_id'] = unique_id
env['beaker.session'].save()
logger.debug('User %s successfully login' % env['beaker.session']['login'])
ext_user = sql_session().query(ExtUser).\
join(LocalUser).\
join(Site).\
filter(LocalUser.login==login).\
filter(Site.name==self.site_name).\
order_by(ExtUser.last_connection.desc()).\
first()
if not ext_user:
logger.debug('User %s is not associate' % env['beaker.session']['login'])
logger.debug('User %s successfully login' % env['beaker.session']['unique_id'])
idp_user = ManagerIDPUser.get(unique_id)
service_provider = ManagerServiceProvider.get(self.site_name)
sp_user = ManagerSPUser.get_last_connected(idp_user, service_provider)
if not sp_user:
logger.debug('User %s is not associate' % env['beaker.session']['unique_id'])
return _302(values.get('associate_url') + "?type=first")
return self._login_ext_user(ext_user, env, condition, values)
return self._login_sp_user(sp_user, env, condition, values)
def logout(self, env, values, request, response):
""" Destroy the Beaker session
@ -317,19 +298,17 @@ a password_field key if you want to encode a password.")
This method must have a query string with a username parameter
"""
# TODO: need to logout the first
login = env['beaker.session']['login']
unique_id = env['beaker.session']['unique_id']
qs = parse_qs(env['QUERY_STRING'])
if not login or not qs.has_key('id'):
return _401('Access denied: beaker session invalid or not qs id')
id = qs['id'][0]
ext_user = sql_session().query(ExtUser).\
join(LocalUser).\
filter(LocalUser.login==login).\
filter(ExtUser.id==id).\
first()
if not ext_user:
service_provider = ManagerServiceProvider.get(self.site_name)
idp_user = ManagerServiceProvider.get(unique_id)
sp_user = ManagerSPUser.get_last_connected(idp_user, service_provider)
if not sp_user:
return _302(values.get('associate_url'))
return self._login_ext_user(ext_user, env, 'response.code==302', values)
return self._login_sp_user(sp_user, env, 'response.code==302', values)
def disassociate(self, env, values, request, response):
""" Multi accounts feature
@ -345,15 +324,9 @@ a password_field key if you want to encode a password.")
if not login or not qs.has_key('id'):
return _401('Access denied: beaker session invalid or not id')
id = qs['id'][0]
ext_user = sql_session().query(ExtUser).\
join(LocalUser).\
filter(LocalUser.login==login).\
filter(ExtUser.id==id).\
first()
if ext_user:
logger.debug('Disassociate account %s' % ext_user.login)
sql_session().delete(ext_user)
sql_session().commit()
sp_user = ManagerSPUser.get_by_id(id)
if sp_user:
ManagerSPUser.delete(sp_user)
if qs.has_key('logout'):
self.logout(env, values, request, response)
return _302(values.get('next_url'))

View File

@ -38,8 +38,8 @@ class VincennesAuth(AuthForm):
res[keyvalue[0]] = keyvalue[1]
return res
def get_current_login(self, env):
""" Return the current Vincennes pseudo
def get_current_unique_id(self, env):
""" Return the current Vincennes unique id
"""
from mandaye import config
# TODO: test time validity
@ -97,7 +97,7 @@ class VincennesAuth(AuthForm):
if not login:
logger.debug('Auto login failed because the user is not connected on vincennes.fr')
return _302(path, request.cookies)
env['beaker.session']['login'] = login
env['beaker.session']['unique_id'] = unique_id
env['beaker.session'].save()
ext_user = sql_session().query(ExtUser).\
join(LocalUser).\

View File

View File

@ -0,0 +1,76 @@
class DefaultManagerIDPUser:
@staticmethod
def get(unique_id, idp_id='default'):
pass
@staticmethod
def create(unique_id, idp_id='default'):
pass
@staticmethod
def get_or_create(unique_id, idp_id='default'):
pass
@staticmethod
def delete(idp_user):
pass
@staticmethod
def save(idp_user):
pass
class DefaultManagerSPUser:
@staticmethod
def get(login, idp_user, service_provider):
pass
@staticmethod
def get_by_id(id):
pass
@staticmethod
def get_last_connected(idp_user, service_provider):
pass
@staticmethod
def create(login, post_values, idp_user, service_provider):
pass
@staticmethod
def get_or_create(login, post_values, idp_user, service_provider):
pass
@staticmethod
def delete(sp_user):
pass
@staticmethod
def save(sp_user):
pass
class DefaultServiceProvider:
@staticmethod
def get(name):
pass
@staticmethod
def create(name):
pass
@staticmethod
def get_or_create(name):
pass
@staticmethod
def delete(service_provider):
pass
@staticmethod
def save(service_provider):
pass

162
mandaye/backends/sql.py Normal file
View File

@ -0,0 +1,162 @@
from datetime import datetime
from mandaye.db import sql_session
from mandaye.models import IDPUser, SPUser, ServiceProvider
class ManagerIDPUserSQL:
@staticmethod
def get(unique_id, idp_id='default'):
idp_user = sql_session().query(IDPUser).\
filter_by(unique_id=unique_id,
idp_id='default')
if len(idp_user) > 1:
logger.critical('ManagerIDPUserSQL.get %s not unique' % unique_id)
raise MandayeException(
'ManagerIDPUserSQL.get : %s is not unique' % unique_id)
if idp_user:
return idp_user.first()
else:
return None
@staticmethod
def create(unique_id, idp_id='default'):
idp_user = IDPUser(
unique_id=unique_id,
idp_id=idp_id)
sql_session().add(idp_user)
return idp_user
@staticmethod
def get_or_create(unique_id, idp_id='default'):
if ManagerIDPUserSQL.get(**kwargs):
return user
else:
return ManagerIDPUserSQL.create(**kwargs)
@staticmethod
def delete(idp_user):
sql_session().delete(idp_user)
sql_session().commit()
@staticmethod
def save():
sql_session().commit()
class ManagerSPUserSQL:
@staticmethod
def get(login, idp_user, service_provider):
sp_user = sql_session().query(SPPUser).\
join(IDPUser).\
join(ServiceProvider).\
filter_by(login=login,
idp_user=idp_user,
service_provider=service_provider)
if sp_user:
return sp_user.first()
else:
return None
@staticmethod
def get_by_id(id):
return sql_session().query(SPUser).\
filter(id==id).first()
@staticmethod
def get_last_connected(idp_user, service_provider):
return sql_session().query(SPPUser).\
join(IDPUser).\
join(ServiceProvider).\
filter(idp_user=idp_user).\
filer(service_provider=service_provider).\
order_by(SPUser.last_connection.desc()).\
first()
@staticmethod
def get_sp_users(idp_unique_id, service_provider_name):
return sql_session().query(SPUser).\
join(IDPUser).\
join(ServiceProvider).\
filter(IDPUser.unique_id==idp_unique_id).\
filter(ServiceProvider.name==service_provider_name).\
order_by(SPUser.last_connection.desc()).\
all()
@staticmethod
def create(login, post_values, idp_user, service_provider):
sp_user = SPUser(
login=login,
post_values=post_values,
idp_id=idp_id,
service_provider = service_provider
)
logger.info('New association: %s with %s on site %s' % \
(login, idp_user.unique_id, service_provider.name))
sql_session().add(sp_user)
sql_session().commit()
return idp_user
@staticmethod
def get_or_create(login, post_values, idp_user, service_provider):
sp_user = ManagerSPUserSQL.get(login, idp_user, service_provider)
if sp_user:
return sp_user
else:
return ManagerSPUserSQL.create(login, post_values,
idp_user, service_provider)
@staticmethod
def all():
return sql_session().query(SPUser).all()
@staticmethod
def delete(sp_user):
logger.debug('Disassociate account %s' % sp_user.login)
sql_session().delete(sp_user)
sql_session().commit()
@staticmethod
def save():
sql_session().commit()
class ManagerServiceProviderSQL:
@staticmethod
def get(name):
sp = sql_session().query(ServiceProvider).\
filter_by(name=name)
if sp:
return sp.first()
else:
return None
@staticmethod
def create(name):
logger.info('Add %s service provider into the database' % name)
sp = ServiceProvider(name=name)
sql_session().add(sp)
sql_session().commit()
return sp
@staticmethod
def get_or_create(name):
sp = ManagerServiceProviderSQL.get(name)
if sp:
return sp
else:
return ManagerServiceProviderSQL.create(name)
@staticmethod
def delete(service_provider):
logger.debug('Delete service provider %s' % service_provider.name)
sql_session().delete(service_provider)
sql_session().commit()
@staticmethod
def save():
sql_session().commit()
ManagerServiceProvider = ManagerServiceProviderSQL
ManagerSPUser = ManagerSPUserSQL
ManagerServiceProvider = ManagerServiceProviderSQL

View File

@ -1,6 +1,14 @@
import logging
from mandaye.exceptions import ImproperlyConfigured
# Database configuration
# rfc 1738 http://rfc.net/rfc1738.html
db_url = 'sqlite:///test.db'
# Default local backend
import mandaye.backends.sql
backend = mandaye.backends.sql
# Needed if ssl is activated
ssl = False
keyfile = ''
@ -11,6 +19,7 @@ debug = False
syslog = False
log_file = '/var/log/mandaye/mandaye.log'
log_level = logging.INFO
# Log rotation
# W[0-6] : weekly (0: Monday), D: day, ... (python doc)
log_when = 'W6'
@ -24,9 +33,6 @@ template_directory = 'mandaye/templates'
# Static folder
static_root = 'mandaye/static'
# Database configuration
# rfc 1738 http://rfc.net/rfc1738.html
db_url = 'sqlite:///test.db'
# Email notification configuration
email_notification = False
@ -42,9 +48,9 @@ use_long_trace = True
# Decompress response only if you load a filter
auto_decompress = True
# Encrypt external passwords with a secret
# Encrypt service provider passwords with a secret
# You should install pycypto to use this feature
encrypt_ext_password = False
encrypt_sp_password = False
# Must be a 16, 24, or 32 bytes long
encrypt_secret = ''

View File

@ -9,3 +9,4 @@ class ImproperlyConfigured(Exception):
class MandayeException(Exception):
"Mandaye generic exception"
pass

View File

@ -6,12 +6,13 @@ from urlparse import parse_qs
from BeautifulSoup import BeautifulSoup
import lxml.html
from mandaye.db import sql_session
from mandaye.log import logger
from mandaye.models import Site, ExtUser, LocalUser
from mandaye.response import _302, _401
from mandaye.template import serve_template
from mandaye.config.backend import ManagerSPUser
def get_associate_form(env, values):
""" Return association template content
"""
@ -37,8 +38,7 @@ def get_current_account(env, values):
""" Return the current Mandaye user """
site_name = values.get('site_name')
if env['beaker.session'].get(site_name):
return sql_session().query(ExtUser).\
get(env['beaker.session'].get(site_name))
return ManagerSPUser.get_by_id(env['beaker.session'].get(site_name))
else:
return None
@ -46,18 +46,13 @@ def get_current_account(env, values):
def get_multi_template(env, values, current_account):
""" return the content of the multi account template
"""
login = env['beaker.session'].get('login')
unique_id = env['beaker.session'].get('unique_id')
if login:
ext_users = sql_session().query(ExtUser).\
join(LocalUser).\
join(Site).\
filter(LocalUser.login==login).\
filter(Site.name==values.get('site_name')).\
order_by(ExtUser.last_connection.desc()).\
all()
accounts = {}
for ext_user in ext_users:
accounts[ext_user.id] = ext_user.login
sp_users = ManagerSPUser.get_sp_users(unique_id,
values.get('site_name'))
for sp_user in sp_users:
accounts[sp_user.id] = sp_user.login
if current_account:
current_login = current_account.login
else:
@ -113,7 +108,7 @@ class Biblio:
""" Modify response html to support multi accounts
"""
if response.msg and '<h2><div>Mon compte</div></h2>' in response.msg:
if env['beaker.session'].get('login'):
if env['beaker.session'].get('unique_id'):
current_account = get_current_account(env, values)
template = get_multi_template(env, values, current_account)
if current_account:
@ -177,7 +172,7 @@ class EspaceFamille:
"""
if response.msg and\
'<!-- Navigation -->' in response.msg:
login = env['beaker.session'].get('login')
login = env['beaker.session'].get('unique_id')
current_account = get_current_account(env, values)
if login and current_account:
disassociate = serve_template('famille/disassociate.html',
@ -225,7 +220,7 @@ class Duonet:
"""
if response.msg \
and 'font-weight:bold;">Conservatoire de Vincennes' in response.msg:
login = env['beaker.session'].get('login')
login = env['beaker.session'].get('unique_id')
site_name = values.get('site_name')
current_account = get_current_account(env, values)
if login and current_account:

View File

@ -1,18 +1,118 @@
import collections
import json
from datetime import datetime
from sqlalchemy import *
from migrate import *
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy import ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.mutable import Mutable
from sqlalchemy.orm import column_property, relationship, backref
from sqlalchemy.types import TypeDecorator, VARCHAR
Base = declarative_base()
class JSONEncodedDict(TypeDecorator):
"Represents an immutable structure as a json-encoded string."
impl = VARCHAR
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
class MutationDict(Mutable, dict):
@classmethod
def coerce(cls, key, value):
""" Convert plain dictionaries to MutationDict. """
if not isinstance(value, MutationDict):
if isinstance(value, dict):
return MutationDict(value)
# this call will raise ValueError
return Mutable.coerce(key, value)
else:
return value
def __setitem__(self, key, value):
""" Detect dictionary set events and emit change events. """
dict.__setitem__(self, key, value)
self.changed()
def __delitem__(self, key):
""" Detect dictionary del events and emit change events. """
dict.__delitem__(self, key)
self.changed()
MutationDict.associate_with(JSONEncodedDict)
class ServiceProvider(Base):
__tablename__ = 'service_provider'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True, nullable=False)
def __init__(self, name):
self.name = name
def __repr__(self):
return "<ServiceProvider('%s')>" % (self.name)
class IDPUser(Base):
__tablename__ = 'idp_user'
id = Column(Integer, primary_key=True)
# Nameid, pseudo, email, ...
unique_id = Column(String(150), nullable=False)
# Entityid
idp_id = Column(String(150), nullable=False)
sp_users = relationship("SPUser", backref=backref('idp_user'))
def __init__(self, unique_id=None, idp_id=None, sp_users=None):
self.unique_id = unique_id
self.idp_id = idp_id
self.sp_users = sp_users
def __repr__(self):
return "<IDPUser %d '%s'>" % (self.id, self.unique_id)
class SPUser(Base):
__tablename__ = 'sp_user'
id = Column(Integer, primary_key=True)
post_values = Column(JSONEncodedDict, nullable=False)
creation_date = Column(DateTime, default=datetime.now(), nullable=False)
last_connection = Column(DateTime, default=datetime.now())
idp_user_id = Column(Integer, ForeignKey('idp_user.id'))
service_provider_id = Column(Integer, ForeignKey('service_provider.id'),
nullable=False)
service_provider = relationship("ServiceProvider", backref=backref('users'))
def __init__(self, post_values=None):
self.post_values = post_values
def __repr__(self):
return "<SPUser '%d'>" % (self.id)
def upgrade(migrate_engine):
meta = MetaData(bind=migrate_engine)
ext_users = Table('ext_users', meta, autoload=True)
creation_date = Column("creation_date", DateTime, default=datetime.now())
creation_date.create(ext_users)
ServiceProvider.__table__.create(migrate_engine)
SPUser.__table__.create(migrate_engine)
IDPUser.__table__.create(migrate_engine)
def downgrade(migrate_engine):
meta = MetaData(bind=migrate_engine)
ext_users = Table('ext_users', meta, autoload=True)
ext_users.c.creation_date.drop()
ServiceProvider.__table__.drop(migrate_engine)
SPUser.__table__.drop(migrate_engine)
IDPUser.__table__.drop(migrate_engine)

View File

@ -1,4 +1,5 @@
""" This models is used if you are using form replay authentification
"""
import collections
import json
@ -11,6 +12,7 @@ from sqlalchemy.ext.mutable import Mutable
from sqlalchemy.orm import column_property, relationship, backref
from sqlalchemy.types import TypeDecorator, VARCHAR
Base = declarative_base()
class JSONEncodedDict(TypeDecorator):
"Represents an immutable structure as a json-encoded string."
@ -51,10 +53,10 @@ class MutationDict(Mutable, dict):
self.changed()
MutationDict.associate_with(JSONEncodedDict)
Base = declarative_base()
class Site(Base):
__tablename__ = 'sites'
class ServiceProvider(Base):
__tablename__ = 'service_provider'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True, nullable=False)
@ -63,52 +65,46 @@ class Site(Base):
self.name = name
def __repr__(self):
return "<Site('%s')>" % (self.name)
return "<ServiceProvider('%s')>" % (self.name)
class LocalUser(Base):
""" Mandaye's user
"""
__tablename__ = 'local_users'
class IDPUser(Base):
__tablename__ = 'idp_user'
id = Column(Integer, primary_key=True)
login = Column(String(150), nullable=False, unique=True)
password = Column(String(25), nullable=True)
firstname = Column(String(150), nullable=True)
lastname = Column(String(150), nullable=True)
fullname = column_property(firstname + " " + lastname)
# Nameid, pseudo, email, ...
unique_id = Column(String(150), nullable=False)
# Entityid
idp_id = Column(String(150), nullable=False)
sp_users = relationship("SPUser", backref=backref('idp_user'))
creation_date = Column(DateTime, default=datetime.now(), nullable=False)
last_connection = Column(DateTime, default=datetime.now())
def __init__(self, login=None, password=None, fullname=None):
self.login = login
self.password = password
self.fullname = fullname
def __init__(self, unique_id=None, idp_id=None, sp_users=None):
self.unique_id = unique_id
self.idp_id = idp_id
self.sp_users = sp_users
def __repr__(self):
return "<LocalUser('%d %s')>" % (self.id, self.fullname)
return "<IDPUser %d '%s'>" % (self.id, self.unique_id)
class ExtUser(Base):
""" User of externals applications
"""
__tablename__ = 'ext_users'
class SPUser(Base):
__tablename__ = 'sp_user'
id = Column(Integer, primary_key=True)
login = Column(String(150), nullable=False)
post_values = Column(JSONEncodedDict)
post_values = Column(JSONEncodedDict, nullable=False)
creation_date = Column(DateTime, default=datetime.now(), nullable=False)
last_connection = Column(DateTime, default=datetime.now())
local_user_id = Column(Integer, ForeignKey('local_users.id'), nullable=False)
site_id = Column(Integer, ForeignKey('sites.id'), nullable=False)
local_user = relationship("LocalUser", backref=backref('ext_users'))
site = relationship("Site", backref=backref('users'))
idp_user_id = Column(Integer, ForeignKey('idp_user.id'))
service_provider_id = Column(Integer, ForeignKey('service_provider.id'),
nullable=False)
service_provider = relationship("ServiceProvider", backref=backref('users'))
def __init__(self, login=None, post_values=None):
self.login = login
def __init__(self, post_values=None):
self.post_values = post_values
def __repr__(self):
return "<ExtUser '%d'>" % (self.id)
return "<SPUser '%d'>" % (self.id)

View File

@ -76,7 +76,7 @@ def _500(path, msg, exception=None, env=None):
tb_str = _get_traceback()
if email:
try:
email.sent('Mandaye internal server error',
email.sent('Mandaye internal server error %s' % exception,
_get_text_error(tb_str, path, env=env))
except Exception as detail:
logger.warning('Sent mail failed with error: %s' % detail)

View File

@ -10,12 +10,9 @@ from optparse import OptionParser
from mandaye import config
from mandaye.log import logger
from mandaye.models import ExtUser
from mandaye.db import sql_session
def get_cmd_options():
usage = "usage: %prog --createdb|--cryptpwd"
usage = "usage: %prog --newproject|--createdb|--cryptpwd"
parser = OptionParser(usage=usage)
parser.add_option("--createdb",
dest="createdb",
@ -29,6 +26,12 @@ def get_cmd_options():
action="store_true",
help="Crypt external password in Mandaye's database"
)
parser.add_option("--newproject",
dest="cryptpwd",
default=False,
action="store_true",
help="Crypt external password in Mandaye's database"
)
(options, args) = parser.parse_args()
return options
@ -56,19 +59,17 @@ def main():
if config.db_url:
import migrate.versioning.api
import mandaye.migration
from sqlalchemy import create_engine
from mandaye.models import Base
engine = create_engine(config.db_url)
Base.metadata.create_all(engine)
migrate.versioning.api.version_control(url=config.db_url,
repository=mandaye.migration.__path__[0])
migrate.versioning.api.upgrade(url=config.db_url,
repository=mandaye.migration.__path__[0])
logger.info("Database created")
if options.cryptpwd:
for user in sql_session().query(ExtUser).all():
from mandaye.config.backend import ManagerSPUserSQL
for user in ManagerSPUserSQL.all():
user.password = encrypt_pwd(user.password)
sql_session().commit()
ManagerSPUserSQL.save()
if __name__ == "__main__":
main()

View File

@ -1,12 +0,0 @@
Beaker==1.5.4
Mako==0.4.2
MarkupSafe==0.15
SQLAlchemy==0.7.2
distribute==0.6.10
gevent==0.13.6
greenlet==0.3.1
importlib==1.0.2
poster==0.8.1
static==0.4
wsgiref==0.1.2
xtraceback==0.3

7
requirements.txt Normal file
View File

@ -0,0 +1,7 @@
beaker>=1.6
poster>=0.8
pycrypto>=2.0
lxml>=2.0
xtraceback>=0.3
sqlalchemy>=0.7,<0.8
static

View File

@ -7,6 +7,23 @@
import mandaye
from setuptools import setup, find_packages
from sys import version
install_requires=[
'beaker>=1.6',
'gunicorn>=0.13',
'mako>=0.3',
'poster>=0.8',
'pycrypto>=2.0',
'sqlalchemy>=0.6',
'sqlalchemy-migrate>=0.7.2',
'lxml>=2.0',
'xtraceback>=0.3',
'static',
]
if version < '2.7':
install_requires.append('importlib')
setup(name="mandaye",
version=mandaye.VERSION,
@ -17,20 +34,9 @@ setup(name="mandaye",
author_email="info@entrouvert.org",
maintainer="Jerome Schneider",
maintainer_email="jschneider@entrouvert.com",
scripts=['mandaye_server.py', 'mandaye_admin.py', 'mandaye_migrate.py'],
scripts=['mandaye_admin.py', 'mandaye_migrate.py'],
packages=find_packages(),
package_data={},
install_requires=[
'beaker>=1.6',
'gunicorn>=0.13',
'importlib',
'mako>=0.3',
'poster>=0.8',
'pycrypto>=2.0',
'sqlalchemy>=0.6',
'sqlalchemy-migrate>=0.7.2',
'lxml>=2.0',
'xtraceback>=0.3',
'static',
],
install_requires=install_requires
)