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