diff --git a/README.rst b/README.rst index 5e9a8dd..2e6c43d 100644 --- a/README.rst +++ b/README.rst @@ -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:: diff --git a/mandaye/auth/authform.py b/mandaye/auth/authform.py index 3b73c15..1ec5efd 100644 --- a/mandaye/auth/authform.py +++ b/mandaye/auth/authform.py @@ -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')) diff --git a/mandaye/auth/vincennes.py b/mandaye/auth/vincennes.py index c3ce06a..f588917 100644 --- a/mandaye/auth/vincennes.py +++ b/mandaye/auth/vincennes.py @@ -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).\ diff --git a/mandaye/backends/__init__.py b/mandaye/backends/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mandaye/backends/default.py b/mandaye/backends/default.py new file mode 100644 index 0000000..d2baa1d --- /dev/null +++ b/mandaye/backends/default.py @@ -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 + + diff --git a/mandaye/backends/sql.py b/mandaye/backends/sql.py new file mode 100644 index 0000000..c9150ba --- /dev/null +++ b/mandaye/backends/sql.py @@ -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 + diff --git a/mandaye/config.py b/mandaye/config.py index 85e67eb..a5cb55f 100644 --- a/mandaye/config.py +++ b/mandaye/config.py @@ -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 = '' diff --git a/mandaye/exceptions.py b/mandaye/exceptions.py index 5d5df3a..e2df73f 100644 --- a/mandaye/exceptions.py +++ b/mandaye/exceptions.py @@ -9,3 +9,4 @@ class ImproperlyConfigured(Exception): class MandayeException(Exception): "Mandaye generic exception" pass + diff --git a/mandaye/filters/vincennes.py b/mandaye/filters/vincennes.py index bbe0ab9..dc065c7 100644 --- a/mandaye/filters/vincennes.py +++ b/mandaye/filters/vincennes.py @@ -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 '

Mon compte

' 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\ '' 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: diff --git a/mandaye/migration/versions/001_initial_schema.py b/mandaye/migration/versions/001_initial_schema.py index c53d5a5..5ae86c4 100644 --- a/mandaye/migration/versions/001_initial_schema.py +++ b/mandaye/migration/versions/001_initial_schema.py @@ -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 "" % (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 "" % (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 "" % (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) diff --git a/mandaye/models.py b/mandaye/models.py index 3417cb6..8022e88 100644 --- a/mandaye/models.py +++ b/mandaye/models.py @@ -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 "" % (self.name) + return "" % (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 "" % (self.id, self.fullname) + return "" % (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 "" % (self.id) - + return "" % (self.id) diff --git a/mandaye/response.py b/mandaye/response.py index cd4c99f..7215ae7 100644 --- a/mandaye/response.py +++ b/mandaye/response.py @@ -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) diff --git a/mandaye/templates/associate.html b/mandaye/templates/passord_replay/associate.html similarity index 100% rename from mandaye/templates/associate.html rename to mandaye/templates/passord_replay/associate.html diff --git a/mandaye/templates/response.html b/mandaye/templates/passord_replay/response.html similarity index 100% rename from mandaye/templates/response.html rename to mandaye/templates/passord_replay/response.html diff --git a/mandaye_admin.py b/mandaye_admin.py index f1190ac..ab1118c 100755 --- a/mandaye_admin.py +++ b/mandaye_admin.py @@ -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() + diff --git a/pip-requirements.txt b/pip-requirements.txt deleted file mode 100644 index 71bb3de..0000000 --- a/pip-requirements.txt +++ /dev/null @@ -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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cd065f9 --- /dev/null +++ b/requirements.txt @@ -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 diff --git a/mandaye_server.py b/server.py.default similarity index 100% rename from mandaye_server.py rename to server.py.default diff --git a/setup.py b/setup.py index 2048b3d..8d5b400 100644 --- a/setup.py +++ b/setup.py @@ -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 ) +