backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 10 Apr 2012 17:03:19 +0200
changeset 8349 fdb796435d7b
parent 8340 622fcca4fe00 (current diff)
parent 8348 1a88d201675c (diff)
child 8350 e1c05bf6fdeb
backport stable
__pkginfo__.py
debian/control
etwist/server.py
misc/migration/3.14.3_Any.py
server/sources/native.py
server/test/unittest_postgres.py
server/test/unittest_querier.py
server/utils.py
web/data/cubicweb.ajax.js
web/data/cubicweb.css
web/data/cubicweb.edition.js
web/data/cubicweb.old.css
web/data/uiprops.py
web/views/startup.py
web/views/tableview.py
web/webconfig.py
--- a/__pkginfo__.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/__pkginfo__.py	Tue Apr 10 17:03:19 2012 +0200
@@ -54,6 +54,7 @@
     # server dependencies
     'logilab-database': '>= 1.8.2',
     'pysqlite': '>= 2.5.5', # XXX install pysqlite2
+    'passlib': '',
     }
 
 __recommends__ = {
--- a/bin/clone_deps.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/bin/clone_deps.py	Tue Apr 10 17:03:19 2012 +0200
@@ -1,24 +1,24 @@
 #!/usr/bin/python
-import os
 import sys
-from subprocess import call, Popen, PIPE
-try:
-    from mercurial.dispatch import dispatch as hg_call
-except ImportError:
+
+from subprocess import call as sbp_call, Popen, PIPE
+from urllib import urlopen
+from os import path as osp, pardir, chdir
+
+
+def find_mercurial():
+    print "trying to find mercurial from the command line ..."
     print '-' * 20
-    print "mercurial module is not reachable from this Python interpreter"
-    print "trying from command line ..."
-    tryhg = os.system('hg --version')
+    tryhg = sbp_call(['hg', '--version'])
     if tryhg:
-        print 'mercurial seems to unavailable, please install it'
+        print 'mercurial seems to be unavailable, please install it'
         raise
-    print 'found it, ok'
     print '-' * 20
     def hg_call(args):
-        call(['hg'] + args)
-from urllib import urlopen
-from os import path as osp, pardir
-from os.path import normpath, join, dirname
+        return sbp_call(['hg'] + args)
+
+    return hg_call
+
 
 BASE_URL = 'http://www.logilab.org/hg/'
 
@@ -27,7 +27,7 @@
             'logilab/devtools', 'logilab/mtconverter',
             'cubes/blog', 'cubes/calendar', 'cubes/card', 'cubes/comment',
             'cubes/datafeed', 'cubes/email', 'cubes/file', 'cubes/folder',
-            'cubes/forgotpwd', 'cubes/keyword', 'cubes/link',
+            'cubes/forgotpwd', 'cubes/keyword', 'cubes/link', 'cubes/localperms',
             'cubes/mailinglist', 'cubes/nosylist', 'cubes/person',
             'cubes/preview', 'cubes/registration', 'cubes/rememberme',
             'cubes/tag', 'cubes/vcsfile', 'cubes/zone']
@@ -65,9 +65,10 @@
     else:
         sys.stderr.write('usage %s [base_url]\n' %  sys.argv[0])
         sys.exit(1)
+    hg_call = find_mercurial()
     print len(to_clone), 'repositories will be cloned'
-    base_dir = normpath(join(dirname(__file__), pardir, pardir))
-    os.chdir(base_dir)
+    base_dir = osp.normpath(osp.join(osp.dirname(__file__), pardir, pardir))
+    chdir(base_dir)
     not_updated = []
     for repo in to_clone:
         url = base_url + repo
@@ -78,7 +79,7 @@
             directory, repo = repo.split('/')
             if not osp.isdir(directory):
                 os.mkdir(directory)
-                open(join(directory, '__init__.py'), 'w').close()
+                open(osp.join(directory, '__init__.py'), 'w').close()
             target_path = osp.join(directory, repo)
         if osp.exists(target_path):
             print target_path, 'seems already cloned. Skipping it.'
--- a/debian/control	Thu Mar 29 14:20:41 2012 +0200
+++ b/debian/control	Tue Apr 10 17:03:19 2012 +0200
@@ -35,7 +35,7 @@
 Conflicts: cubicweb-multisources
 Replaces: cubicweb-multisources
 Provides: cubicweb-multisources
-Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.8.2), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
+Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.8.2), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2, python-passlib
 Recommends: pyro (<< 4.0.0), cubicweb-documentation (= ${source:Version})
 Suggests: python-zmq
 Description: server part of the CubicWeb framework
--- a/devtools/data/xvfb-run.sh	Thu Mar 29 14:20:41 2012 +0200
+++ b/devtools/data/xvfb-run.sh	Tue Apr 10 17:03:19 2012 +0200
@@ -1,13 +1,11 @@
 #!/bin/sh
 
-# $Id: xvfb-run 2027 2004-11-16 14:54:16Z branden $
-
 # This script starts an instance of Xvfb, the "fake" X server, runs a command
 # with that server available, and kills the X server when done.  The return
 # value of the command becomes the return value of this script.
 #
 # If anyone is using this to build a Debian package, make sure the package
-# Build-Depends on xvfb, xbase-clients, and xfonts-base.
+# Build-Depends on xvfb and xauth.
 
 set -e
 
@@ -15,7 +13,6 @@
 SERVERNUM=99
 AUTHFILE=
 ERRORFILE=/dev/null
-STARTWAIT=3
 XVFBARGS="-screen 0 640x480x8"
 LISTENTCP="-nolisten tcp"
 XAUTHPROTO=.
@@ -62,8 +59,6 @@
 -s ARGS   --server-args=ARGS        arguments (other than server number and
                                     "-nolisten tcp") to pass to the Xvfb server
                                     (default: "$XVFBARGS")
--w DELAY  --wait=DELAY              delay in seconds to wait for Xvfb to start
-                                    before running COMMAND (default: $STARTWAIT)
 EOF
 }
 
@@ -93,7 +88,7 @@
         fi
     fi
     if [ -n "$XVFBPID" ]; then
-        kill $XVFBPID
+        kill "$XVFBPID"
     fi
 }
 
@@ -120,7 +115,7 @@
         -l|--listen-tcp) LISTENTCP="" ;;
         -p|--xauth-protocol) XAUTHPROTO="$2"; shift ;;
         -s|--server-args) XVFBARGS="$2"; shift ;;
-        -w|--wait) STARTWAIT="$2"; shift ;;
+        -w|--wait) shift ;;
         --) shift; break ;;
         *) error "internal error; getopt permitted \"$1\" unexpectedly"
            exit 6
@@ -163,10 +158,13 @@
     XAUTHORITY=$AUTHFILE xauth source - << EOF >>"$ERRORFILE" 2>&1
 add :$SERVERNUM $XAUTHPROTO $MCOOKIE
 EOF
-    XAUTHORITY=$AUTHFILE Xvfb ":$SERVERNUM" $XVFBARGS $LISTENTCP >>"$ERRORFILE" 2>&1 &
+    # handle SIGUSR1 so Xvfb knows to send a signal when it's ready to accept
+    # connections
+    trap : USR1
+    (trap '' USR1; XAUTHORITY=$AUTHFILE exec Xvfb ":$SERVERNUM" $XVFBARGS $LISTENTCP >>"$ERRORFILE" 2>&1) &
     XVFBPID=$!
 
-    sleep "$STARTWAIT"
+    wait || :
     if kill -0 $XVFBPID 2>/dev/null; then
         break
     elif [ -n "$AUTONUM" ]; then
@@ -176,6 +174,7 @@
         continue
     fi
     error "Xvfb failed to start" >&2
+    XVFBPID=
     exit 1
 done
 
--- a/doc/book/en/admin/config.rst	Thu Mar 29 14:20:41 2012 +0200
+++ b/doc/book/en/admin/config.rst	Tue Apr 10 17:03:19 2012 +0200
@@ -70,53 +70,53 @@
 install the `postgresql-client` package on the |cubicweb| host, and others on the
 database host.
 
-.. Note::
+Database cluster
+++++++++++++++++
 
-    If you already have an existing cluster and PostgreSQL server running, you do
-    not need to execute the initilization step of your PostgreSQL database unless
-    you want a specific cluster for |cubicweb| databases or if your existing
-    cluster doesn't use the UTF8 encoding (see note below).
+If you already have an existing cluster and PostgreSQL server running, you do
+not need to execute the initilization step of your PostgreSQL database unless
+you want a specific cluster for |cubicweb| databases or if your existing
+cluster doesn't use the UTF8 encoding (see note below).
 
-* First, initialize a PostgreSQL cluster with the command ``initdb``::
+To initialize a PostgreSQL cluster, use the command ``initdb``::
 
     $ initdb -E UTF8 -D /path/to/pgsql
 
-  Notice the encoding specification. This is necessary since |cubicweb| usually
-  want UTF8 encoded database. If you use a cluster with the wrong encoding, you'll
-  get error like::
+Notice the encoding specification. This is necessary since |cubicweb| usually
+want UTF8 encoded database. If you use a cluster with the wrong encoding, you'll
+get error like::
 
-    new encoding (UTF8) is incompatible with the encoding of the template database (SQL_ASCII)
-    HINT:  Use the same encoding as in the template database, or use template0 as template.
-
+  new encoding (UTF8) is incompatible with the encoding of the template database (SQL_ASCII)
+  HINT:  Use the same encoding as in the template database, or use template0 as template.
 
-  Once initialized, start the database server PostgreSQL with the command::
+Once initialized, start the database server PostgreSQL with the command::
 
-    $ postgres -D /path/to/psql
+  $ postgres -D /path/to/psql
 
-  If you cannot execute this command due to permission issues, please make sure
-  that your username has write access on the database.  ::
+If you cannot execute this command due to permission issues, please make sure
+that your username has write access on the database.  ::
 
-    $ chown username /path/to/pgsql
+  $ chown username /path/to/pgsql
 
-* The database authentication can be either set to `ident sameuser` or `md5`.  If
-  set to `md5`, make sure to use an existing user of your database.  If set to
-  `ident sameuser`, make sure that your client's operating system user name has a
-  matching user in the database. If not, please do as follow to create a user::
+Database authentication
++++++++++++++++++++++++
 
-    $ su
-    $ su - postgres
-    $ createuser -s -P username
+The database authentication is configured in `pg_hba.conf`. It can be either set
+to `ident sameuser` or `md5`.  If set to `md5`, make sure to use an existing
+user of your database.  If set to `ident sameuser`, make sure that your client's
+operating system user name has a matching user in the database. If not, please
+do as follow to create a user::
 
-  The option `-P` (for password prompt), will encrypt the password with the
-  method set in the configuration file :file:`pg_hba.conf`.  If you do not use this
-  option `-P`, then the default value will be null and you will need to set it
-  with::
+  $ su
+  $ su - postgres
+  $ createuser -s -P username
 
-    $ su postgres -c "echo ALTER USER username WITH PASSWORD 'userpasswd' | psql"
+The option `-P` (for password prompt), will encrypt the password with the
+method set in the configuration file :file:`pg_hba.conf`.  If you do not use this
+option `-P`, then the default value will be null and you will need to set it
+with::
 
-.. Note::
-    The authentication method can be configured in file:`pg_hba.conf`.
-
+  $ su postgres -c "echo ALTER USER username WITH PASSWORD 'userpasswd' | psql"
 
 The above login/password will be requested when you will create an instance with
 `cubicweb-ctl create` to initialize the database of your instance.
@@ -149,7 +149,6 @@
     cat /usr/share/postgresql/8.X/contrib/tsearch2.sql | psql -U username template1
 
 
-
 .. _MySqlConfiguration:
 
 MySql
@@ -196,12 +195,12 @@
 
 The ALTER DATABASE command above requires some permissions that your
 user may not have. In that case you will have to ask your local DBA to
-run the query for you. 
+run the query for you.
 
 You can check that the setting is correct by running the following
 query which must return '1'::
 
-   SELECT is_read_committed_snapshot_on 
+   SELECT is_read_committed_snapshot_on
      FROM sys.databases WHERE name='<databasename>';
 
 
@@ -210,6 +209,7 @@
 
 SQLite
 ~~~~~~
+
 SQLite has the great advantage of requiring almost no configuration. Simply
 use 'sqlite' as db-driver, and set path to the dabase as db-name. Don't specify
 anything for db-user and db-password, they will be ignore anyway.
@@ -226,6 +226,7 @@
 
 Pyro name server
 ~~~~~~~~~~~~~~~~
+
 If you want to use Pyro to access your instance remotely, or to have multi-source
 or distributed configuration, it is required to have a Pyro name server running
 on your network. By default it is detected by a broadcast request, but you can
--- a/doc/book/en/admin/setup.rst	Thu Mar 29 14:20:41 2012 +0200
+++ b/doc/book/en/admin/setup.rst	Tue Apr 10 17:03:19 2012 +0200
@@ -63,9 +63,9 @@
 
   deb http://download.logilab.org/production/ lucid/
 
-  Note that for Ubuntu Maverick and newer, you shall use the `lucid`
-  repository and install the ``libgecode19`` package from `lucid
-  universe <http://packages.ubuntu.com/lucid/libgecode19>`_.
+Note that for Ubuntu Maverick and newer, you shall use the `lucid`
+repository and install the ``libgecode19`` package from `lucid
+universe <http://packages.ubuntu.com/lucid/libgecode19>`_.
 
 The repositories are signed with the `Logilab's gnupg key`_. You can download
 and register the key to avoid warnings::
--- a/etwist/server.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/etwist/server.py	Tue Apr 10 17:03:19 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/md5crypt.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/md5crypt.py	Tue Apr 10 17:03:19 2012 +0200
@@ -51,18 +51,16 @@
         v = v >> 6
     return ret
 
-def crypt(pw, salt, magic=None):
+def crypt(pw, salt):
     if isinstance(pw, unicode):
         pw = pw.encode('utf-8')
-    if magic is None:
-        magic = MAGIC
     # Take care of the magic string if present
-    if salt[:len(magic)] == magic:
-        salt = salt[len(magic):]
+    if salt.startswith(MAGIC):
+        salt = salt[len(MAGIC):]
     # salt can have up to 8 characters:
     salt = salt.split('$', 1)[0]
     salt = salt[:8]
-    ctx = pw + magic + salt
+    ctx = pw + MAGIC + salt
     final = md5(pw + salt + pw).digest()
     for pl in xrange(len(pw), 0, -16):
         if pl > 16:
@@ -114,4 +112,4 @@
                            |(int(ord(final[10])) << 8)
                            |(int(ord(final[5]))), 4)
     passwd = passwd + to64((int(ord(final[11]))), 2)
-    return salt + '$' + passwd
+    return passwd
--- a/misc/migration/3.14.3_Any.py	Thu Mar 29 14:20:41 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-# keep the same behavior on existing instance but use the new one on new instance.
-config['https-deny-anonymous'] = True
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.14.7_Any.py	Tue Apr 10 17:03:19 2012 +0200
@@ -0,0 +1,4 @@
+# migrate default format for TriInfo `comment_format` attribute
+sync_schema_props_perms('TrInfo')
+
+commit()
--- a/schemas/workflow.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/schemas/workflow.py	Tue Apr 10 17:03:19 2012 +0200
@@ -185,7 +185,7 @@
     # make by_transition optional because we want to allow managers to set
     # entity into an arbitrary state without having to respect wf transition
     by_transition = SubjectRelation('BaseTransition', cardinality='?*')
-    comment = RichString(fulltextindexed=True)
+    comment = RichString(fulltextindexed=True, default_format='text/plain')
     tr_count = Int(description='autocomputed attribute used to ensure transition coherency')
     # get actor and date time using owned_by and creation_date
 
--- a/server/querier.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/server/querier.py	Tue Apr 10 17:03:19 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -417,7 +417,7 @@
                         if rqlexpr.check(session, eid):
                             break
                     else:
-                        raise Unauthorized()
+                        raise Unauthorized('No read acces on %r with eid %i.' % (var, eid))
                 restricted_vars.update(localcheck)
                 localchecks.setdefault(tuple(localcheck.iteritems()), []).append(solution)
         # raise Unautorized exception if the user can't access to any solution
@@ -723,7 +723,7 @@
             rqlst = rqlst.copy()
             self._annotate(rqlst)
             if args:
-                 # different SQL generated when some argument is None or not (IS
+                # different SQL generated when some argument is None or not (IS
                 # NULL). This should be considered when computing sql cache key
                 cachekey += tuple(sorted([k for k,v in args.iteritems()
                                           if v is None]))
--- a/server/rqlannotation.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/server/rqlannotation.py	Tue Apr 10 17:03:19 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -28,12 +28,13 @@
 from rql.utils import common_parent
 
 def _annotate_select(annotator, rqlst):
+    has_text_query = False
     for subquery in rqlst.with_:
-        annotator._annotate_union(subquery.query)
+        if annotator._annotate_union(subquery.query):
+            has_text_query = True
     #if server.DEBUG:
     #    print '-------- sql annotate', repr(rqlst)
     getrschema = annotator.schema.rschema
-    has_text_query = False
     need_distinct = rqlst.distinct
     for rel in rqlst.iget_nodes(Relation):
         if getrschema(rel.r_type).symmetric and not isinstance(rel.parent, Exists):
@@ -154,6 +155,11 @@
                     sstinfo['scope'] = common_parent(sstinfo['scope'], stinfo['scope']).scope
             except CantSelectPrincipal:
                 stinfo['invariant'] = False
+    # see unittest_rqlannotation. test_has_text_security_cache_bug
+    # XXX probably more to do, but yet that work without more...
+    for col_alias in rqlst.aliases.itervalues():
+        if col_alias.stinfo.get('ftirels'):
+            has_text_query = True
     rqlst.need_distinct = need_distinct
     return has_text_query
 
@@ -272,8 +278,7 @@
     def _annotate_union(self, union):
         has_text_query = False
         for select in union.children:
-            htq = _annotate_select(self, select)
-            if htq:
+            if _annotate_select(self, select):
                 has_text_query = True
         return has_text_query
 
--- a/server/sources/native.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/server/sources/native.py	Tue Apr 10 17:03:19 2012 +0200
@@ -1626,7 +1626,7 @@
                 # if pwd is None but a password is provided, something is wrong
                 raise AuthenticationError('bad password')
             # passwords are stored using the Bytes type, so we get a StringIO
-            args['pwd'] = Binary(crypt_password(password, pwd.getvalue()[:2]))
+            args['pwd'] = Binary(crypt_password(password, pwd.getvalue()))
         # get eid from login and (crypted) password
         rset = self.source.syntax_tree_search(session, self._auth_rqlst, args)
         try:
--- a/server/test/unittest_postgres.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/server/test/unittest_postgres.py	Tue Apr 10 17:03:19 2012 +0200
@@ -1,20 +1,20 @@
 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
-# This file is part of Logilab-common.
+# This file is part of CubicWeb.
 #
-# Logilab-common is free software: you can redistribute it and/or modify it
-# under the terms of the GNU Lesser General Public License as published by the
-# Free Software Foundation, either version 2.1 of the License, or (at your
-# option) any later version.
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
 #
-# Logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
 # details.
 #
 # You should have received a copy of the GNU Lesser General Public License along
-# with Logilab-common.  If not, see <http://www.gnu.org/licenses/>.
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import with_statement
 
--- a/server/test/unittest_querier.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/server/test/unittest_querier.py	Tue Apr 10 17:03:19 2012 +0200
@@ -1,5 +1,5 @@
 # -*- coding: iso-8859-1 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -18,6 +18,8 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for modules cubicweb.server.querier and cubicweb.server.ssplanner
 """
+from __future__ import with_statement
+
 from datetime import date, datetime, timedelta, tzinfo
 
 from logilab.common.testlib import TestCase, unittest_main
@@ -28,7 +30,7 @@
 from cubicweb.server.utils import crypt_password
 from cubicweb.server.sources.native import make_schema
 from cubicweb.devtools import get_test_db_handler, TestServerConfiguration
-
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.repotest import tuplify, BaseQuerierTC
 from unittest_session import Variable
 
@@ -70,14 +72,15 @@
                           ('C0 text,C1 integer', {'A': 'table0.C0', 'B': 'table0.C1'}))
 
 
-def setUpModule(*args):
+def setUpClass(cls, *args):
     global repo, cnx
     config = TestServerConfiguration(apphome=UtilsTC.datadir)
     handler = get_test_db_handler(config)
     handler.build_db_cache()
     repo, cnx = handler.get_repo_and_cnx()
+    cls.repo = repo
 
-def tearDownModule(*args):
+def tearDownClass(cls, *args):
     global repo, cnx
     cnx.close()
     repo.shutdown()
@@ -85,9 +88,8 @@
 
 
 class UtilsTC(BaseQuerierTC):
-    def setUp(self):
-        self.__class__.repo = repo
-        super(UtilsTC, self).setUp()
+    setUpClass = classmethod(setUpClass)
+    tearDownClass = classmethod(tearDownClass)
 
     def get_max_eid(self):
         # no need for cleanup here
@@ -242,9 +244,8 @@
 
 
 class QuerierTC(BaseQuerierTC):
-    def setUp(self):
-        self.__class__.repo = repo
-        super(QuerierTC, self).setUp()
+    setUpClass = classmethod(setUpClass)
+    tearDownClass = classmethod(tearDownClass)
 
     def test_encoding_pb(self):
         self.assertRaises(RQLSyntaxError, self.execute,
@@ -1259,7 +1260,7 @@
         cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'"
                        % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX))
         passwd = str(cursor.fetchone()[0])
-        self.assertEqual(passwd, crypt_password('toto', passwd[:2]))
+        self.assertEqual(passwd, crypt_password('toto', passwd))
         rset = self.execute("Any X WHERE X is CWUser, X login 'bob', X upassword %(pwd)s",
                             {'pwd': Binary(passwd)})
         self.assertEqual(len(rset.rows), 1)
@@ -1274,7 +1275,7 @@
         cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'"
                        % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX))
         passwd = str(cursor.fetchone()[0])
-        self.assertEqual(passwd, crypt_password('tutu', passwd[:2]))
+        self.assertEqual(passwd, crypt_password('tutu', passwd))
         rset = self.execute("Any X WHERE X is CWUser, X login 'bob', X upassword %(pwd)s",
                             {'pwd': Binary(passwd)})
         self.assertEqual(len(rset.rows), 1)
@@ -1501,5 +1502,20 @@
         self.assertFalse(self.execute('Any X WHERE X is CWEType, X name %(name)s', {'name': None}))
         self.assertTrue(self.execute('Any X WHERE X is CWEType, X name %(name)s', {'name': 'CWEType'}))
 
+
+class NonRegressionTC(CubicWebTC):
+
+    def test_has_text_security_cache_bug(self):
+        req = self.request()
+        self.create_user(req, 'user', ('users',))
+        aff1 = req.create_entity('Societe', nom=u'aff1')
+        aff2 = req.create_entity('Societe', nom=u'aff2')
+        self.commit()
+        with self.login('user', password='user'):
+            res = self.execute('Any X WHERE X has_text %(text)s', {'text': 'aff1'})
+            self.assertEqual(res.rows, [[aff1.eid]])
+            res = self.execute('Any X WHERE X has_text %(text)s', {'text': 'aff2'})
+            self.assertEqual(res.rows, [[aff2.eid]])
+
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_rqlannotation.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/server/test/unittest_rqlannotation.py	Tue Apr 10 17:03:19 2012 +0200
@@ -1,5 +1,5 @@
 # -*- coding: iso-8859-1 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -350,6 +350,12 @@
         self.assertEqual(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEqual(rqlst.defined_vars['Y']._q_invariant, True)
 
+
+    def test_has_text_security_cache_bug(self):
+        rqlst = self._prepare('Any X WHERE X has_text "toto" WITH X BEING '
+                              '(Any C WHERE C is Societe, C nom CS)')
+        self.assertTrue(rqlst.parent.has_text_query)
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/server/utils.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/server/utils.py	Tue Apr 10 17:03:19 2012 +0200
@@ -28,27 +28,49 @@
 
 from cubicweb.server import SOURCE_TYPES
 
-try:
-    from crypt import crypt
-except ImportError:
-    # crypt is not available (eg windows)
-    from cubicweb.md5crypt import crypt
+from passlib.utils import handlers as uh, to_hash_str
+from passlib.context import CryptContext
+
+from cubicweb.md5crypt import crypt as md5crypt
 
 
-def getsalt(chars=string.letters + string.digits):
-    """generate a random 2-character 'salt'"""
-    return choice(chars) + choice(chars)
+class CustomMD5Crypt(uh.HasSalt, uh.GenericHandler):
+    name = 'cubicweb-md5crypt'
+    setting_kwds = ("salt",)
+    min_salt_size = 0
+    max_salt_size = 8
+    salt_chars = uh.H64_CHARS
 
+    @classmethod
+    def from_string(cls, hash):
+        if hash is None:
+            raise ValueError("no hash specified")
+        if hash.count('$') != 1:
+            raise ValueError("invalid cubicweb-md5 hash")
+        salt = hash.split('$', 1)[0]
+        chk = hash.split('$', 1)[1]
+        return cls(salt=salt, checksum=chk, strict=True)
+
+    def to_string(self):
+        return to_hash_str(u'%s$%s' % (self.salt, self.checksum or u''))
+
+    def calc_checksum(self, secret):
+        return md5crypt(secret, self.salt.encode('ascii'))
+
+myctx = CryptContext(['sha512_crypt', CustomMD5Crypt, 'des_crypt'])
 
 def crypt_password(passwd, salt=None):
     """return the encrypted password using the given salt or a generated one
     """
-    if passwd is None:
-        return None
     if salt is None:
-        salt = getsalt()
-    return crypt(passwd, salt)
-
+        return myctx.encrypt(passwd)
+    # empty hash, accept any password for backwards compat
+    if salt == '':
+        return salt
+    if myctx.verify(passwd, salt):
+        return salt
+    # wrong password
+    return ''
 
 def cartesian_product(seqin):
     """returns a generator which returns the cartesian product of `seqin`
--- a/test/unittest_rqlrewrite.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/test/unittest_rqlrewrite.py	Tue Apr 10 17:03:19 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/test/unittest_schema.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/test/unittest_schema.py	Tue Apr 10 17:03:19 2012 +0200
@@ -348,6 +348,10 @@
         self.assertEqual(cstr.repo_check(self.session, 1, self.session.user.eid),
         None) # no validation error, constraint checked
 
+class WorkflowShemaTC(CubicWebTC):
+    def test_trinfo_default_format(self):
+         tr = self.session.user.cw_adapt_to('IWorkflowable').fire_transition('deactivate')
+         self.assertEqual(tr.comment_format, 'text/plain')
 
 if __name__ == '__main__':
     unittest_main()
--- a/web/data/cubicweb.ajax.js	Thu Mar 29 14:20:41 2012 +0200
+++ b/web/data/cubicweb.ajax.js	Tue Apr 10 17:03:19 2012 +0200
@@ -372,7 +372,7 @@
 /**
  * .. function:: loadRemote(url, form, reqtype='GET', sync=false)
  *
- * Asynchronously (unless `async` argument is set to false) load an url or path
+ * Asynchronously (unless `sync` argument is set to true) load an url or path
  * and return a deferred whose callbacks args are decoded according to the
  * Content-Type response header. `form` should be additional form params
  * dictionary, `reqtype` the HTTP request type (get 'GET' or 'POST').
--- a/web/data/cubicweb.css	Thu Mar 29 14:20:41 2012 +0200
+++ b/web/data/cubicweb.css	Tue Apr 10 17:03:19 2012 +0200
@@ -39,6 +39,12 @@
   padding: %(h3Padding)s;
 }
 
+
+h4 {
+  font-size: %(h4FontSize)s;
+}
+
+
 div.tabbedprimary + h1,
 h1.plain {
  border-bottom: none;
--- a/web/data/cubicweb.edition.js	Thu Mar 29 14:20:41 2012 +0200
+++ b/web/data/cubicweb.edition.js	Tue Apr 10 17:03:19 2012 +0200
@@ -435,11 +435,15 @@
         }
     }
     if (globalerrors.length) {
-        if (globalerrors.length == 1) {
-            var innernode = SPAN(null, globalerrors[0]);
-        } else {
-            var innernode = UL(null, $.map(globalerrors, partial(LI, null)));
-        }
+       if (globalerrors.length == 1) {
+           var innernode = SPAN(null, globalerrors[0]);
+       } else {
+           var linodes =[];
+           for(var i=0; i<globalerrors.length; i++){
+             linodes.push(LI(null, globalerrors[i]));
+           }
+           var innernode = UL(null, linodes);
+       }
         // insert DIV and innernode before the form
         var div = DIV({
             'class': "errorMessage",
--- a/web/data/cubicweb.old.css	Thu Mar 29 14:20:41 2012 +0200
+++ b/web/data/cubicweb.old.css	Tue Apr 10 17:03:19 2012 +0200
@@ -43,7 +43,7 @@
 }
 
 h4 {
-  font-size: 120%;
+  font-size: %(h4FontSize)s;
   margin: 0.2em 0px;
 }
 
--- a/web/data/uiprops.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/web/data/uiprops.py	Tue Apr 10 17:03:19 2012 +0200
@@ -89,20 +89,22 @@
 # h3 { font-size:1.30769em; }
 
 # h
-h1FontSize = '1.5em' # 18px
+h1FontSize = '2.3em' # 25.3833px
 h1Padding = '0 0 0.14em 0 '
 h1Margin = '0.8em 0 0.5em'
 h1Color = '#000'
 h1BorderBottomStyle = lazystr('0.06em solid %(h1Color)s')
 
-h2FontSize = '1.33333em'
-h2Padding = '0.4em 0 0.35em 0'
+h2FontSize = '2em' #
+h2Padding = '0.4em 0 0.35em 0' # 22.0667px
 h2Margin = '0'
 
-h3FontSize = '1.16667em'
+h3FontSize = '1.7em' #18.75px
 h3Padding = '0.5em 0 0.57em 0'
 h3Margin = '0'
 
+h4FontSize = '1.4em' # 15.45px
+
 # links
 aColor = '#e6820e'
 
--- a/web/form.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/web/form.py	Tue Apr 10 17:03:19 2012 +0200
@@ -91,7 +91,9 @@
         super(Form, self).__init__(req, rset=rset, row=row, col=col)
         self.fields = list(self.__class__._fields_)
         if mainform:
-            self.add_hidden(u'__form_id', kwargs.pop('formvid', self.__regid__))
+            formid = kwargs.pop('formvid', self.__regid__)
+            self.add_hidden(u'__form_id', formid)
+            self._posting = self._cw.form.get('__form_id') == formid
         for key, val in kwargs.iteritems():
             if key in controller.NAV_FORM_PARAMETERS:
                 self.add_hidden(key, val)
@@ -145,6 +147,16 @@
             return getattr(self, '_form_previous_values', {})
         return self.parent_form.form_previous_values
 
+    @property
+    def posting(self):
+        """return True if the form is being posted, False if it is being
+        generated.
+        """
+        # XXX check behaviour on regeneration after error
+        if self.parent_form is None:
+            return self._posting
+        return self.parent_form.posting
+
     @iclassmethod
     def _fieldsattr(cls_or_self):
         if isinstance(cls_or_self, type):
--- a/web/views/startup.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/web/views/startup.py	Tue Apr 10 17:03:19 2012 +0200
@@ -53,6 +53,7 @@
     add_etype_links = ()
     skip_startup_views = set( ('index', 'manage', 'schema', 'owl', 'changelog',
                                'systempropertiesform', 'propertiesform',
+                               'loggedout', 'login',
                                'cw.users-and-groups-management', 'cw.groups-management', 
                                'cw.users-management', 'cw.sources-management',
                                'siteinfo', 'info', 'registry', 'gc',
--- a/web/views/tableview.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/web/views/tableview.py	Tue Apr 10 17:03:19 2012 +0200
@@ -883,7 +883,7 @@
     default_column_renderer_class = EntityTableColRenderer
     columns = None # to be defined in concret class
 
-    def call(self, columns=None):
+    def call(self, columns=None, **kwargs):
         if columns is not None:
             self.columns = columns
         self.layout_render(self.w)
--- a/web/webconfig.py	Thu Mar 29 14:20:41 2012 +0200
+++ b/web/webconfig.py	Tue Apr 10 17:03:19 2012 +0200
@@ -21,7 +21,7 @@
 _ = unicode
 
 import os
-from os.path import join, exists, split
+from os.path import join, exists, split, isdir
 from warnings import warn
 
 from logilab.common.decorators import cached
@@ -408,7 +408,8 @@
         rdir, filename = split(rpath)
         if rdir:
             staticdir = join(staticdir, rdir)
-            os.makedirs(staticdir)
+            if not isdir(staticdir) and 'w' in mode:
+                os.makedirs(staticdir)
         return file(join(staticdir, filename), mode)
 
     def static_file_add(self, rpath, data):