backport stable

authorSylvain Th?nault <sylvain.thenault@logilab.fr>
changesetfdb796435d7b
branchdefault
phasepublic
hiddenno
parent revision#622fcca4fe00 [form renderer] refactor inline form renderer to ease overriding. Closes #2282662, #1a88d201675c [test] update create_user call
child revision#e1c05bf6fdeb [zmq] Implement a ZMQ-based Repository (closes #2290125)
files modified by this revision
__pkginfo__.py
bin/clone_deps.py
debian/control
devtools/data/xvfb-run.sh
doc/book/en/admin/config.rst
doc/book/en/admin/setup.rst
etwist/server.py
md5crypt.py
misc/migration/3.14.3_Any.py
misc/migration/3.14.7_Any.py
schemas/workflow.py
server/querier.py
server/rqlannotation.py
server/sources/native.py
server/test/unittest_postgres.py
server/test/unittest_querier.py
server/test/unittest_rqlannotation.py
server/utils.py
test/unittest_rqlrewrite.py
test/unittest_schema.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/form.py
web/views/startup.py
web/views/tableview.py
web/webconfig.py
# HG changeset patch
# User Sylvain Thénault <sylvain.thenault@logilab.fr>
# Date 1334070199 -7200
# Tue Apr 10 17:03:19 2012 +0200
# Node ID fdb796435d7b6afb4e5b68fdfb97468cdb1edb19
# Parent 622fcca4fe003803233394b5f7973bf8d97744e3
# Parent 1a88d201675c210dd9acee7497f35cfd7e1ba74d
backport stable

diff --git a/__pkginfo__.py b/__pkginfo__.py
@@ -52,10 +52,11 @@
1      'Twisted': '',
2      # XXX graphviz
3      # server dependencies
4      'logilab-database': '>= 1.8.2',
5      'pysqlite': '>= 2.5.5', # XXX install pysqlite2
6 +    'passlib': '',
7      }
8 
9  __recommends__ = {
10      'Pyro': '>= 3.9.1, < 4.0.0',
11      'PIL': '',                  # for captcha
diff --git a/bin/clone_deps.py b/bin/clone_deps.py
@@ -1,35 +1,35 @@
12  #!/usr/bin/python
13 -import os
14  import sys
15 -from subprocess import call, Popen, PIPE
16 -try:
17 -    from mercurial.dispatch import dispatch as hg_call
18 -except ImportError:
19 +
20 +from subprocess import call as sbp_call, Popen, PIPE
21 +from urllib import urlopen
22 +from os import path as osp, pardir, chdir
23 +
24 +
25 +def find_mercurial():
26 +    print "trying to find mercurial from the command line ..."
27      print '-' * 20
28 -    print "mercurial module is not reachable from this Python interpreter"
29 -    print "trying from command line ..."
30 -    tryhg = os.system('hg --version')
31 +    tryhg = sbp_call(['hg', '--version'])
32      if tryhg:
33 -        print 'mercurial seems to unavailable, please install it'
34 +        print 'mercurial seems to be unavailable, please install it'
35          raise
36 -    print 'found it, ok'
37      print '-' * 20
38      def hg_call(args):
39 -        call(['hg'] + args)
40 -from urllib import urlopen
41 -from os import path as osp, pardir
42 -from os.path import normpath, join, dirname
43 +        return sbp_call(['hg'] + args)
44 +
45 +    return hg_call
46 +
47 
48  BASE_URL = 'http://www.logilab.org/hg/'
49 
50  to_clone = ['fyzz', 'yams', 'rql',
51              'logilab/common', 'logilab/constraint', 'logilab/database',
52              'logilab/devtools', 'logilab/mtconverter',
53              'cubes/blog', 'cubes/calendar', 'cubes/card', 'cubes/comment',
54              'cubes/datafeed', 'cubes/email', 'cubes/file', 'cubes/folder',
55 -            'cubes/forgotpwd', 'cubes/keyword', 'cubes/link',
56 +            'cubes/forgotpwd', 'cubes/keyword', 'cubes/link', 'cubes/localperms',
57              'cubes/mailinglist', 'cubes/nosylist', 'cubes/person',
58              'cubes/preview', 'cubes/registration', 'cubes/rememberme',
59              'cubes/tag', 'cubes/vcsfile', 'cubes/zone']
60 
61  # a couple of functions to be used to explore available
@@ -63,24 +63,25 @@
62      elif len(sys.argv) == 2:
63          base_url = sys.argv[1]
64      else:
65          sys.stderr.write('usage %s [base_url]\n' %  sys.argv[0])
66          sys.exit(1)
67 +    hg_call = find_mercurial()
68      print len(to_clone), 'repositories will be cloned'
69 -    base_dir = normpath(join(dirname(__file__), pardir, pardir))
70 -    os.chdir(base_dir)
71 +    base_dir = osp.normpath(osp.join(osp.dirname(__file__), pardir, pardir))
72 +    chdir(base_dir)
73      not_updated = []
74      for repo in to_clone:
75          url = base_url + repo
76          if '/' not in repo:
77              target_path = repo
78          else:
79              assert repo.count('/') == 1, repo
80              directory, repo = repo.split('/')
81              if not osp.isdir(directory):
82                  os.mkdir(directory)
83 -                open(join(directory, '__init__.py'), 'w').close()
84 +                open(osp.join(directory, '__init__.py'), 'w').close()
85              target_path = osp.join(directory, repo)
86          if osp.exists(target_path):
87              print target_path, 'seems already cloned. Skipping it.'
88          else:
89              hg_call(['clone', '-U', url, target_path])
diff --git a/debian/control b/debian/control
@@ -33,11 +33,11 @@
90  Architecture: all
91  XB-Python-Version: ${python:Versions}
92  Conflicts: cubicweb-multisources
93  Replaces: cubicweb-multisources
94  Provides: cubicweb-multisources
95 -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
96 +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
97  Recommends: pyro (<< 4.0.0), cubicweb-documentation (= ${source:Version})
98  Suggests: python-zmq
99  Description: server part of the CubicWeb framework
100   CubicWeb is a semantic web application framework.
101   .
diff --git a/devtools/data/xvfb-run.sh b/devtools/data/xvfb-run.sh
@@ -1,23 +1,20 @@
102  #!/bin/sh
103 
104 -# $Id: xvfb-run 2027 2004-11-16 14:54:16Z branden $
105 -
106  # This script starts an instance of Xvfb, the "fake" X server, runs a command
107  # with that server available, and kills the X server when done.  The return
108  # value of the command becomes the return value of this script.
109  #
110  # If anyone is using this to build a Debian package, make sure the package
111 -# Build-Depends on xvfb, xbase-clients, and xfonts-base.
112 +# Build-Depends on xvfb and xauth.
113 
114  set -e
115 
116  PROGNAME=xvfb-run
117  SERVERNUM=99
118  AUTHFILE=
119  ERRORFILE=/dev/null
120 -STARTWAIT=3
121  XVFBARGS="-screen 0 640x480x8"
122  LISTENTCP="-nolisten tcp"
123  XAUTHPROTO=.
124 
125  # Query the terminal to establish a default number of columns to use for
@@ -60,12 +57,10 @@
126  -p PROTO  --xauth-protocol=PROTO    X authority protocol name to use
127                                      (default: xauth command's default)
128  -s ARGS   --server-args=ARGS        arguments (other than server number and
129                                      "-nolisten tcp") to pass to the Xvfb server
130                                      (default: "$XVFBARGS")
131 --w DELAY  --wait=DELAY              delay in seconds to wait for Xvfb to start
132 -                                    before running COMMAND (default: $STARTWAIT)
133  EOF
134  }
135 
136  # Find a free server number by looking at .X*-lock files in /tmp.
137  find_free_servernum() {
@@ -91,11 +86,11 @@
138              error "problem while cleaning up temporary directory"
139              exit 5
140          fi
141      fi
142      if [ -n "$XVFBPID" ]; then
143 -        kill $XVFBPID
144 +        kill "$XVFBPID"
145      fi
146  }
147 
148  # Parse the command line.
149  ARGS=$(getopt --options +ae:f:hn:lp:s:w: \
@@ -118,11 +113,11 @@
150          -h|--help) SHOWHELP="yes" ;;
151          -n|--server-num) SERVERNUM="$2"; shift ;;
152          -l|--listen-tcp) LISTENTCP="" ;;
153          -p|--xauth-protocol) XAUTHPROTO="$2"; shift ;;
154          -s|--server-args) XVFBARGS="$2"; shift ;;
155 -        -w|--wait) STARTWAIT="$2"; shift ;;
156 +        -w|--wait) shift ;;
157          --) shift; break ;;
158          *) error "internal error; getopt permitted \"$1\" unexpectedly"
159             exit 6
160             ;;
161      esac
@@ -161,23 +156,27 @@
162  while [ $tries -gt 0 ]; do
163      tries=$(( $tries - 1 ))
164      XAUTHORITY=$AUTHFILE xauth source - << EOF >>"$ERRORFILE" 2>&1
165  add :$SERVERNUM $XAUTHPROTO $MCOOKIE
166  EOF
167 -    XAUTHORITY=$AUTHFILE Xvfb ":$SERVERNUM" $XVFBARGS $LISTENTCP >>"$ERRORFILE" 2>&1 &
168 +    # handle SIGUSR1 so Xvfb knows to send a signal when it's ready to accept
169 +    # connections
170 +    trap : USR1
171 +    (trap '' USR1; XAUTHORITY=$AUTHFILE exec Xvfb ":$SERVERNUM" $XVFBARGS $LISTENTCP >>"$ERRORFILE" 2>&1) &
172      XVFBPID=$!
173 
174 -    sleep "$STARTWAIT"
175 +    wait || :
176      if kill -0 $XVFBPID 2>/dev/null; then
177          break
178      elif [ -n "$AUTONUM" ]; then
179          # The display is in use so try another one (if '-a' was specified).
180          SERVERNUM=$((SERVERNUM + 1))
181          SERVERNUM=$(find_free_servernum)
182          continue
183      fi
184      error "Xvfb failed to start" >&2
185 +    XVFBPID=
186      exit 1
187  done
188 
189  # Start the command and save its exit status.
190  set +e
diff --git a/doc/book/en/admin/config.rst b/doc/book/en/admin/config.rst
@@ -68,57 +68,57 @@
191 
192  If you run postgres on another host than the |cubicweb| repository, you should
193  install the `postgresql-client` package on the |cubicweb| host, and others on the
194  database host.
195 
196 -.. Note::
197 +Database cluster
198 +++++++++++++++++
199 
200 -    If you already have an existing cluster and PostgreSQL server running, you do
201 -    not need to execute the initilization step of your PostgreSQL database unless
202 -    you want a specific cluster for |cubicweb| databases or if your existing
203 -    cluster doesn't use the UTF8 encoding (see note below).
204 +If you already have an existing cluster and PostgreSQL server running, you do
205 +not need to execute the initilization step of your PostgreSQL database unless
206 +you want a specific cluster for |cubicweb| databases or if your existing
207 +cluster doesn't use the UTF8 encoding (see note below).
208 
209 -* First, initialize a PostgreSQL cluster with the command ``initdb``::
210 +To initialize a PostgreSQL cluster, use the command ``initdb``::
211 
212      $ initdb -E UTF8 -D /path/to/pgsql
213 
214 -  Notice the encoding specification. This is necessary since |cubicweb| usually
215 -  want UTF8 encoded database. If you use a cluster with the wrong encoding, you'll
216 -  get error like::
217 -
218 -    new encoding (UTF8) is incompatible with the encoding of the template database (SQL_ASCII)
219 -    HINT:  Use the same encoding as in the template database, or use template0 as template.
220 -
221 -
222 -  Once initialized, start the database server PostgreSQL with the command::
223 -
224 -    $ postgres -D /path/to/psql
225 -
226 -  If you cannot execute this command due to permission issues, please make sure
227 -  that your username has write access on the database.  ::
228 -
229 -    $ chown username /path/to/pgsql
230 +Notice the encoding specification. This is necessary since |cubicweb| usually
231 +want UTF8 encoded database. If you use a cluster with the wrong encoding, you'll
232 +get error like::
233 
234 -* The database authentication can be either set to `ident sameuser` or `md5`.  If
235 -  set to `md5`, make sure to use an existing user of your database.  If set to
236 -  `ident sameuser`, make sure that your client's operating system user name has a
237 -  matching user in the database. If not, please do as follow to create a user::
238 -
239 -    $ su
240 -    $ su - postgres
241 -    $ createuser -s -P username
242 +  new encoding (UTF8) is incompatible with the encoding of the template database (SQL_ASCII)
243 +  HINT:  Use the same encoding as in the template database, or use template0 as template.
244 
245 -  The option `-P` (for password prompt), will encrypt the password with the
246 -  method set in the configuration file :file:`pg_hba.conf`.  If you do not use this
247 -  option `-P`, then the default value will be null and you will need to set it
248 -  with::
249 +Once initialized, start the database server PostgreSQL with the command::
250 
251 -    $ su postgres -c "echo ALTER USER username WITH PASSWORD 'userpasswd' | psql"
252 +  $ postgres -D /path/to/psql
253 
254 -.. Note::
255 -    The authentication method can be configured in file:`pg_hba.conf`.
256 +If you cannot execute this command due to permission issues, please make sure
257 +that your username has write access on the database.  ::
258 
259 +  $ chown username /path/to/pgsql
260 +
261 +Database authentication
262 ++++++++++++++++++++++++
263 +
264 +The database authentication is configured in `pg_hba.conf`. It can be either set
265 +to `ident sameuser` or `md5`.  If set to `md5`, make sure to use an existing
266 +user of your database.  If set to `ident sameuser`, make sure that your client's
267 +operating system user name has a matching user in the database. If not, please
268 +do as follow to create a user::
269 +
270 +  $ su
271 +  $ su - postgres
272 +  $ createuser -s -P username
273 +
274 +The option `-P` (for password prompt), will encrypt the password with the
275 +method set in the configuration file :file:`pg_hba.conf`.  If you do not use this
276 +option `-P`, then the default value will be null and you will need to set it
277 +with::
278 +
279 +  $ su postgres -c "echo ALTER USER username WITH PASSWORD 'userpasswd' | psql"
280 
281  The above login/password will be requested when you will create an instance with
282  `cubicweb-ctl create` to initialize the database of your instance.
283 
284  Notice that the `cubicweb-ctl db-create` does database initialization that
@@ -147,11 +147,10 @@
285  To install the tsearch plain-text index extension on postgres prior to 8.3, run::
286 
287      cat /usr/share/postgresql/8.X/contrib/tsearch2.sql | psql -U username template1
288 
289 
290 -
291  .. _MySqlConfiguration:
292 
293  MySql
294  ~~~~~
295 
@@ -194,24 +193,25 @@
296 
297   ALTER DATABASE <databasename> SET READ_COMMITTED_SNAPSHOT ON;
298 
299  The ALTER DATABASE command above requires some permissions that your
300  user may not have. In that case you will have to ask your local DBA to
301 -run the query for you. 
302 +run the query for you.
303 
304  You can check that the setting is correct by running the following
305  query which must return '1'::
306 
307 -   SELECT is_read_committed_snapshot_on 
308 +   SELECT is_read_committed_snapshot_on
309       FROM sys.databases WHERE name='<databasename>';
310 
311 
312 
313  .. _SQLiteConfiguration:
314 
315  SQLite
316  ~~~~~~
317 +
318  SQLite has the great advantage of requiring almost no configuration. Simply
319  use 'sqlite' as db-driver, and set path to the dabase as db-name. Don't specify
320  anything for db-user and db-password, they will be ignore anyway.
321 
322  .. Note::
@@ -224,10 +224,11 @@
323  Pyro configuration
324  ------------------
325 
326  Pyro name server
327  ~~~~~~~~~~~~~~~~
328 +
329  If you want to use Pyro to access your instance remotely, or to have multi-source
330  or distributed configuration, it is required to have a Pyro name server running
331  on your network. By default it is detected by a broadcast request, but you can
332  specify a location in the instance's configuration file.
333 
diff --git a/doc/book/en/admin/setup.rst b/doc/book/en/admin/setup.rst
@@ -61,13 +61,13 @@
334 
335  For Ubuntu Lucid (Long Term Support) and newer::
336 
337    deb http://download.logilab.org/production/ lucid/
338 
339 -  Note that for Ubuntu Maverick and newer, you shall use the `lucid`
340 -  repository and install the ``libgecode19`` package from `lucid
341 -  universe <http://packages.ubuntu.com/lucid/libgecode19>`_.
342 +Note that for Ubuntu Maverick and newer, you shall use the `lucid`
343 +repository and install the ``libgecode19`` package from `lucid
344 +universe <http://packages.ubuntu.com/lucid/libgecode19>`_.
345 
346  The repositories are signed with the `Logilab's gnupg key`_. You can download
347  and register the key to avoid warnings::
348 
349    wget -q http://download.logilab.org/logilab-dists-key.asc -O- | sudo apt-key add -
diff --git a/etwist/server.py b/etwist/server.py
@@ -1,6 +1,6 @@
350 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
351 +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
352  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
353  #
354  # This file is part of CubicWeb.
355  #
356  # CubicWeb is free software: you can redistribute it and/or modify it under the
diff --git a/md5crypt.py b/md5crypt.py
@@ -49,22 +49,20 @@
357          n = n - 1
358          ret = ret + ITOA64[v & 0x3f]
359          v = v >> 6
360      return ret
361 
362 -def crypt(pw, salt, magic=None):
363 +def crypt(pw, salt):
364      if isinstance(pw, unicode):
365          pw = pw.encode('utf-8')
366 -    if magic is None:
367 -        magic = MAGIC
368      # Take care of the magic string if present
369 -    if salt[:len(magic)] == magic:
370 -        salt = salt[len(magic):]
371 +    if salt.startswith(MAGIC):
372 +        salt = salt[len(MAGIC):]
373      # salt can have up to 8 characters:
374      salt = salt.split('$', 1)[0]
375      salt = salt[:8]
376 -    ctx = pw + magic + salt
377 +    ctx = pw + MAGIC + salt
378      final = md5(pw + salt + pw).digest()
379      for pl in xrange(len(pw), 0, -16):
380          if pl > 16:
381              ctx = ctx + final[:16]
382          else:
@@ -112,6 +110,6 @@
383                             |(int(ord(final[15]))), 4)
384      passwd = passwd + to64((int(ord(final[4])) << 16)
385                             |(int(ord(final[10])) << 8)
386                             |(int(ord(final[5]))), 4)
387      passwd = passwd + to64((int(ord(final[11]))), 2)
388 -    return salt + '$' + passwd
389 +    return passwd
diff --git a/misc/migration/3.14.3_Any.py b/misc/migration/3.14.7_Any.py
@@ -1,2 +1,4 @@
390 -# keep the same behavior on existing instance but use the new one on new instance.
391 -config['https-deny-anonymous'] = True
392 +# migrate default format for TriInfo `comment_format` attribute
393 +sync_schema_props_perms('TrInfo')
394 +
395 +commit()
diff --git a/schemas/workflow.py b/schemas/workflow.py
@@ -183,11 +183,11 @@
396      from_state = SubjectRelation('State', cardinality='1*', inlined=True)
397      to_state = SubjectRelation('State', cardinality='1*', inlined=True)
398      # make by_transition optional because we want to allow managers to set
399      # entity into an arbitrary state without having to respect wf transition
400      by_transition = SubjectRelation('BaseTransition', cardinality='?*')
401 -    comment = RichString(fulltextindexed=True)
402 +    comment = RichString(fulltextindexed=True, default_format='text/plain')
403      tr_count = Int(description='autocomputed attribute used to ensure transition coherency')
404      # get actor and date time using owned_by and creation_date
405 
406  class from_state(RelationType):
407      __permissions__ = RO_REL_PERMS.copy()
diff --git a/server/querier.py b/server/querier.py
@@ -1,6 +1,6 @@
408 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
409 +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
410  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
411  #
412  # This file is part of CubicWeb.
413  #
414  # CubicWeb is free software: you can redistribute it and/or modify it under the
@@ -415,11 +415,11 @@
415                          continue
416                      for rqlexpr in rqlexprs:
417                          if rqlexpr.check(session, eid):
418                              break
419                      else:
420 -                        raise Unauthorized()
421 +                        raise Unauthorized('No read acces on %r with eid %i.' % (var, eid))
422                  restricted_vars.update(localcheck)
423                  localchecks.setdefault(tuple(localcheck.iteritems()), []).append(solution)
424          # raise Unautorized exception if the user can't access to any solution
425          if not newsolutions:
426              raise Unauthorized('\n'.join(msgs))
@@ -721,11 +721,11 @@
427              # bother modifying it. This is not necessary on write queries since
428              # a new syntax tree is built from them.
429              rqlst = rqlst.copy()
430              self._annotate(rqlst)
431              if args:
432 -                 # different SQL generated when some argument is None or not (IS
433 +                # different SQL generated when some argument is None or not (IS
434                  # NULL). This should be considered when computing sql cache key
435                  cachekey += tuple(sorted([k for k,v in args.iteritems()
436                                            if v is None]))
437          # make an execution plan
438          plan = self.plan_factory(rqlst, args, session)
diff --git a/server/rqlannotation.py b/server/rqlannotation.py
@@ -1,6 +1,6 @@
439 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
440 +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
441  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
442  #
443  # This file is part of CubicWeb.
444  #
445  # CubicWeb is free software: you can redistribute it and/or modify it under the
@@ -26,16 +26,17 @@
446  from rql import BadRQLQuery
447  from rql.nodes import Relation, VariableRef, Constant, Variable, Or, Exists
448  from rql.utils import common_parent
449 
450  def _annotate_select(annotator, rqlst):
451 +    has_text_query = False
452      for subquery in rqlst.with_:
453 -        annotator._annotate_union(subquery.query)
454 +        if annotator._annotate_union(subquery.query):
455 +            has_text_query = True
456      #if server.DEBUG:
457      #    print '-------- sql annotate', repr(rqlst)
458      getrschema = annotator.schema.rschema
459 -    has_text_query = False
460      need_distinct = rqlst.distinct
461      for rel in rqlst.iget_nodes(Relation):
462          if getrschema(rel.r_type).symmetric and not isinstance(rel.parent, Exists):
463              for vref in rel.iget_nodes(VariableRef):
464                  stinfo = vref.variable.stinfo
@@ -152,10 +153,15 @@
465                      # rhs variable's scope (since it's retrieved from lhs's table)
466                      sstinfo = principal.children[0].variable.stinfo
467                      sstinfo['scope'] = common_parent(sstinfo['scope'], stinfo['scope']).scope
468              except CantSelectPrincipal:
469                  stinfo['invariant'] = False
470 +    # see unittest_rqlannotation. test_has_text_security_cache_bug
471 +    # XXX probably more to do, but yet that work without more...
472 +    for col_alias in rqlst.aliases.itervalues():
473 +        if col_alias.stinfo.get('ftirels'):
474 +            has_text_query = True
475      rqlst.need_distinct = need_distinct
476      return has_text_query
477 
478 
479 
@@ -270,12 +276,11 @@
480          rqlst.has_text_query = self._annotate_union(rqlst)
481 
482      def _annotate_union(self, union):
483          has_text_query = False
484          for select in union.children:
485 -            htq = _annotate_select(self, select)
486 -            if htq:
487 +            if _annotate_select(self, select):
488                  has_text_query = True
489          return has_text_query
490 
491      def is_ambiguous(self, var):
492          # ignore has_text relation when we know it will be used as principal.
diff --git a/server/sources/native.py b/server/sources/native.py
@@ -1624,11 +1624,11 @@
493                  raise AuthenticationError('bad login')
494              if pwd is None:
495                  # if pwd is None but a password is provided, something is wrong
496                  raise AuthenticationError('bad password')
497              # passwords are stored using the Bytes type, so we get a StringIO
498 -            args['pwd'] = Binary(crypt_password(password, pwd.getvalue()[:2]))
499 +            args['pwd'] = Binary(crypt_password(password, pwd.getvalue()))
500          # get eid from login and (crypted) password
501          rset = self.source.syntax_tree_search(session, self._auth_rqlst, args)
502          try:
503              return rset[0][0]
504          except IndexError:
diff --git a/server/test/unittest_postgres.py b/server/test/unittest_postgres.py
@@ -1,22 +1,22 @@
505  # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
506  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
507  #
508 -# This file is part of Logilab-common.
509 +# This file is part of CubicWeb.
510  #
511 -# Logilab-common is free software: you can redistribute it and/or modify it
512 -# under the terms of the GNU Lesser General Public License as published by the
513 -# Free Software Foundation, either version 2.1 of the License, or (at your
514 -# option) any later version.
515 +# CubicWeb is free software: you can redistribute it and/or modify it under the
516 +# terms of the GNU Lesser General Public License as published by the Free
517 +# Software Foundation, either version 2.1 of the License, or (at your option)
518 +# any later version.
519  #
520 -# Logilab-common is distributed in the hope that it will be useful, but WITHOUT
521 +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
522  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
523  # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
524  # details.
525  #
526  # You should have received a copy of the GNU Lesser General Public License along
527 -# with Logilab-common.  If not, see <http://www.gnu.org/licenses/>.
528 +# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
529 
530  from __future__ import with_statement
531 
532  import socket
533  from datetime import datetime
diff --git a/server/test/unittest_querier.py b/server/test/unittest_querier.py
@@ -1,7 +1,7 @@
534  # -*- coding: iso-8859-1 -*-
535 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
536 +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
537  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
538  #
539  # This file is part of CubicWeb.
540  #
541  # CubicWeb is free software: you can redistribute it and/or modify it under the
@@ -16,21 +16,23 @@
542  #
543  # You should have received a copy of the GNU Lesser General Public License along
544  # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
545  """unit tests for modules cubicweb.server.querier and cubicweb.server.ssplanner
546  """
547 +from __future__ import with_statement
548 +
549  from datetime import date, datetime, timedelta, tzinfo
550 
551  from logilab.common.testlib import TestCase, unittest_main
552  from rql import BadRQLQuery, RQLSyntaxError
553 
554  from cubicweb import QueryError, Unauthorized, Binary
555  from cubicweb.server.sqlutils import SQL_PREFIX
556  from cubicweb.server.utils import crypt_password
557  from cubicweb.server.sources.native import make_schema
558  from cubicweb.devtools import get_test_db_handler, TestServerConfiguration
559 -
560 +from cubicweb.devtools.testlib import CubicWebTC
561  from cubicweb.devtools.repotest import tuplify, BaseQuerierTC
562  from unittest_session import Variable
563 
564  class FixedOffset(tzinfo):
565      def __init__(self, hours=0):
@@ -68,28 +70,28 @@
566          self.assertEqual(make_schema((Variable('A'), Variable('B')), solution,
567                                        'table0', TYPEMAP),
568                            ('C0 text,C1 integer', {'A': 'table0.C0', 'B': 'table0.C1'}))
569 
570 
571 -def setUpModule(*args):
572 +def setUpClass(cls, *args):
573      global repo, cnx
574      config = TestServerConfiguration(apphome=UtilsTC.datadir)
575      handler = get_test_db_handler(config)
576      handler.build_db_cache()
577      repo, cnx = handler.get_repo_and_cnx()
578 +    cls.repo = repo
579 
580 -def tearDownModule(*args):
581 +def tearDownClass(cls, *args):
582      global repo, cnx
583      cnx.close()
584      repo.shutdown()
585      del repo, cnx
586 
587 
588  class UtilsTC(BaseQuerierTC):
589 -    def setUp(self):
590 -        self.__class__.repo = repo
591 -        super(UtilsTC, self).setUp()
592 +    setUpClass = classmethod(setUpClass)
593 +    tearDownClass = classmethod(tearDownClass)
594 
595      def get_max_eid(self):
596          # no need for cleanup here
597          return None
598      def cleanup(self):
@@ -240,13 +242,12 @@
599          rset = self.execute('Any %(x)s', {'x': u'str'})
600          self.assertEqual(rset.description[0][0], 'String')
601 
602 
603  class QuerierTC(BaseQuerierTC):
604 -    def setUp(self):
605 -        self.__class__.repo = repo
606 -        super(QuerierTC, self).setUp()
607 +    setUpClass = classmethod(setUpClass)
608 +    tearDownClass = classmethod(tearDownClass)
609 
610      def test_encoding_pb(self):
611          self.assertRaises(RQLSyntaxError, self.execute,
612                            'Any X WHERE X is CWRType, X name "�wned_by"')
613 
@@ -1257,11 +1258,11 @@
614                            self.execute, "Any P WHERE X is CWUser, X login 'bob', X upassword P")
615          cursor = self.cnxset['system']
616          cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'"
617                         % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX))
618          passwd = str(cursor.fetchone()[0])
619 -        self.assertEqual(passwd, crypt_password('toto', passwd[:2]))
620 +        self.assertEqual(passwd, crypt_password('toto', passwd))
621          rset = self.execute("Any X WHERE X is CWUser, X login 'bob', X upassword %(pwd)s",
622                              {'pwd': Binary(passwd)})
623          self.assertEqual(len(rset.rows), 1)
624          self.assertEqual(rset.description, [('CWUser',)])
625 
@@ -1272,11 +1273,11 @@
626                              {'pwd': 'tutu'})
627          cursor = self.cnxset['system']
628          cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'"
629                         % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX))
630          passwd = str(cursor.fetchone()[0])
631 -        self.assertEqual(passwd, crypt_password('tutu', passwd[:2]))
632 +        self.assertEqual(passwd, crypt_password('tutu', passwd))
633          rset = self.execute("Any X WHERE X is CWUser, X login 'bob', X upassword %(pwd)s",
634                              {'pwd': Binary(passwd)})
635          self.assertEqual(len(rset.rows), 1)
636          self.assertEqual(rset.description, [('CWUser',)])
637 
@@ -1499,7 +1500,22 @@
638      def test_nonregr_sql_cache(self):
639          # different SQL generated when 'name' is None or not (IS NULL).
640          self.assertFalse(self.execute('Any X WHERE X is CWEType, X name %(name)s', {'name': None}))
641          self.assertTrue(self.execute('Any X WHERE X is CWEType, X name %(name)s', {'name': 'CWEType'}))
642 
643 +
644 +class NonRegressionTC(CubicWebTC):
645 +
646 +    def test_has_text_security_cache_bug(self):
647 +        req = self.request()
648 +        self.create_user(req, 'user', ('users',))
649 +        aff1 = req.create_entity('Societe', nom=u'aff1')
650 +        aff2 = req.create_entity('Societe', nom=u'aff2')
651 +        self.commit()
652 +        with self.login('user', password='user'):
653 +            res = self.execute('Any X WHERE X has_text %(text)s', {'text': 'aff1'})
654 +            self.assertEqual(res.rows, [[aff1.eid]])
655 +            res = self.execute('Any X WHERE X has_text %(text)s', {'text': 'aff2'})
656 +            self.assertEqual(res.rows, [[aff2.eid]])
657 +
658  if __name__ == '__main__':
659      unittest_main()
diff --git a/server/test/unittest_rqlannotation.py b/server/test/unittest_rqlannotation.py
@@ -1,7 +1,7 @@
660  # -*- coding: iso-8859-1 -*-
661 -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
662 +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
663  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
664  #
665  # This file is part of CubicWeb.
666  #
667  # CubicWeb is free software: you can redistribute it and/or modify it under the
@@ -348,8 +348,14 @@
668      def test_remove_from_deleted_source_2(self):
669          rqlst = self._prepare('Note X WHERE X eid IN (999998, 999999), NOT X cw_source Y')
670          self.assertEqual(rqlst.defined_vars['X']._q_invariant, False)
671          self.assertEqual(rqlst.defined_vars['Y']._q_invariant, True)
672 
673 +
674 +    def test_has_text_security_cache_bug(self):
675 +        rqlst = self._prepare('Any X WHERE X has_text "toto" WITH X BEING '
676 +                              '(Any C WHERE C is Societe, C nom CS)')
677 +        self.assertTrue(rqlst.parent.has_text_query)
678 +
679  if __name__ == '__main__':
680      from logilab.common.testlib import unittest_main
681      unittest_main()
diff --git a/server/utils.py b/server/utils.py
@@ -26,31 +26,53 @@
682  from getpass import getpass
683  from random import choice
684 
685  from cubicweb.server import SOURCE_TYPES
686 
687 -try:
688 -    from crypt import crypt
689 -except ImportError:
690 -    # crypt is not available (eg windows)
691 -    from cubicweb.md5crypt import crypt
692 +from passlib.utils import handlers as uh, to_hash_str
693 +from passlib.context import CryptContext
694 +
695 +from cubicweb.md5crypt import crypt as md5crypt
696 
697 
698 -def getsalt(chars=string.letters + string.digits):
699 -    """generate a random 2-character 'salt'"""
700 -    return choice(chars) + choice(chars)
701 +class CustomMD5Crypt(uh.HasSalt, uh.GenericHandler):
702 +    name = 'cubicweb-md5crypt'
703 +    setting_kwds = ("salt",)
704 +    min_salt_size = 0
705 +    max_salt_size = 8
706 +    salt_chars = uh.H64_CHARS
707 
708 +    @classmethod
709 +    def from_string(cls, hash):
710 +        if hash is None:
711 +            raise ValueError("no hash specified")
712 +        if hash.count('$') != 1:
713 +            raise ValueError("invalid cubicweb-md5 hash")
714 +        salt = hash.split('$', 1)[0]
715 +        chk = hash.split('$', 1)[1]
716 +        return cls(salt=salt, checksum=chk, strict=True)
717 +
718 +    def to_string(self):
719 +        return to_hash_str(u'%s$%s' % (self.salt, self.checksum or u''))
720 +
721 +    def calc_checksum(self, secret):
722 +        return md5crypt(secret, self.salt.encode('ascii'))
723 +
724 +myctx = CryptContext(['sha512_crypt', CustomMD5Crypt, 'des_crypt'])
725 
726  def crypt_password(passwd, salt=None):
727      """return the encrypted password using the given salt or a generated one
728      """
729 -    if passwd is None:
730 -        return None
731      if salt is None:
732 -        salt = getsalt()
733 -    return crypt(passwd, salt)
734 -
735 +        return myctx.encrypt(passwd)
736 +    # empty hash, accept any password for backwards compat
737 +    if salt == '':
738 +        return salt
739 +    if myctx.verify(passwd, salt):
740 +        return salt
741 +    # wrong password
742 +    return ''
743 
744  def cartesian_product(seqin):
745      """returns a generator which returns the cartesian product of `seqin`
746 
747      for more details, see :
diff --git a/test/unittest_rqlrewrite.py b/test/unittest_rqlrewrite.py
@@ -1,6 +1,6 @@
748 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
749 +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
750  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
751  #
752  # This file is part of CubicWeb.
753  #
754  # CubicWeb is free software: you can redistribute it and/or modify it under the
diff --git a/test/unittest_schema.py b/test/unittest_schema.py
@@ -346,8 +346,12 @@
755          anoneid = self.execute('Any X WHERE X login "anon"')[0][0]
756          self.assertRaises(ValidationError, cstr.repo_check, self.session, 1, 'rel', anoneid)
757          self.assertEqual(cstr.repo_check(self.session, 1, self.session.user.eid),
758          None) # no validation error, constraint checked
759 
760 +class WorkflowShemaTC(CubicWebTC):
761 +    def test_trinfo_default_format(self):
762 +         tr = self.session.user.cw_adapt_to('IWorkflowable').fire_transition('deactivate')
763 +         self.assertEqual(tr.comment_format, 'text/plain')
764 
765  if __name__ == '__main__':
766      unittest_main()
diff --git a/web/data/cubicweb.ajax.js b/web/data/cubicweb.ajax.js
@@ -370,11 +370,11 @@
767  }
768 
769  /**
770   * .. function:: loadRemote(url, form, reqtype='GET', sync=false)
771   *
772 - * Asynchronously (unless `async` argument is set to false) load an url or path
773 + * Asynchronously (unless `sync` argument is set to true) load an url or path
774   * and return a deferred whose callbacks args are decoded according to the
775   * Content-Type response header. `form` should be additional form params
776   * dictionary, `reqtype` the HTTP request type (get 'GET' or 'POST').
777   */
778  function loadRemote(url, form, reqtype, sync) {
diff --git a/web/data/cubicweb.css b/web/data/cubicweb.css
@@ -37,10 +37,16 @@
779  h3 {
780    font-size: %(h3FontSize)s;
781    padding: %(h3Padding)s;
782  }
783 
784 +
785 +h4 {
786 +  font-size: %(h4FontSize)s;
787 +}
788 +
789 +
790  div.tabbedprimary + h1,
791  h1.plain {
792   border-bottom: none;
793  }
794 
diff --git a/web/data/cubicweb.edition.js b/web/data/cubicweb.edition.js
@@ -433,15 +433,19 @@
795                  globalerrors.push(_(fieldname) + ' : ' + errmsg);
796              }
797          }
798      }
799      if (globalerrors.length) {
800 -        if (globalerrors.length == 1) {
801 -            var innernode = SPAN(null, globalerrors[0]);
802 -        } else {
803 -            var innernode = UL(null, $.map(globalerrors, partial(LI, null)));
804 -        }
805 +       if (globalerrors.length == 1) {
806 +           var innernode = SPAN(null, globalerrors[0]);
807 +       } else {
808 +           var linodes =[];
809 +           for(var i=0; i<globalerrors.length; i++){
810 +             linodes.push(LI(null, globalerrors[i]));
811 +           }
812 +           var innernode = UL(null, linodes);
813 +       }
814          // insert DIV and innernode before the form
815          var div = DIV({
816              'class': "errorMessage",
817              'id': formid + 'ErrorMessage'
818          });
diff --git a/web/data/cubicweb.old.css b/web/data/cubicweb.old.css
@@ -41,11 +41,11 @@
819  h3 {
820    font-size: %(h3FontSize)s;
821  }
822 
823  h4 {
824 -  font-size: 120%;
825 +  font-size: %(h4FontSize)s;
826    margin: 0.2em 0px;
827  }
828 
829  h5 {
830    font-size:110%;
diff --git a/web/data/uiprops.py b/web/data/uiprops.py
@@ -87,24 +87,26 @@
831  # h1 { font-size:2.11538em; }
832  # h2 { font-size:1.61538em; }
833  # h3 { font-size:1.30769em; }
834 
835  # h
836 -h1FontSize = '1.5em' # 18px
837 +h1FontSize = '2.3em' # 25.3833px
838  h1Padding = '0 0 0.14em 0 '
839  h1Margin = '0.8em 0 0.5em'
840  h1Color = '#000'
841  h1BorderBottomStyle = lazystr('0.06em solid %(h1Color)s')
842 
843 -h2FontSize = '1.33333em'
844 -h2Padding = '0.4em 0 0.35em 0'
845 +h2FontSize = '2em' #
846 +h2Padding = '0.4em 0 0.35em 0' # 22.0667px
847  h2Margin = '0'
848 
849 -h3FontSize = '1.16667em'
850 +h3FontSize = '1.7em' #18.75px
851  h3Padding = '0.5em 0 0.57em 0'
852  h3Margin = '0'
853 
854 +h4FontSize = '1.4em' # 15.45px
855 +
856  # links
857  aColor = '#e6820e'
858 
859 
860  # page frame
diff --git a/web/form.py b/web/form.py
@@ -89,11 +89,13 @@
861      def __init__(self, req, rset=None, row=None, col=None,
862                   submitmsg=None, mainform=True, **kwargs):
863          super(Form, self).__init__(req, rset=rset, row=row, col=col)
864          self.fields = list(self.__class__._fields_)
865          if mainform:
866 -            self.add_hidden(u'__form_id', kwargs.pop('formvid', self.__regid__))
867 +            formid = kwargs.pop('formvid', self.__regid__)
868 +            self.add_hidden(u'__form_id', formid)
869 +            self._posting = self._cw.form.get('__form_id') == formid
870          for key, val in kwargs.iteritems():
871              if key in controller.NAV_FORM_PARAMETERS:
872                  self.add_hidden(key, val)
873              elif key == 'redirect_path':
874                  self.add_hidden(u'__redirectpath', val)
@@ -143,10 +145,20 @@
875          if self.parent_form is None:
876              # unset if restore_previous_post has not be called
877              return getattr(self, '_form_previous_values', {})
878          return self.parent_form.form_previous_values
879 
880 +    @property
881 +    def posting(self):
882 +        """return True if the form is being posted, False if it is being
883 +        generated.
884 +        """
885 +        # XXX check behaviour on regeneration after error
886 +        if self.parent_form is None:
887 +            return self._posting
888 +        return self.parent_form.posting
889 +
890      @iclassmethod
891      def _fieldsattr(cls_or_self):
892          if isinstance(cls_or_self, type):
893              fields = cls_or_self._fields_
894          else:
diff --git a/web/views/startup.py b/web/views/startup.py
@@ -51,10 +51,11 @@
895      title = _('manage')
896      http_cache_manager = httpcache.EtagHTTPCacheManager
897      add_etype_links = ()
898      skip_startup_views = set( ('index', 'manage', 'schema', 'owl', 'changelog',
899                                 'systempropertiesform', 'propertiesform',
900 +                               'loggedout', 'login',
901                                 'cw.users-and-groups-management', 'cw.groups-management', 
902                                 'cw.users-management', 'cw.sources-management',
903                                 'siteinfo', 'info', 'registry', 'gc',
904                                 'tree') )
905 
diff --git a/web/views/tableview.py b/web/views/tableview.py
@@ -881,11 +881,11 @@
906      """
907      __abstract__ = True
908      default_column_renderer_class = EntityTableColRenderer
909      columns = None # to be defined in concret class
910 
911 -    def call(self, columns=None):
912 +    def call(self, columns=None, **kwargs):
913          if columns is not None:
914              self.columns = columns
915          self.layout_render(self.w)
916 
917      @property
diff --git a/web/webconfig.py b/web/webconfig.py
@@ -19,11 +19,11 @@
918 
919  __docformat__ = "restructuredtext en"
920  _ = unicode
921 
922  import os
923 -from os.path import join, exists, split
924 +from os.path import join, exists, split, isdir
925  from warnings import warn
926 
927  from logilab.common.decorators import cached
928  from logilab.common.deprecation import deprecated
929 
@@ -406,11 +406,12 @@
930      def static_file_open(self, rpath, mode='wb'):
931          staticdir = self.static_directory
932          rdir, filename = split(rpath)
933          if rdir:
934              staticdir = join(staticdir, rdir)
935 -            os.makedirs(staticdir)
936 +            if not isdir(staticdir) and 'w' in mode:
937 +                os.makedirs(staticdir)
938          return file(join(staticdir, filename), mode)
939 
940      def static_file_add(self, rpath, data):
941          stream = self.static_file_open(rpath)
942          stream.write(data)