##// END OF EJS Templates
added some fixes to LDAP form re-submition, new simples ldap-settings getter....
marcink -
r1292:c0335c1d beta
parent child Browse files
Show More
@@ -1,532 +1,540 b''
1 1 .. _setup:
2 2
3 3 Setup
4 4 =====
5 5
6 6
7 7 Setting up RhodeCode
8 8 --------------------------
9 9
10 10 First, you will need to create a RhodeCode configuration file. Run the following
11 11 command to do this::
12 12
13 13 paster make-config RhodeCode production.ini
14 14
15 15 - This will create the file `production.ini` in the current directory. This
16 16 configuration file contains the various settings for RhodeCode, e.g proxy port,
17 17 email settings, usage of static files, cache, celery settings and logging.
18 18
19 19
20 20 Next, you need to create the databases used by RhodeCode. I recommend that you
21 21 use sqlite (default) or postgresql. If you choose a database other than the
22 22 default ensure you properly adjust the db url in your production.ini
23 23 configuration file to use this other database. Create the databases by running
24 24 the following command::
25 25
26 26 paster setup-app production.ini
27 27
28 28 This will prompt you for a "root" path. This "root" path is the location where
29 29 RhodeCode will store all of its repositories on the current machine. After
30 30 entering this "root" path ``setup-app`` will also prompt you for a username and password
31 31 for the initial admin account which ``setup-app`` sets up for you.
32 32
33 33 - The ``setup-app`` command will create all of the needed tables and an admin
34 34 account. When choosing a root path you can either use a new empty location, or a
35 35 location which already contains existing repositories. If you choose a location
36 36 which contains existing repositories RhodeCode will simply add all of the
37 37 repositories at the chosen location to it's database. (Note: make sure you
38 38 specify the correct path to the root).
39 39 - Note: the given path for mercurial_ repositories **must** be write accessible
40 40 for the application. It's very important since the RhodeCode web interface will
41 41 work without write access, but when trying to do a push it will eventually fail
42 42 with permission denied errors unless it has write access.
43 43
44 44 You are now ready to use RhodeCode, to run it simply execute::
45 45
46 46 paster serve production.ini
47 47
48 48 - This command runs the RhodeCode server. The web app should be available at the
49 49 127.0.0.1:5000. This ip and port is configurable via the production.ini
50 50 file created in previous step
51 51 - Use the admin account you created above when running ``setup-app`` to login to the web app.
52 52 - The default permissions on each repository is read, and the owner is admin.
53 53 Remember to update these if needed.
54 54 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
55 55 well as edit more advanced options on users and repositories
56 56
57 57 Try copying your own mercurial repository into the "root" directory you are
58 58 using, then from within the RhodeCode web application choose Admin >
59 59 repositories. Then choose Add New Repository. Add the repository you copied into
60 60 the root. Test that you can browse your repository from within RhodeCode and then
61 61 try cloning your repository from RhodeCode with::
62 62
63 63 hg clone http://127.0.0.1:5000/<repository name>
64 64
65 65 where *repository name* is replaced by the name of your repository.
66 66
67 67 Using RhodeCode with SSH
68 68 ------------------------
69 69
70 70 RhodeCode currently only hosts repositories using http and https. (The addition of
71 71 ssh hosting is a planned future feature.) However you can easily use ssh in
72 72 parallel with RhodeCode. (Repository access via ssh is a standard "out of
73 73 the box" feature of mercurial_ and you can use this to access any of the
74 74 repositories that RhodeCode is hosting. See PublishingRepositories_)
75 75
76 76 RhodeCode repository structures are kept in directories with the same name
77 77 as the project. When using repository groups, each group is a subdirectory.
78 78 This allows you to easily use ssh for accessing repositories.
79 79
80 80 In order to use ssh you need to make sure that your web-server and the users login
81 81 accounts have the correct permissions set on the appropriate directories. (Note
82 82 that these permissions are independent of any permissions you have set up using
83 83 the RhodeCode web interface.)
84 84
85 85 If your main directory (the same as set in RhodeCode settings) is for example
86 86 set to **/home/hg** and the repository you are using is named `rhodecode`, then
87 87 to clone via ssh you should run::
88 88
89 89 hg clone ssh://user@server.com/home/hg/rhodecode
90 90
91 91 Using other external tools such as mercurial-server_ or using ssh key based
92 92 authentication is fully supported.
93 93
94 94 Note: In an advanced setup, in order for your ssh access to use the same
95 95 permissions as set up via the RhodeCode web interface, you can create an
96 96 authentication hook to connect to the rhodecode db and runs check functions for
97 97 permissions against that.
98 98
99 99 Setting up Whoosh full text search
100 100 ----------------------------------
101 101
102 102 Starting from version 1.1 the whoosh index can be build by using the paster
103 103 command ``make-index``. To use ``make-index`` you must specify the configuration
104 104 file that stores the location of the index, and the location of the repositories
105 105 (`--repo-location`).Starting from version 1.2 it is
106 106 also possible to specify a comma separated list of repositories (`--index-only`)
107 107 to build index only on chooses repositories skipping any other found in repos
108 108 location
109 109
110 110 You may optionally pass the option `-f` to enable a full index rebuild. Without
111 111 the `-f` option, indexing will run always in "incremental" mode.
112 112
113 113 For an incremental index build use::
114 114
115 115 paster make-index production.ini --repo-location=<location for repos>
116 116
117 117 For a full index rebuild use::
118 118
119 119 paster make-index production.ini -f --repo-location=<location for repos>
120 120
121 121
122 122 building index just for chosen repositories is possible with such command::
123 123
124 124 paster make-index production.ini --repo-location=<location for repos> --index-only=vcs,rhodecode
125 125
126 126
127 127 In order to do periodical index builds and keep your index always up to date.
128 128 It's recommended to do a crontab entry for incremental indexing.
129 129 An example entry might look like this::
130 130
131 131 /path/to/python/bin/paster /path/to/rhodecode/production.ini --repo-location=<location for repos>
132 132
133 133 When using incremental mode (the default) whoosh will check the last
134 134 modification date of each file and add it to be reindexed if a newer file is
135 135 available. The indexing daemon checks for any removed files and removes them
136 136 from index.
137 137
138 138 If you want to rebuild index from scratch, you can use the `-f` flag as above,
139 139 or in the admin panel you can check `build from scratch` flag.
140 140
141 141
142 142 Setting up LDAP support
143 143 -----------------------
144 144
145 145 RhodeCode starting from version 1.1 supports ldap authentication. In order
146 to use LDAP, you have to install the python-ldap_ package. This package is available
147 via pypi, so you can install it by running
146 to use LDAP, you have to install the python-ldap_ package. This package is
147 available via pypi, so you can install it by running
148 148
149 ::
149 using easy_install::
150 150
151 151 easy_install python-ldap
152 152
153 ::
153 using pip::
154 154
155 155 pip install python-ldap
156 156
157 157 .. note::
158 158 python-ldap requires some certain libs on your system, so before installing
159 159 it check that you have at least `openldap`, and `sasl` libraries.
160 160
161 161 LDAP settings are located in admin->ldap section,
162 162
163 163 Here's a typical ldap setup::
164 164
165 165 Connection settings
166 166 Enable LDAP = checked
167 167 Host = host.example.org
168 168 Port = 389
169 169 Account = <account>
170 170 Password = <password>
171 Enable LDAPS = checked
171 Connection Security = LDAPS connection
172 172 Certificate Checks = DEMAND
173 173
174 174 Search settings
175 175 Base DN = CN=users,DC=host,DC=example,DC=org
176 176 LDAP Filter = (&(objectClass=user)(!(objectClass=computer)))
177 177 LDAP Search Scope = SUBTREE
178 178
179 179 Attribute mappings
180 180 Login Attribute = uid
181 181 First Name Attribute = firstName
182 182 Last Name Attribute = lastName
183 183 E-mail Attribute = mail
184 184
185 185 .. _enable_ldap:
186 186
187 187 Enable LDAP : required
188 188 Whether to use LDAP for authenticating users.
189 189
190 190 .. _ldap_host:
191 191
192 192 Host : required
193 193 LDAP server hostname or IP address.
194 194
195 195 .. _Port:
196 196
197 197 Port : required
198 198 389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP.
199 199
200 200 .. _ldap_account:
201 201
202 202 Account : optional
203 203 Only required if the LDAP server does not allow anonymous browsing of
204 204 records. This should be a special account for record browsing. This
205 205 will require `LDAP Password`_ below.
206 206
207 207 .. _LDAP Password:
208 208
209 209 Password : optional
210 210 Only required if the LDAP server does not allow anonymous browsing of
211 211 records.
212 212
213 213 .. _Enable LDAPS:
214 214
215 Enable LDAPS : optional
216 Check this if SSL encryption is necessary for communication with the
217 LDAP server - it will likely require `Port`_ to be set to a different
218 value (standard LDAPS port is 636). When LDAPS is enabled then
219 `Certificate Checks`_ is required.
215 Connection Security : required
216 Defines the connection to LDAP server
217
218 No encryption
219 Plain non encrypted connection
220
221 LDAPS connection
222 Enable ldaps connection. It will likely require `Port`_ to be set to
223 a different value (standard LDAPS port is 636). When LDAPS is enabled
224 then `Certificate Checks`_ is required.
225
226 START_TLS on LDAP connection
227 START TLS connection
220 228
221 229 .. _Certificate Checks:
222 230
223 231 Certificate Checks : optional
224 232 How SSL certificates verification is handled - this is only useful when
225 233 `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security while
226 234 the other options are susceptible to man-in-the-middle attacks. SSL
227 235 certificates can be installed to /etc/openldap/cacerts so that the
228 236 DEMAND or HARD options can be used with self-signed certificates or
229 237 certificates that do not have traceable certificates of authority.
230 238
231 239 NEVER
232 240 A serve certificate will never be requested or checked.
233 241
234 242 ALLOW
235 243 A server certificate is requested. Failure to provide a
236 244 certificate or providing a bad certificate will not terminate the
237 245 session.
238 246
239 247 TRY
240 248 A server certificate is requested. Failure to provide a
241 249 certificate does not halt the session; providing a bad certificate
242 250 halts the session.
243 251
244 252 DEMAND
245 253 A server certificate is requested and must be provided and
246 254 authenticated for the session to proceed.
247 255
248 256 HARD
249 257 The same as DEMAND.
250 258
251 259 .. _Base DN:
252 260
253 261 Base DN : required
254 262 The Distinguished Name (DN) where searches for users will be performed.
255 263 Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
256 264
257 265 .. _LDAP Filter:
258 266
259 267 LDAP Filter : optional
260 268 A LDAP filter defined by RFC 2254. This is more useful when `LDAP
261 269 Search Scope`_ is set to SUBTREE. The filter is useful for limiting
262 270 which LDAP objects are identified as representing Users for
263 271 authentication. The filter is augmented by `Login Attribute`_ below.
264 272 This can commonly be left blank.
265 273
266 274 .. _LDAP Search Scope:
267 275
268 276 LDAP Search Scope : required
269 277 This limits how far LDAP will search for a matching object.
270 278
271 279 BASE
272 280 Only allows searching of `Base DN`_ and is usually not what you
273 281 want.
274 282
275 283 ONELEVEL
276 284 Searches all entries under `Base DN`_, but not Base DN itself.
277 285
278 286 SUBTREE
279 287 Searches all entries below `Base DN`_, but not Base DN itself.
280 288 When using SUBTREE `LDAP Filter`_ is useful to limit object
281 289 location.
282 290
283 291 .. _Login Attribute:
284 292
285 293 Login Attribute : required
286 294 The LDAP record attribute that will be matched as the USERNAME or
287 295 ACCOUNT used to connect to RhodeCode. This will be added to `LDAP
288 296 Filter`_ for locating the User object. If `LDAP Filter`_ is specified as
289 297 "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
290 298 connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
291 299 ::
292 300
293 301 (&(LDAPFILTER)(uid=jsmith))
294 302
295 303 .. _ldap_attr_firstname:
296 304
297 305 First Name Attribute : required
298 306 The LDAP record attribute which represents the user's first name.
299 307
300 308 .. _ldap_attr_lastname:
301 309
302 310 Last Name Attribute : required
303 311 The LDAP record attribute which represents the user's last name.
304 312
305 313 .. _ldap_attr_email:
306 314
307 315 Email Attribute : required
308 316 The LDAP record attribute which represents the user's email address.
309 317
310 318 If all data are entered correctly, and python-ldap_ is properly installed
311 319 users should be granted access to RhodeCode with ldap accounts. At this
312 320 time user information is copied from LDAP into the RhodeCode user database.
313 321 This means that updates of an LDAP user object may not be reflected as a
314 322 user update in RhodeCode.
315 323
316 324 If You have problems with LDAP access and believe You entered correct
317 325 information check out the RhodeCode logs, any error messages sent from LDAP
318 326 will be saved there.
319 327
320 328 Active Directory
321 329 ''''''''''''''''
322 330
323 331 RhodeCode can use Microsoft Active Directory for user authentication. This
324 332 is done through an LDAP or LDAPS connection to Active Directory. The
325 333 following LDAP configuration settings are typical for using Active
326 334 Directory ::
327 335
328 336 Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
329 337 Login Attribute = sAMAccountName
330 338 First Name Attribute = givenName
331 339 Last Name Attribute = sn
332 340 E-mail Attribute = mail
333 341
334 342 All other LDAP settings will likely be site-specific and should be
335 343 appropriately configured.
336 344
337 345 Setting Up Celery
338 346 -----------------
339 347
340 348 Since version 1.1 celery is configured by the rhodecode ini configuration files.
341 349 Simply set use_celery=true in the ini file then add / change the configuration
342 350 variables inside the ini file.
343 351
344 352 Remember that the ini files use the format with '.' not with '_' like celery.
345 353 So for example setting `BROKER_HOST` in celery means setting `broker.host` in
346 354 the config file.
347 355
348 356 In order to start using celery run::
349 357
350 358 paster celeryd <configfile.ini>
351 359
352 360
353 361 .. note::
354 362 Make sure you run this command from the same virtualenv, and with the same user
355 363 that rhodecode runs.
356 364
357 365 HTTPS support
358 366 -------------
359 367
360 368 There are two ways to enable https:
361 369
362 370 - Set HTTP_X_URL_SCHEME in your http server headers, than rhodecode will
363 371 recognize this headers and make proper https redirections
364 372 - Alternatively, set `force_https = true` in the ini configuration to force using
365 373 https, no headers are needed than to enable https
366 374
367 375
368 376 Nginx virtual host example
369 377 --------------------------
370 378
371 379 Sample config for nginx using proxy::
372 380
373 381 server {
374 382 listen 80;
375 383 server_name hg.myserver.com;
376 384 access_log /var/log/nginx/rhodecode.access.log;
377 385 error_log /var/log/nginx/rhodecode.error.log;
378 386 location / {
379 387 root /var/www/rhodecode/rhodecode/public/;
380 388 if (!-f $request_filename){
381 389 proxy_pass http://127.0.0.1:5000;
382 390 }
383 391 #this is important if you want to use https !!!
384 392 proxy_set_header X-Url-Scheme $scheme;
385 393 include /etc/nginx/proxy.conf;
386 394 }
387 395 }
388 396
389 397 Here's the proxy.conf. It's tuned so it will not timeout on long
390 398 pushes or large pushes::
391 399
392 400 proxy_redirect off;
393 401 proxy_set_header Host $host;
394 402 proxy_set_header X-Host $http_host;
395 403 proxy_set_header X-Real-IP $remote_addr;
396 404 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
397 405 proxy_set_header Proxy-host $proxy_host;
398 406 client_max_body_size 400m;
399 407 client_body_buffer_size 128k;
400 408 proxy_buffering off;
401 409 proxy_connect_timeout 3600;
402 410 proxy_send_timeout 3600;
403 411 proxy_read_timeout 3600;
404 412 proxy_buffer_size 16k;
405 413 proxy_buffers 4 16k;
406 414 proxy_busy_buffers_size 64k;
407 415 proxy_temp_file_write_size 64k;
408 416
409 417 Also, when using root path with nginx you might set the static files to false
410 418 in the production.ini file::
411 419
412 420 [app:main]
413 421 use = egg:rhodecode
414 422 full_stack = true
415 423 static_files = false
416 424 lang=en
417 425 cache_dir = %(here)s/data
418 426
419 427 In order to not have the statics served by the application. This improves speed.
420 428
421 429
422 430 Apache virtual host example
423 431 ---------------------------
424 432
425 433 Here is a sample configuration file for apache using proxy::
426 434
427 435 <VirtualHost *:80>
428 436 ServerName hg.myserver.com
429 437 ServerAlias hg.myserver.com
430 438
431 439 <Proxy *>
432 440 Order allow,deny
433 441 Allow from all
434 442 </Proxy>
435 443
436 444 #important !
437 445 #Directive to properly generate url (clone url) for pylons
438 446 ProxyPreserveHost On
439 447
440 448 #rhodecode instance
441 449 ProxyPass / http://127.0.0.1:5000/
442 450 ProxyPassReverse / http://127.0.0.1:5000/
443 451
444 452 #to enable https use line below
445 453 #SetEnvIf X-Url-Scheme https HTTPS=1
446 454
447 455 </VirtualHost>
448 456
449 457
450 458 Additional tutorial
451 459 http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons
452 460
453 461
454 462 Apache as subdirectory
455 463 ----------------------
456 464
457 465 Apache subdirectory part::
458 466
459 467 <Location /<someprefix> >
460 468 ProxyPass http://127.0.0.1:5000/<someprefix>
461 469 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
462 470 SetEnvIf X-Url-Scheme https HTTPS=1
463 471 </Location>
464 472
465 473 Besides the regular apache setup you will need to add the following to your .ini file::
466 474
467 475 filter-with = proxy-prefix
468 476
469 477 Add the following at the end of the .ini file::
470 478
471 479 [filter:proxy-prefix]
472 480 use = egg:PasteDeploy#prefix
473 481 prefix = /<someprefix>
474 482
475 483
476 484 then change <someprefix> into your choosen prefix
477 485
478 486 Apache's example FCGI config
479 487 ----------------------------
480 488
481 489 TODO !
482 490
483 491 Other configuration files
484 492 -------------------------
485 493
486 494 Some example init.d scripts can be found here, for debian and gentoo:
487 495
488 496 https://rhodecode.org/rhodecode/files/tip/init.d
489 497
490 498
491 499 Troubleshooting
492 500 ---------------
493 501
494 502 :Q: **Missing static files?**
495 503 :A: Make sure either to set the `static_files = true` in the .ini file or
496 504 double check the root path for your http setup. It should point to
497 505 for example:
498 506 /home/my-virtual-python/lib/python2.6/site-packages/rhodecode/public
499 507
500 508 |
501 509
502 510 :Q: **Can't install celery/rabbitmq**
503 511 :A: Don't worry RhodeCode works without them too. No extra setup is required.
504 512
505 513 |
506 514
507 515 :Q: **Long lasting push timeouts?**
508 516 :A: Make sure you set a longer timeouts in your proxy/fcgi settings, timeouts
509 517 are caused by https server and not RhodeCode.
510 518
511 519 |
512 520
513 521 :Q: **Large pushes timeouts?**
514 522 :A: Make sure you set a proper max_body_size for the http server.
515 523
516 524 |
517 525
518 526 :Q: **Apache doesn't pass basicAuth on pull/push?**
519 527 :A: Make sure you added `WSGIPassAuthorization true`.
520 528
521 529 For further questions search the `Issues tracker`_, or post a message in the `google group rhodecode`_
522 530
523 531 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
524 532 .. _python: http://www.python.org/
525 533 .. _mercurial: http://mercurial.selenic.com/
526 534 .. _celery: http://celeryproject.org/
527 535 .. _rabbitmq: http://www.rabbitmq.com/
528 536 .. _python-ldap: http://www.python-ldap.org/
529 537 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
530 538 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
531 539 .. _Issues tracker: https://bitbucket.org/marcinkuzminski/rhodecode/issues
532 540 .. _google group rhodecode: http://groups.google.com/group/rhodecode No newline at end of file
@@ -1,134 +1,138 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.ldap_settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 ldap controller for RhodeCode
7 7
8 8 :created_on: Nov 26, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26 import formencode
27 27 import traceback
28 28
29 29 from formencode import htmlfill
30 30
31 31 from pylons import request, response, session, tmpl_context as c, url
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _
34 34
35 from sqlalchemy.exc import DatabaseError
36
35 37 from rhodecode.lib.base import BaseController, render
36 38 from rhodecode.lib import helpers as h
37 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 from rhodecode.lib.auth_ldap import LdapImportError
39 from rhodecode.model.settings import SettingsModel
40 from rhodecode.lib.exceptions import LdapImportError
40 41 from rhodecode.model.forms import LdapSettingsForm
41 from sqlalchemy.exc import DatabaseError
42 from rhodecode.model.db import RhodeCodeSettings
42 43
43 44 log = logging.getLogger(__name__)
44 45
45 46
46 47 class LdapSettingsController(BaseController):
47 48
48 49 search_scope_choices = [('BASE', _('BASE'),),
49 50 ('ONELEVEL', _('ONELEVEL'),),
50 51 ('SUBTREE', _('SUBTREE'),),
51 52 ]
52 53 search_scope_default = 'SUBTREE'
53 54
54 55 tls_reqcert_choices = [('NEVER', _('NEVER'),),
55 56 ('ALLOW', _('ALLOW'),),
56 57 ('TRY', _('TRY'),),
57 58 ('DEMAND', _('DEMAND'),),
58 59 ('HARD', _('HARD'),),
59 60 ]
60 61 tls_reqcert_default = 'DEMAND'
61 62
62 63 tls_kind_choices = [('PLAIN', _('No encryption'),),
63 64 ('LDAPS', _('LDAPS connection'),),
64 65 ('START_TLS', _('START_TLS on LDAP connection'),)
65 66 ]
66 67
67 68 tls_kind_default = 'PLAIN'
68 69
69 70 @LoginRequired()
70 71 @HasPermissionAllDecorator('hg.admin')
71 72 def __before__(self):
72 73 c.admin_user = session.get('admin_user')
73 74 c.admin_username = session.get('admin_username')
74 75 c.search_scope_choices = self.search_scope_choices
75 76 c.tls_reqcert_choices = self.tls_reqcert_choices
76 77 c.tls_kind_choices = self.tls_kind_choices
78
79 c.search_scope_cur = self.search_scope_default
80 c.tls_reqcert_cur = self.tls_reqcert_default
81 c.tls_kind_cur = self.tls_kind_default
82
77 83 super(LdapSettingsController, self).__before__()
78 84
79 85 def index(self):
80 defaults = SettingsModel().get_ldap_settings()
86 defaults = RhodeCodeSettings.get_ldap_settings()
81 87 c.search_scope_cur = defaults.get('ldap_search_scope')
82 88 c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
83 89 c.tls_kind_cur = defaults.get('ldap_tls_kind')
84 90
85 91 return htmlfill.render(
86 92 render('admin/ldap/ldap.html'),
87 93 defaults=defaults,
88 94 encoding="UTF-8",
89 95 force_defaults=True,)
90 96
91 97 def ldap_settings(self):
92 98 """POST ldap create and store ldap settings"""
93 99
94 settings_model = SettingsModel()
95 100 _form = LdapSettingsForm([x[0] for x in self.tls_reqcert_choices],
96 101 [x[0] for x in self.search_scope_choices],
97 102 [x[0] for x in self.tls_kind_choices])()
98 103
99 104 try:
100 105 form_result = _form.to_python(dict(request.POST))
101 106 try:
102 107
103 108 for k, v in form_result.items():
104 109 if k.startswith('ldap_'):
105 setting = settings_model.get(k)
110 setting = RhodeCodeSettings.get_by_name(k)
106 111 setting.app_settings_value = v
107 112 self.sa.add(setting)
108 113
109 114 self.sa.commit()
110 115 h.flash(_('Ldap settings updated successfully'),
111 116 category='success')
112 117 except (DatabaseError,):
113 118 raise
114 119 except LdapImportError:
115 120 h.flash(_('Unable to activate ldap. The "python-ldap" library '
116 121 'is missing.'), category='warning')
117 122
118 123 except formencode.Invalid, errors:
124 e = errors.error_dict or {}
119 125
120 c.search_scope_cur = self.search_scope_default
121 c.tls_reqcert_cur = self.search_scope_default
122 126
123 127 return htmlfill.render(
124 128 render('admin/ldap/ldap.html'),
125 129 defaults=errors.value,
126 errors=errors.error_dict or {},
130 errors=e,
127 131 prefix_error=False,
128 132 encoding="UTF-8")
129 133 except Exception:
130 134 log.error(traceback.format_exc())
131 135 h.flash(_('error occurred during update of ldap settings'),
132 136 category='error')
133 137
134 138 return redirect(url('ldap_home'))
@@ -1,363 +1,362 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 settings controller for rhodecode admin
7 7
8 8 :created_on: Jul 14, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from sqlalchemy import func
31 31 from formencode import htmlfill
32 32 from pylons import request, session, tmpl_context as c, url, config
33 33 from pylons.controllers.util import abort, redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
38 38 HasPermissionAnyDecorator, NotAnonymous
39 39 from rhodecode.lib.base import BaseController, render
40 40 from rhodecode.lib.celerylib import tasks, run_task
41 41 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
42 42 set_rhodecode_config, repo_name_slug
43 from rhodecode.model.db import RhodeCodeUi, Repository, Group
43 from rhodecode.model.db import RhodeCodeUi, Repository, Group, \
44 RhodeCodeSettings
44 45 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
45 46 ApplicationUiSettingsForm
46 47 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.settings import SettingsModel
48 48 from rhodecode.model.user import UserModel
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class SettingsController(BaseController):
54 54 """REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('setting', 'settings', controller='admin/settings',
58 58 # path_prefix='/admin', name_prefix='admin_')
59 59
60 60 @LoginRequired()
61 61 def __before__(self):
62 62 c.admin_user = session.get('admin_user')
63 63 c.admin_username = session.get('admin_username')
64 64 super(SettingsController, self).__before__()
65 65
66 66 @HasPermissionAllDecorator('hg.admin')
67 67 def index(self, format='html'):
68 68 """GET /admin/settings: All items in the collection"""
69 69 # url('admin_settings')
70 70
71 defaults = SettingsModel().get_app_settings()
71 defaults = RhodeCodeSettings.get_app_settings()
72 72 defaults.update(self.get_hg_ui_settings())
73 73 return htmlfill.render(
74 74 render('admin/settings/settings.html'),
75 75 defaults=defaults,
76 76 encoding="UTF-8",
77 77 force_defaults=False
78 78 )
79 79
80 80 @HasPermissionAllDecorator('hg.admin')
81 81 def create(self):
82 82 """POST /admin/settings: Create a new item"""
83 83 # url('admin_settings')
84 84
85 85 @HasPermissionAllDecorator('hg.admin')
86 86 def new(self, format='html'):
87 87 """GET /admin/settings/new: Form to create a new item"""
88 88 # url('admin_new_setting')
89 89
90 90 @HasPermissionAllDecorator('hg.admin')
91 91 def update(self, setting_id):
92 92 """PUT /admin/settings/setting_id: Update an existing item"""
93 93 # Forms posted to this method should contain a hidden field:
94 94 # <input type="hidden" name="_method" value="PUT" />
95 95 # Or using helpers:
96 96 # h.form(url('admin_setting', setting_id=ID),
97 97 # method='put')
98 98 # url('admin_setting', setting_id=ID)
99 99 if setting_id == 'mapping':
100 100 rm_obsolete = request.POST.get('destroy', False)
101 101 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
102 102 initial = ScmModel().repo_scan()
103 103 log.debug('invalidating all repositories')
104 104 for repo_name in initial.keys():
105 105 invalidate_cache('get_repo_cached_%s' % repo_name)
106 106
107 107 added, removed = repo2db_mapper(initial, rm_obsolete)
108 108
109 109 h.flash(_('Repositories successfully'
110 110 ' rescanned added: %s,removed: %s') % (added, removed),
111 111 category='success')
112 112
113 113 if setting_id == 'whoosh':
114 114 repo_location = self.get_hg_ui_settings()['paths_root_path']
115 115 full_index = request.POST.get('full_index', False)
116 116 run_task(tasks.whoosh_index, repo_location, full_index)
117 117
118 118 h.flash(_('Whoosh reindex task scheduled'), category='success')
119 119 if setting_id == 'global':
120 120
121 121 application_form = ApplicationSettingsForm()()
122 122 try:
123 123 form_result = application_form.to_python(dict(request.POST))
124 settings_model = SettingsModel()
125 124
126 125 try:
127 hgsettings1 = settings_model.get('title')
126 hgsettings1 = RhodeCodeSettings.get_by_name('title')
128 127 hgsettings1.app_settings_value = \
129 128 form_result['rhodecode_title']
130 129
131 hgsettings2 = settings_model.get('realm')
130 hgsettings2 = RhodeCodeSettings.get_by_name('realm')
132 131 hgsettings2.app_settings_value = \
133 132 form_result['rhodecode_realm']
134 133
135 hgsettings3 = settings_model.get('ga_code')
134 hgsettings3 = RhodeCodeSettings.get_by_name('ga_code')
136 135 hgsettings3.app_settings_value = \
137 136 form_result['rhodecode_ga_code']
138 137
139 138 self.sa.add(hgsettings1)
140 139 self.sa.add(hgsettings2)
141 140 self.sa.add(hgsettings3)
142 141 self.sa.commit()
143 142 set_rhodecode_config(config)
144 143 h.flash(_('Updated application settings'),
145 144 category='success')
146 145
147 146 except Exception:
148 147 log.error(traceback.format_exc())
149 148 h.flash(_('error occurred during updating '
150 149 'application settings'),
151 150 category='error')
152 151
153 152 self.sa.rollback()
154 153
155 154 except formencode.Invalid, errors:
156 155 return htmlfill.render(
157 156 render('admin/settings/settings.html'),
158 157 defaults=errors.value,
159 158 errors=errors.error_dict or {},
160 159 prefix_error=False,
161 160 encoding="UTF-8")
162 161
163 162 if setting_id == 'mercurial':
164 163 application_form = ApplicationUiSettingsForm()()
165 164 try:
166 165 form_result = application_form.to_python(dict(request.POST))
167 166
168 167 try:
169 168
170 169 hgsettings1 = self.sa.query(RhodeCodeUi)\
171 170 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
172 171 hgsettings1.ui_value = form_result['web_push_ssl']
173 172
174 173 hgsettings2 = self.sa.query(RhodeCodeUi)\
175 174 .filter(RhodeCodeUi.ui_key == '/').one()
176 175 hgsettings2.ui_value = form_result['paths_root_path']
177 176
178 177 #HOOKS
179 178 hgsettings3 = self.sa.query(RhodeCodeUi)\
180 179 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
181 180 hgsettings3.ui_active = \
182 181 bool(form_result['hooks_changegroup_update'])
183 182
184 183 hgsettings4 = self.sa.query(RhodeCodeUi)\
185 184 .filter(RhodeCodeUi.ui_key ==
186 185 'changegroup.repo_size').one()
187 186 hgsettings4.ui_active = \
188 187 bool(form_result['hooks_changegroup_repo_size'])
189 188
190 189 hgsettings5 = self.sa.query(RhodeCodeUi)\
191 190 .filter(RhodeCodeUi.ui_key ==
192 191 'pretxnchangegroup.push_logger').one()
193 192 hgsettings5.ui_active = \
194 193 bool(form_result['hooks_pretxnchangegroup'
195 194 '_push_logger'])
196 195
197 196 hgsettings6 = self.sa.query(RhodeCodeUi)\
198 197 .filter(RhodeCodeUi.ui_key ==
199 198 'preoutgoing.pull_logger').one()
200 199 hgsettings6.ui_active = \
201 200 bool(form_result['hooks_preoutgoing_pull_logger'])
202 201
203 202 self.sa.add(hgsettings1)
204 203 self.sa.add(hgsettings2)
205 204 self.sa.add(hgsettings3)
206 205 self.sa.add(hgsettings4)
207 206 self.sa.add(hgsettings5)
208 207 self.sa.add(hgsettings6)
209 208 self.sa.commit()
210 209
211 210 h.flash(_('Updated mercurial settings'),
212 211 category='success')
213 212
214 213 except:
215 214 log.error(traceback.format_exc())
216 215 h.flash(_('error occurred during updating '
217 216 'application settings'), category='error')
218 217
219 218 self.sa.rollback()
220 219
221 220 except formencode.Invalid, errors:
222 221 return htmlfill.render(
223 222 render('admin/settings/settings.html'),
224 223 defaults=errors.value,
225 224 errors=errors.error_dict or {},
226 225 prefix_error=False,
227 226 encoding="UTF-8")
228 227
229 228 return redirect(url('admin_settings'))
230 229
231 230 @HasPermissionAllDecorator('hg.admin')
232 231 def delete(self, setting_id):
233 232 """DELETE /admin/settings/setting_id: Delete an existing item"""
234 233 # Forms posted to this method should contain a hidden field:
235 234 # <input type="hidden" name="_method" value="DELETE" />
236 235 # Or using helpers:
237 236 # h.form(url('admin_setting', setting_id=ID),
238 237 # method='delete')
239 238 # url('admin_setting', setting_id=ID)
240 239
241 240 @HasPermissionAllDecorator('hg.admin')
242 241 def show(self, setting_id, format='html'):
243 242 """
244 243 GET /admin/settings/setting_id: Show a specific item"""
245 244 # url('admin_setting', setting_id=ID)
246 245
247 246 @HasPermissionAllDecorator('hg.admin')
248 247 def edit(self, setting_id, format='html'):
249 248 """
250 249 GET /admin/settings/setting_id/edit: Form to
251 250 edit an existing item"""
252 251 # url('admin_edit_setting', setting_id=ID)
253 252
254 253 @NotAnonymous()
255 254 def my_account(self):
256 255 """
257 256 GET /_admin/my_account Displays info about my account
258 257 """
259 258 # url('admin_settings_my_account')
260 259
261 260 c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
262 261 all_repos = [r.repo_name for r in self.sa.query(Repository)\
263 262 .filter(Repository.user_id == c.user.user_id)\
264 263 .order_by(func.lower(Repository.repo_name)).all()]
265 264 c.user_repos = ScmModel().get_repos(all_repos)
266 265
267 266 if c.user.username == 'default':
268 267 h.flash(_("You can't edit this user since it's"
269 268 " crucial for entire application"), category='warning')
270 269 return redirect(url('users'))
271 270
272 271 defaults = c.user.get_dict()
273 272 return htmlfill.render(
274 273 render('admin/users/user_edit_my_account.html'),
275 274 defaults=defaults,
276 275 encoding="UTF-8",
277 276 force_defaults=False
278 277 )
279 278
280 279 def my_account_update(self):
281 280 """PUT /_admin/my_account_update: Update an existing item"""
282 281 # Forms posted to this method should contain a hidden field:
283 282 # <input type="hidden" name="_method" value="PUT" />
284 283 # Or using helpers:
285 284 # h.form(url('admin_settings_my_account_update'),
286 285 # method='put')
287 286 # url('admin_settings_my_account_update', id=ID)
288 287 user_model = UserModel()
289 288 uid = self.rhodecode_user.user_id
290 289 _form = UserForm(edit=True,
291 290 old_data={'user_id': uid,
292 291 'email': self.rhodecode_user.email})()
293 292 form_result = {}
294 293 try:
295 294 form_result = _form.to_python(dict(request.POST))
296 295 user_model.update_my_account(uid, form_result)
297 296 h.flash(_('Your account was updated successfully'),
298 297 category='success')
299 298
300 299 except formencode.Invalid, errors:
301 300 c.user = user_model.get(self.rhodecode_user.user_id, cache=False)
302 301 c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
303 302 all_repos = self.sa.query(Repository)\
304 303 .filter(Repository.user_id == c.user.user_id)\
305 304 .order_by(func.lower(Repository.repo_name))\
306 305 .all()
307 306 c.user_repos = ScmModel().get_repos(all_repos)
308 307
309 308 return htmlfill.render(
310 309 render('admin/users/user_edit_my_account.html'),
311 310 defaults=errors.value,
312 311 errors=errors.error_dict or {},
313 312 prefix_error=False,
314 313 encoding="UTF-8")
315 314 except Exception:
316 315 log.error(traceback.format_exc())
317 316 h.flash(_('error occurred during update of user %s') \
318 317 % form_result.get('username'), category='error')
319 318
320 319 return redirect(url('my_account'))
321 320
322 321 @NotAnonymous()
323 322 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
324 323 def create_repository(self):
325 324 """GET /_admin/create_repository: Form to create a new item"""
326 325
327 326 c.repo_groups = [('', '')]
328 327 parents_link = lambda k: h.literal('&raquo;'.join(
329 328 map(lambda k: k.group_name,
330 329 k.parents + [k])
331 330 )
332 331 )
333 332
334 333 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
335 334 x in self.sa.query(Group).all()])
336 335 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
337 336
338 337 new_repo = request.GET.get('repo', '')
339 338 c.new_repo = repo_name_slug(new_repo)
340 339
341 340 return render('admin/repos/repo_add_create_repository.html')
342 341
343 342 def get_hg_ui_settings(self):
344 343 ret = self.sa.query(RhodeCodeUi).all()
345 344
346 345 if not ret:
347 346 raise Exception('Could not get application ui settings !')
348 347 settings = {}
349 348 for each in ret:
350 349 k = each.ui_key
351 350 v = each.ui_value
352 351 if k == '/':
353 352 k = 'root_path'
354 353
355 354 if k.find('.') != -1:
356 355 k = k.replace('.', '_')
357 356
358 357 if each.ui_section == 'hooks':
359 358 v = each.ui_active
360 359
361 360 settings[each.ui_section + '_' + k] = v
362 361
363 362 return settings
@@ -1,602 +1,601 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.auth
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 authentication and permission libraries
7 7
8 8 :created_on: Apr 4, 2010
9 9 :copyright: (c) 2010 by marcink.
10 10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 11 """
12 12 # This program is free software: you can redistribute it and/or modify
13 13 # it under the terms of the GNU General Public License as published by
14 14 # the Free Software Foundation, either version 3 of the License, or
15 15 # (at your option) any later version.
16 16 #
17 17 # This program is distributed in the hope that it will be useful,
18 18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 20 # GNU General Public License for more details.
21 21 #
22 22 # You should have received a copy of the GNU General Public License
23 23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 24
25 25 import random
26 26 import logging
27 27 import traceback
28 28 import hashlib
29 29
30 30 from tempfile import _RandomNameSequence
31 31 from decorator import decorator
32 32
33 33 from pylons import config, session, url, request
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38 38
39 39 if __platform__ in PLATFORM_WIN:
40 40 from hashlib import sha256
41 41 if __platform__ in PLATFORM_OTHERS:
42 42 import bcrypt
43 43
44 44 from rhodecode.lib import str2bool
45 45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
46 46 from rhodecode.lib.utils import get_repo_slug
47 47 from rhodecode.lib.auth_ldap import AuthLdap
48 48
49 49 from rhodecode.model import meta
50 50 from rhodecode.model.user import UserModel
51 from rhodecode.model.db import Permission
51 from rhodecode.model.db import Permission, RhodeCodeSettings
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 class PasswordGenerator(object):
57 57 """This is a simple class for generating password from
58 58 different sets of characters
59 59 usage:
60 60 passwd_gen = PasswordGenerator()
61 61 #print 8-letter password containing only big and small letters
62 62 of alphabet
63 63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 64 """
65 65 ALPHABETS_NUM = r'''1234567890'''
66 66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
67 67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
68 68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
69 69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
70 70 + ALPHABETS_NUM + ALPHABETS_SPECIAL
71 71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
72 72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
74 74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
75 75
76 76 def __init__(self, passwd=''):
77 77 self.passwd = passwd
78 78
79 79 def gen_password(self, len, type):
80 80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
81 81 return self.passwd
82 82
83 83
84 84 class RhodeCodeCrypto(object):
85 85
86 86 @classmethod
87 87 def hash_string(cls, str_):
88 88 """
89 89 Cryptographic function used for password hashing based on pybcrypt
90 90 or pycrypto in windows
91 91
92 92 :param password: password to hash
93 93 """
94 94 if __platform__ in PLATFORM_WIN:
95 95 return sha256(str_).hexdigest()
96 96 elif __platform__ in PLATFORM_OTHERS:
97 97 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
98 98 else:
99 99 raise Exception('Unknown or unsupported platform %s' \
100 100 % __platform__)
101 101
102 102 @classmethod
103 103 def hash_check(cls, password, hashed):
104 104 """
105 105 Checks matching password with it's hashed value, runs different
106 106 implementation based on platform it runs on
107 107
108 108 :param password: password
109 109 :param hashed: password in hashed form
110 110 """
111 111
112 112 if __platform__ in PLATFORM_WIN:
113 113 return sha256(password).hexdigest() == hashed
114 114 elif __platform__ in PLATFORM_OTHERS:
115 115 return bcrypt.hashpw(password, hashed) == hashed
116 116 else:
117 117 raise Exception('Unknown or unsupported platform %s' \
118 118 % __platform__)
119 119
120 120
121 121 def get_crypt_password(password):
122 122 return RhodeCodeCrypto.hash_string(password)
123 123
124 124
125 125 def check_password(password, hashed):
126 126 return RhodeCodeCrypto.hash_check(password, hashed)
127 127
128 128
129 129 def generate_api_key(username, salt=None):
130 130 if salt is None:
131 131 salt = _RandomNameSequence().next()
132 132
133 133 return hashlib.sha1(username + salt).hexdigest()
134 134
135 135
136 136 def authfunc(environ, username, password):
137 137 """Dummy authentication function used in Mercurial/Git/ and access control,
138 138
139 139 :param environ: needed only for using in Basic auth
140 140 """
141 141 return authenticate(username, password)
142 142
143 143
144 144 def authenticate(username, password):
145 145 """Authentication function used for access control,
146 146 firstly checks for db authentication then if ldap is enabled for ldap
147 147 authentication, also creates ldap user if not in database
148 148
149 149 :param username: username
150 150 :param password: password
151 151 """
152
152 153 user_model = UserModel()
153 154 user = user_model.get_by_username(username, cache=False)
154 155
155 156 log.debug('Authenticating user using RhodeCode account')
156 157 if user is not None and not user.ldap_dn:
157 158 if user.active:
158 159 if user.username == 'default' and user.active:
159 160 log.info('user %s authenticated correctly as anonymous user',
160 161 username)
161 162 return True
162 163
163 164 elif user.username == username and check_password(password,
164 165 user.password):
165 166 log.info('user %s authenticated correctly', username)
166 167 return True
167 168 else:
168 169 log.warning('user %s is disabled', username)
169 170
170 171 else:
171 172 log.debug('Regular authentication failed')
172 173 user_obj = user_model.get_by_username(username, cache=False,
173 174 case_insensitive=True)
174 175
175 176 if user_obj is not None and not user_obj.ldap_dn:
176 177 log.debug('this user already exists as non ldap')
177 178 return False
178 179
179 from rhodecode.model.settings import SettingsModel
180 ldap_settings = SettingsModel().get_ldap_settings()
181
180 ldap_settings = RhodeCodeSettings.get_ldap_settings()
182 181 #======================================================================
183 182 # FALLBACK TO LDAP AUTH IF ENABLE
184 183 #======================================================================
185 184 if str2bool(ldap_settings.get('ldap_active')):
186 185 log.debug("Authenticating user using ldap")
187 186 kwargs = {
188 187 'server': ldap_settings.get('ldap_host', ''),
189 188 'base_dn': ldap_settings.get('ldap_base_dn', ''),
190 189 'port': ldap_settings.get('ldap_port'),
191 190 'bind_dn': ldap_settings.get('ldap_dn_user'),
192 191 'bind_pass': ldap_settings.get('ldap_dn_pass'),
193 192 'tls_kind': ldap_settings.get('ldap_tls_kind'),
194 193 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
195 194 'ldap_filter': ldap_settings.get('ldap_filter'),
196 195 'search_scope': ldap_settings.get('ldap_search_scope'),
197 196 'attr_login': ldap_settings.get('ldap_attr_login'),
198 197 'ldap_version': 3,
199 198 }
200 199 log.debug('Checking for ldap authentication')
201 200 try:
202 201 aldap = AuthLdap(**kwargs)
203 202 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
204 203 password)
205 204 log.debug('Got ldap DN response %s', user_dn)
206 205
206 get_ldap_attr = lambda k:ldap_attrs.get(ldap_settings\
207 .get(k), [''])[0]
208
207 209 user_attrs = {
208 'name': ldap_attrs.get(ldap_settings\
209 .get('ldap_attr_firstname'), [''])[0],
210 'lastname': ldap_attrs.get(ldap_settings\
211 .get('ldap_attr_lastname'),[''])[0],
212 'email': ldap_attrs.get(ldap_settings\
213 .get('ldap_attr_email'), [''])[0],
210 'name': get_ldap_attr('ldap_attr_firstname'),
211 'lastname': get_ldap_attr('ldap_attr_lastname'),
212 'email': get_ldap_attr('ldap_attr_email'),
214 213 }
215 214
216 215 if user_model.create_ldap(username, password, user_dn,
217 216 user_attrs):
218 217 log.info('created new ldap user %s', username)
219 218
220 219 return True
221 220 except (LdapUsernameError, LdapPasswordError,):
222 221 pass
223 222 except (Exception,):
224 223 log.error(traceback.format_exc())
225 224 pass
226 225 return False
227 226
228 227
229 228 class AuthUser(object):
230 229 """
231 230 A simple object that handles all attributes of user in RhodeCode
232 231
233 232 It does lookup based on API key,given user, or user present in session
234 233 Then it fills all required information for such user. It also checks if
235 234 anonymous access is enabled and if so, it returns default user as logged
236 235 in
237 236 """
238 237
239 238 def __init__(self, user_id=None, api_key=None):
240 239
241 240 self.user_id = user_id
242 241 self.api_key = None
243 242
244 243 self.username = 'None'
245 244 self.name = ''
246 245 self.lastname = ''
247 246 self.email = ''
248 247 self.is_authenticated = False
249 248 self.admin = False
250 249 self.permissions = {}
251 250 self._api_key = api_key
252 251 self.propagate_data()
253 252
254 253 def propagate_data(self):
255 254 user_model = UserModel()
256 255 self.anonymous_user = user_model.get_by_username('default', cache=True)
257 256 if self._api_key and self._api_key != self.anonymous_user.api_key:
258 257 #try go get user by api key
259 258 log.debug('Auth User lookup by API KEY %s', self._api_key)
260 259 user_model.fill_data(self, api_key=self._api_key)
261 260 else:
262 261 log.debug('Auth User lookup by USER ID %s', self.user_id)
263 262 if self.user_id is not None \
264 263 and self.user_id != self.anonymous_user.user_id:
265 264 user_model.fill_data(self, user_id=self.user_id)
266 265 else:
267 266 if self.anonymous_user.active is True:
268 267 user_model.fill_data(self,
269 268 user_id=self.anonymous_user.user_id)
270 269 #then we set this user is logged in
271 270 self.is_authenticated = True
272 271 else:
273 272 self.is_authenticated = False
274 273
275 274 log.debug('Auth User is now %s', self)
276 275 user_model.fill_perms(self)
277 276
278 277 @property
279 278 def is_admin(self):
280 279 return self.admin
281 280
282 281 def __repr__(self):
283 282 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
284 283 self.is_authenticated)
285 284
286 285 def set_authenticated(self, authenticated=True):
287 286
288 287 if self.user_id != self.anonymous_user.user_id:
289 288 self.is_authenticated = authenticated
290 289
291 290
292 291 def set_available_permissions(config):
293 292 """This function will propagate pylons globals with all available defined
294 293 permission given in db. We don't want to check each time from db for new
295 294 permissions since adding a new permission also requires application restart
296 295 ie. to decorate new views with the newly created permission
297 296
298 297 :param config: current pylons config instance
299 298
300 299 """
301 300 log.info('getting information about all available permissions')
302 301 try:
303 302 sa = meta.Session()
304 303 all_perms = sa.query(Permission).all()
305 304 except:
306 305 pass
307 306 finally:
308 307 meta.Session.remove()
309 308
310 309 config['available_permissions'] = [x.permission_name for x in all_perms]
311 310
312 311
313 312 #==============================================================================
314 313 # CHECK DECORATORS
315 314 #==============================================================================
316 315 class LoginRequired(object):
317 316 """
318 317 Must be logged in to execute this function else
319 318 redirect to login page
320 319
321 320 :param api_access: if enabled this checks only for valid auth token
322 321 and grants access based on valid token
323 322 """
324 323
325 324 def __init__(self, api_access=False):
326 325 self.api_access = api_access
327 326
328 327 def __call__(self, func):
329 328 return decorator(self.__wrapper, func)
330 329
331 330 def __wrapper(self, func, *fargs, **fkwargs):
332 331 cls = fargs[0]
333 332 user = cls.rhodecode_user
334 333
335 334 api_access_ok = False
336 335 if self.api_access:
337 336 log.debug('Checking API KEY access for %s', cls)
338 337 if user.api_key == request.GET.get('api_key'):
339 338 api_access_ok = True
340 339 else:
341 340 log.debug("API KEY token not valid")
342 341
343 342 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
344 343 if user.is_authenticated or api_access_ok:
345 344 log.debug('user %s is authenticated', user.username)
346 345 return func(*fargs, **fkwargs)
347 346 else:
348 347 log.warn('user %s NOT authenticated', user.username)
349 348 p = url.current()
350 349
351 350 log.debug('redirecting to login page with %s', p)
352 351 return redirect(url('login_home', came_from=p))
353 352
354 353
355 354 class NotAnonymous(object):
356 355 """Must be logged in to execute this function else
357 356 redirect to login page"""
358 357
359 358 def __call__(self, func):
360 359 return decorator(self.__wrapper, func)
361 360
362 361 def __wrapper(self, func, *fargs, **fkwargs):
363 362 cls = fargs[0]
364 363 self.user = cls.rhodecode_user
365 364
366 365 log.debug('Checking if user is not anonymous @%s', cls)
367 366
368 367 anonymous = self.user.username == 'default'
369 368
370 369 if anonymous:
371 370 p = ''
372 371 if request.environ.get('SCRIPT_NAME') != '/':
373 372 p += request.environ.get('SCRIPT_NAME')
374 373
375 374 p += request.environ.get('PATH_INFO')
376 375 if request.environ.get('QUERY_STRING'):
377 376 p += '?' + request.environ.get('QUERY_STRING')
378 377
379 378 import rhodecode.lib.helpers as h
380 379 h.flash(_('You need to be a registered user to '
381 380 'perform this action'),
382 381 category='warning')
383 382 return redirect(url('login_home', came_from=p))
384 383 else:
385 384 return func(*fargs, **fkwargs)
386 385
387 386
388 387 class PermsDecorator(object):
389 388 """Base class for controller decorators"""
390 389
391 390 def __init__(self, *required_perms):
392 391 available_perms = config['available_permissions']
393 392 for perm in required_perms:
394 393 if perm not in available_perms:
395 394 raise Exception("'%s' permission is not defined" % perm)
396 395 self.required_perms = set(required_perms)
397 396 self.user_perms = None
398 397
399 398 def __call__(self, func):
400 399 return decorator(self.__wrapper, func)
401 400
402 401 def __wrapper(self, func, *fargs, **fkwargs):
403 402 cls = fargs[0]
404 403 self.user = cls.rhodecode_user
405 404 self.user_perms = self.user.permissions
406 405 log.debug('checking %s permissions %s for %s %s',
407 406 self.__class__.__name__, self.required_perms, cls,
408 407 self.user)
409 408
410 409 if self.check_permissions():
411 410 log.debug('Permission granted for %s %s', cls, self.user)
412 411 return func(*fargs, **fkwargs)
413 412
414 413 else:
415 414 log.warning('Permission denied for %s %s', cls, self.user)
416 415 #redirect with forbidden ret code
417 416 return abort(403)
418 417
419 418 def check_permissions(self):
420 419 """Dummy function for overriding"""
421 420 raise Exception('You have to write this function in child class')
422 421
423 422
424 423 class HasPermissionAllDecorator(PermsDecorator):
425 424 """Checks for access permission for all given predicates. All of them
426 425 have to be meet in order to fulfill the request
427 426 """
428 427
429 428 def check_permissions(self):
430 429 if self.required_perms.issubset(self.user_perms.get('global')):
431 430 return True
432 431 return False
433 432
434 433
435 434 class HasPermissionAnyDecorator(PermsDecorator):
436 435 """Checks for access permission for any of given predicates. In order to
437 436 fulfill the request any of predicates must be meet
438 437 """
439 438
440 439 def check_permissions(self):
441 440 if self.required_perms.intersection(self.user_perms.get('global')):
442 441 return True
443 442 return False
444 443
445 444
446 445 class HasRepoPermissionAllDecorator(PermsDecorator):
447 446 """Checks for access permission for all given predicates for specific
448 447 repository. All of them have to be meet in order to fulfill the request
449 448 """
450 449
451 450 def check_permissions(self):
452 451 repo_name = get_repo_slug(request)
453 452 try:
454 453 user_perms = set([self.user_perms['repositories'][repo_name]])
455 454 except KeyError:
456 455 return False
457 456 if self.required_perms.issubset(user_perms):
458 457 return True
459 458 return False
460 459
461 460
462 461 class HasRepoPermissionAnyDecorator(PermsDecorator):
463 462 """Checks for access permission for any of given predicates for specific
464 463 repository. In order to fulfill the request any of predicates must be meet
465 464 """
466 465
467 466 def check_permissions(self):
468 467 repo_name = get_repo_slug(request)
469 468
470 469 try:
471 470 user_perms = set([self.user_perms['repositories'][repo_name]])
472 471 except KeyError:
473 472 return False
474 473 if self.required_perms.intersection(user_perms):
475 474 return True
476 475 return False
477 476
478 477
479 478 #==============================================================================
480 479 # CHECK FUNCTIONS
481 480 #==============================================================================
482 481 class PermsFunction(object):
483 482 """Base function for other check functions"""
484 483
485 484 def __init__(self, *perms):
486 485 available_perms = config['available_permissions']
487 486
488 487 for perm in perms:
489 488 if perm not in available_perms:
490 489 raise Exception("'%s' permission in not defined" % perm)
491 490 self.required_perms = set(perms)
492 491 self.user_perms = None
493 492 self.granted_for = ''
494 493 self.repo_name = None
495 494
496 495 def __call__(self, check_Location=''):
497 496 user = session.get('rhodecode_user', False)
498 497 if not user:
499 498 return False
500 499 self.user_perms = user.permissions
501 500 self.granted_for = user
502 501 log.debug('checking %s %s %s', self.__class__.__name__,
503 502 self.required_perms, user)
504 503
505 504 if self.check_permissions():
506 505 log.debug('Permission granted %s @ %s', self.granted_for,
507 506 check_Location or 'unspecified location')
508 507 return True
509 508
510 509 else:
511 510 log.warning('Permission denied for %s @ %s', self.granted_for,
512 511 check_Location or 'unspecified location')
513 512 return False
514 513
515 514 def check_permissions(self):
516 515 """Dummy function for overriding"""
517 516 raise Exception('You have to write this function in child class')
518 517
519 518
520 519 class HasPermissionAll(PermsFunction):
521 520 def check_permissions(self):
522 521 if self.required_perms.issubset(self.user_perms.get('global')):
523 522 return True
524 523 return False
525 524
526 525
527 526 class HasPermissionAny(PermsFunction):
528 527 def check_permissions(self):
529 528 if self.required_perms.intersection(self.user_perms.get('global')):
530 529 return True
531 530 return False
532 531
533 532
534 533 class HasRepoPermissionAll(PermsFunction):
535 534
536 535 def __call__(self, repo_name=None, check_Location=''):
537 536 self.repo_name = repo_name
538 537 return super(HasRepoPermissionAll, self).__call__(check_Location)
539 538
540 539 def check_permissions(self):
541 540 if not self.repo_name:
542 541 self.repo_name = get_repo_slug(request)
543 542
544 543 try:
545 544 self.user_perms = set([self.user_perms['reposit'
546 545 'ories'][self.repo_name]])
547 546 except KeyError:
548 547 return False
549 548 self.granted_for = self.repo_name
550 549 if self.required_perms.issubset(self.user_perms):
551 550 return True
552 551 return False
553 552
554 553
555 554 class HasRepoPermissionAny(PermsFunction):
556 555
557 556 def __call__(self, repo_name=None, check_Location=''):
558 557 self.repo_name = repo_name
559 558 return super(HasRepoPermissionAny, self).__call__(check_Location)
560 559
561 560 def check_permissions(self):
562 561 if not self.repo_name:
563 562 self.repo_name = get_repo_slug(request)
564 563
565 564 try:
566 565 self.user_perms = set([self.user_perms['reposi'
567 566 'tories'][self.repo_name]])
568 567 except KeyError:
569 568 return False
570 569 self.granted_for = self.repo_name
571 570 if self.required_perms.intersection(self.user_perms):
572 571 return True
573 572 return False
574 573
575 574
576 575 #==============================================================================
577 576 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
578 577 #==============================================================================
579 578 class HasPermissionAnyMiddleware(object):
580 579 def __init__(self, *perms):
581 580 self.required_perms = set(perms)
582 581
583 582 def __call__(self, user, repo_name):
584 583 usr = AuthUser(user.user_id)
585 584 try:
586 585 self.user_perms = set([usr.permissions['repositories'][repo_name]])
587 586 except:
588 587 self.user_perms = set()
589 588 self.granted_for = ''
590 589 self.username = user.username
591 590 self.repo_name = repo_name
592 591 return self.check_permissions()
593 592
594 593 def check_permissions(self):
595 594 log.debug('checking mercurial protocol '
596 595 'permissions %s for user:%s repository:%s', self.user_perms,
597 596 self.username, self.repo_name)
598 597 if self.required_perms.intersection(self.user_perms):
599 598 log.debug('permission granted')
600 599 return True
601 600 log.debug('permission denied')
602 601 return False
@@ -1,135 +1,144 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # ldap authentication lib
4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
5 #
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.controllers.changelog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 RhodeCode authentication library for LDAP
7
8 :created_on: Created on Nov 17, 2010
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
6 13 # This program is free software: you can redistribute it and/or modify
7 14 # it under the terms of the GNU General Public License as published by
8 15 # the Free Software Foundation, either version 3 of the License, or
9 16 # (at your option) any later version.
10 17 #
11 18 # This program is distributed in the hope that it will be useful,
12 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 21 # GNU General Public License for more details.
15 22 #
16 23 # You should have received a copy of the GNU General Public License
17 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 """
19 Created on Nov 17, 2010
20 25
21 @author: marcink
22 """
23
24 from rhodecode.lib.exceptions import *
25 26 import logging
26 27
28 from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \
29 LdapPasswordError
30
27 31 log = logging.getLogger(__name__)
28 32
33
29 34 try:
30 35 import ldap
31 36 except ImportError:
37 # means that python-ldap is not installed
32 38 pass
33 39
40
34 41 class AuthLdap(object):
35 42
36 43 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
37 44 tls_kind = 'PLAIN', tls_reqcert='DEMAND', ldap_version=3,
38 45 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
39 46 search_scope='SUBTREE',
40 47 attr_login='uid'):
41 48 self.ldap_version = ldap_version
42 49 ldap_server_type = 'ldap'
43 50
44 51 self.TLS_KIND = tls_kind
45 52
46 53 if self.TLS_KIND == 'LDAPS':
47 54 port = port or 689
48 55 ldap_server_type = ldap_server_type + 's'
49 56
50 57 self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
51 58 self.LDAP_SERVER_ADDRESS = server
52 59 self.LDAP_SERVER_PORT = port
53 60
54 61 #USE FOR READ ONLY BIND TO LDAP SERVER
55 62 self.LDAP_BIND_DN = bind_dn
56 63 self.LDAP_BIND_PASS = bind_pass
57 64
58 65 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
59 66 self.LDAP_SERVER_ADDRESS,
60 67 self.LDAP_SERVER_PORT)
61 68
62 69 self.BASE_DN = base_dn
63 70 self.LDAP_FILTER = ldap_filter
64 71 self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
65 72 self.attr_login = attr_login
66 73
67
68 74 def authenticate_ldap(self, username, password):
69 75 """Authenticate a user via LDAP and return his/her LDAP properties.
70 76
71 77 Raises AuthenticationError if the credentials are rejected, or
72 78 EnvironmentError if the LDAP server can't be reached.
73 79
74 80 :param username: username
75 81 :param password: password
76 82 """
77 83
78 84 from rhodecode.lib.helpers import chop_at
79 85
80 86 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
81 87
82 88 if "," in username:
83 89 raise LdapUsernameError("invalid character in username: ,")
84 90 try:
85 91 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
86 92 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
87 93 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
88 94 ldap.set_option(ldap.OPT_TIMEOUT, 20)
89 95 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
90 96 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
91 97 if self.TLS_KIND != 'PLAIN':
92 98 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
93 99 server = ldap.initialize(self.LDAP_SERVER)
94 100 if self.ldap_version == 2:
95 101 server.protocol = ldap.VERSION2
96 102 else:
97 103 server.protocol = ldap.VERSION3
98 104
99 105 if self.TLS_KIND == 'START_TLS':
100 106 server.start_tls_s()
101 107
102 108 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
103 109 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
104 110
105 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, username)
111 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
112 username)
106 113 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
107 114 filt, self.LDAP_SERVER)
108 115 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
109 116 filt)
110 117
111 118 if not lobjects:
112 119 raise ldap.NO_SUCH_OBJECT()
113 120
114 121 for (dn, _attrs) in lobjects:
115 122 try:
116 123 server.simple_bind_s(dn, password)
117 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, '(objectClass=*)')[0][1]
124 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
125 '(objectClass=*)')[0][1]
118 126 break
119 127
120 128 except ldap.INVALID_CREDENTIALS, e:
121 129 log.debug("LDAP rejected password for user '%s' (%s): %s",
122 130 uid, username, dn)
123 131
124 132 else:
125 133 log.debug("No matching LDAP objects for authentication "
126 134 "of '%s' (%s)", uid, username)
127 135 raise LdapPasswordError()
128 136
129 137 except ldap.NO_SUCH_OBJECT, e:
130 138 log.debug("LDAP says no such user '%s' (%s)", uid, username)
131 139 raise LdapUsernameError()
132 140 except ldap.SERVER_DOWN, e:
133 raise LdapConnectionError("LDAP can't access authentication server")
141 raise LdapConnectionError("LDAP can't access "
142 "authentication server")
134 143
135 144 return (dn, attrs)
@@ -1,706 +1,706 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import paste
31 31 import beaker
32 32
33 33 from paste.script.command import Command, BadCommand
34 34
35 35 from UserDict import DictMixin
36 36
37 37 from mercurial import ui, config, hg
38 38 from mercurial.error import RepoError
39 39
40 40 from webhelpers.text import collapse, remove_formatting, strip_tags
41 41
42 42 from vcs.backends.base import BaseChangeset
43 43 from vcs.utils.lazy import LazyProperty
44 44
45 45 from rhodecode.model import meta
46 46 from rhodecode.model.caching_query import FromCache
47 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group
47 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group, \
48 RhodeCodeSettings
48 49 from rhodecode.model.repo import RepoModel
49 50 from rhodecode.model.user import UserModel
50 51
51 52 log = logging.getLogger(__name__)
52 53
53 54
54 55 def recursive_replace(str, replace=' '):
55 56 """Recursive replace of given sign to just one instance
56 57
57 58 :param str: given string
58 59 :param replace: char to find and replace multiple instances
59 60
60 61 Examples::
61 62 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
62 63 'Mighty-Mighty-Bo-sstones'
63 64 """
64 65
65 66 if str.find(replace * 2) == -1:
66 67 return str
67 68 else:
68 69 str = str.replace(replace * 2, replace)
69 70 return recursive_replace(str, replace)
70 71
71 72
72 73 def repo_name_slug(value):
73 74 """Return slug of name of repository
74 75 This function is called on each creation/modification
75 76 of repository to prevent bad names in repo
76 77 """
77 78
78 79 slug = remove_formatting(value)
79 80 slug = strip_tags(slug)
80 81
81 82 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
82 83 slug = slug.replace(c, '-')
83 84 slug = recursive_replace(slug, '-')
84 85 slug = collapse(slug, '-')
85 86 return slug
86 87
87 88
88 89 def get_repo_slug(request):
89 90 return request.environ['pylons.routes_dict'].get('repo_name')
90 91
91 92
92 93 def action_logger(user, action, repo, ipaddr='', sa=None):
93 94 """
94 95 Action logger for various actions made by users
95 96
96 97 :param user: user that made this action, can be a unique username string or
97 98 object containing user_id attribute
98 99 :param action: action to log, should be on of predefined unique actions for
99 100 easy translations
100 101 :param repo: string name of repository or object containing repo_id,
101 102 that action was made on
102 103 :param ipaddr: optional ip address from what the action was made
103 104 :param sa: optional sqlalchemy session
104 105
105 106 """
106 107
107 108 if not sa:
108 109 sa = meta.Session()
109 110
110 111 try:
111 112 um = UserModel()
112 113 if hasattr(user, 'user_id'):
113 114 user_obj = user
114 115 elif isinstance(user, basestring):
115 116 user_obj = um.get_by_username(user, cache=False)
116 117 else:
117 118 raise Exception('You have to provide user object or username')
118 119
119 120 rm = RepoModel()
120 121 if hasattr(repo, 'repo_id'):
121 122 repo_obj = rm.get(repo.repo_id, cache=False)
122 123 repo_name = repo_obj.repo_name
123 124 elif isinstance(repo, basestring):
124 125 repo_name = repo.lstrip('/')
125 126 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
126 127 else:
127 128 raise Exception('You have to provide repository to action logger')
128 129
129 130 user_log = UserLog()
130 131 user_log.user_id = user_obj.user_id
131 132 user_log.action = action
132 133
133 134 user_log.repository_id = repo_obj.repo_id
134 135 user_log.repository_name = repo_name
135 136
136 137 user_log.action_date = datetime.datetime.now()
137 138 user_log.user_ip = ipaddr
138 139 sa.add(user_log)
139 140 sa.commit()
140 141
141 142 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
142 143 except:
143 144 log.error(traceback.format_exc())
144 145 sa.rollback()
145 146
146 147
147 148 def get_repos(path, recursive=False):
148 149 """
149 150 Scans given path for repos and return (name,(type,path)) tuple
150 151
151 152 :param path: path to scann for repositories
152 153 :param recursive: recursive search and return names with subdirs in front
153 154 """
154 155 from vcs.utils.helpers import get_scm
155 156 from vcs.exceptions import VCSError
156 157
157 158 if path.endswith(os.sep):
158 159 #remove ending slash for better results
159 160 path = path[:-1]
160 161
161 162 def _get_repos(p):
162 163 if not os.access(p, os.W_OK):
163 164 return
164 165 for dirpath in os.listdir(p):
165 166 if os.path.isfile(os.path.join(p, dirpath)):
166 167 continue
167 168 cur_path = os.path.join(p, dirpath)
168 169 try:
169 170 scm_info = get_scm(cur_path)
170 171 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
171 172 except VCSError:
172 173 if not recursive:
173 174 continue
174 175 #check if this dir containts other repos for recursive scan
175 176 rec_path = os.path.join(p, dirpath)
176 177 if os.path.isdir(rec_path):
177 178 for inner_scm in _get_repos(rec_path):
178 179 yield inner_scm
179 180
180 181 return _get_repos(path)
181 182
182 183
183 184 def check_repo_fast(repo_name, base_path):
184 185 """
185 186 Check given path for existence of directory
186 187 :param repo_name:
187 188 :param base_path:
188 189
189 190 :return False: if this directory is present
190 191 """
191 192 if os.path.isdir(os.path.join(base_path, repo_name)):
192 193 return False
193 194 return True
194 195
195 196
196 197 def check_repo(repo_name, base_path, verify=True):
197 198
198 199 repo_path = os.path.join(base_path, repo_name)
199 200
200 201 try:
201 202 if not check_repo_fast(repo_name, base_path):
202 203 return False
203 204 r = hg.repository(ui.ui(), repo_path)
204 205 if verify:
205 206 hg.verify(r)
206 207 #here we hnow that repo exists it was verified
207 208 log.info('%s repo is already created', repo_name)
208 209 return False
209 210 except RepoError:
210 211 #it means that there is no valid repo there...
211 212 log.info('%s repo is free for creation', repo_name)
212 213 return True
213 214
214 215
215 216 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
216 217 while True:
217 218 ok = raw_input(prompt)
218 219 if ok in ('y', 'ye', 'yes'):
219 220 return True
220 221 if ok in ('n', 'no', 'nop', 'nope'):
221 222 return False
222 223 retries = retries - 1
223 224 if retries < 0:
224 225 raise IOError
225 226 print complaint
226 227
227 228 #propagated from mercurial documentation
228 229 ui_sections = ['alias', 'auth',
229 230 'decode/encode', 'defaults',
230 231 'diff', 'email',
231 232 'extensions', 'format',
232 233 'merge-patterns', 'merge-tools',
233 234 'hooks', 'http_proxy',
234 235 'smtp', 'patch',
235 236 'paths', 'profiling',
236 237 'server', 'trusted',
237 238 'ui', 'web', ]
238 239
239 240
240 241 def make_ui(read_from='file', path=None, checkpaths=True):
241 242 """A function that will read python rc files or database
242 243 and make an mercurial ui object from read options
243 244
244 245 :param path: path to mercurial config file
245 246 :param checkpaths: check the path
246 247 :param read_from: read from 'file' or 'db'
247 248 """
248 249
249 250 baseui = ui.ui()
250 251
251 252 #clean the baseui object
252 253 baseui._ocfg = config.config()
253 254 baseui._ucfg = config.config()
254 255 baseui._tcfg = config.config()
255 256
256 257 if read_from == 'file':
257 258 if not os.path.isfile(path):
258 259 log.warning('Unable to read config file %s' % path)
259 260 return False
260 261 log.debug('reading hgrc from %s', path)
261 262 cfg = config.config()
262 263 cfg.read(path)
263 264 for section in ui_sections:
264 265 for k, v in cfg.items(section):
265 266 log.debug('settings ui from file[%s]%s:%s', section, k, v)
266 267 baseui.setconfig(section, k, v)
267 268
268 269 elif read_from == 'db':
269 270 sa = meta.Session()
270 271 ret = sa.query(RhodeCodeUi)\
271 272 .options(FromCache("sql_cache_short",
272 273 "get_hg_ui_settings")).all()
273 274
274 275 hg_ui = ret
275 276 for ui_ in hg_ui:
276 277 if ui_.ui_active:
277 278 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
278 279 ui_.ui_key, ui_.ui_value)
279 280 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
280 281
281 282 meta.Session.remove()
282 283 return baseui
283 284
284 285
285 286 def set_rhodecode_config(config):
286 287 """Updates pylons config with new settings from database
287 288
288 289 :param config:
289 290 """
290 from rhodecode.model.settings import SettingsModel
291 hgsettings = SettingsModel().get_app_settings()
291 hgsettings = RhodeCodeSettings.get_app_settings()
292 292
293 293 for k, v in hgsettings.items():
294 294 config[k] = v
295 295
296 296
297 297 def invalidate_cache(cache_key, *args):
298 298 """Puts cache invalidation task into db for
299 299 further global cache invalidation
300 300 """
301 301
302 302 from rhodecode.model.scm import ScmModel
303 303
304 304 if cache_key.startswith('get_repo_cached_'):
305 305 name = cache_key.split('get_repo_cached_')[-1]
306 306 ScmModel().mark_for_invalidation(name)
307 307
308 308
309 309 class EmptyChangeset(BaseChangeset):
310 310 """
311 311 An dummy empty changeset. It's possible to pass hash when creating
312 312 an EmptyChangeset
313 313 """
314 314
315 315 def __init__(self, cs='0' * 40, repo=None):
316 316 self._empty_cs = cs
317 317 self.revision = -1
318 318 self.message = ''
319 319 self.author = ''
320 320 self.date = ''
321 321 self.repository = repo
322 322
323 323 @LazyProperty
324 324 def raw_id(self):
325 325 """Returns raw string identifying this changeset, useful for web
326 326 representation.
327 327 """
328 328
329 329 return self._empty_cs
330 330
331 331 @LazyProperty
332 332 def short_id(self):
333 333 return self.raw_id[:12]
334 334
335 335 def get_file_changeset(self, path):
336 336 return self
337 337
338 338 def get_file_content(self, path):
339 339 return u''
340 340
341 341 def get_file_size(self, path):
342 342 return 0
343 343
344 344
345 345 def map_groups(groups):
346 346 """Checks for groups existence, and creates groups structures.
347 347 It returns last group in structure
348 348
349 349 :param groups: list of groups structure
350 350 """
351 351 sa = meta.Session()
352 352
353 353 parent = None
354 354 group = None
355 355 for lvl, group_name in enumerate(groups[:-1]):
356 356 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
357 357
358 358 if group is None:
359 359 group = Group(group_name, parent)
360 360 sa.add(group)
361 361 sa.commit()
362 362
363 363 parent = group
364 364
365 365 return group
366 366
367 367
368 368 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
369 369 """maps all repos given in initial_repo_list, non existing repositories
370 370 are created, if remove_obsolete is True it also check for db entries
371 371 that are not in initial_repo_list and removes them.
372 372
373 373 :param initial_repo_list: list of repositories found by scanning methods
374 374 :param remove_obsolete: check for obsolete entries in database
375 375 """
376 376
377 377 sa = meta.Session()
378 378 rm = RepoModel()
379 379 user = sa.query(User).filter(User.admin == True).first()
380 380 added = []
381 381 for name, repo in initial_repo_list.items():
382 382 group = map_groups(name.split('/'))
383 383 if not rm.get_by_repo_name(name, cache=False):
384 384 log.info('repository %s not found creating default', name)
385 385 added.append(name)
386 386 form_data = {
387 387 'repo_name': name,
388 388 'repo_type': repo.alias,
389 389 'description': repo.description \
390 390 if repo.description != 'unknown' else \
391 391 '%s repository' % name,
392 392 'private': False,
393 393 'group_id': getattr(group, 'group_id', None)
394 394 }
395 395 rm.create(form_data, user, just_db=True)
396 396
397 397 removed = []
398 398 if remove_obsolete:
399 399 #remove from database those repositories that are not in the filesystem
400 400 for repo in sa.query(Repository).all():
401 401 if repo.repo_name not in initial_repo_list.keys():
402 402 removed.append(repo.repo_name)
403 403 sa.delete(repo)
404 404 sa.commit()
405 405
406 406 return added, removed
407 407
408 408
409 409 class OrderedDict(dict, DictMixin):
410 410
411 411 def __init__(self, *args, **kwds):
412 412 if len(args) > 1:
413 413 raise TypeError('expected at most 1 arguments, got %d' % len(args))
414 414 try:
415 415 self.__end
416 416 except AttributeError:
417 417 self.clear()
418 418 self.update(*args, **kwds)
419 419
420 420 def clear(self):
421 421 self.__end = end = []
422 422 end += [None, end, end] # sentinel node for doubly linked list
423 423 self.__map = {} # key --> [key, prev, next]
424 424 dict.clear(self)
425 425
426 426 def __setitem__(self, key, value):
427 427 if key not in self:
428 428 end = self.__end
429 429 curr = end[1]
430 430 curr[2] = end[1] = self.__map[key] = [key, curr, end]
431 431 dict.__setitem__(self, key, value)
432 432
433 433 def __delitem__(self, key):
434 434 dict.__delitem__(self, key)
435 435 key, prev, next = self.__map.pop(key)
436 436 prev[2] = next
437 437 next[1] = prev
438 438
439 439 def __iter__(self):
440 440 end = self.__end
441 441 curr = end[2]
442 442 while curr is not end:
443 443 yield curr[0]
444 444 curr = curr[2]
445 445
446 446 def __reversed__(self):
447 447 end = self.__end
448 448 curr = end[1]
449 449 while curr is not end:
450 450 yield curr[0]
451 451 curr = curr[1]
452 452
453 453 def popitem(self, last=True):
454 454 if not self:
455 455 raise KeyError('dictionary is empty')
456 456 if last:
457 457 key = reversed(self).next()
458 458 else:
459 459 key = iter(self).next()
460 460 value = self.pop(key)
461 461 return key, value
462 462
463 463 def __reduce__(self):
464 464 items = [[k, self[k]] for k in self]
465 465 tmp = self.__map, self.__end
466 466 del self.__map, self.__end
467 467 inst_dict = vars(self).copy()
468 468 self.__map, self.__end = tmp
469 469 if inst_dict:
470 470 return (self.__class__, (items,), inst_dict)
471 471 return self.__class__, (items,)
472 472
473 473 def keys(self):
474 474 return list(self)
475 475
476 476 setdefault = DictMixin.setdefault
477 477 update = DictMixin.update
478 478 pop = DictMixin.pop
479 479 values = DictMixin.values
480 480 items = DictMixin.items
481 481 iterkeys = DictMixin.iterkeys
482 482 itervalues = DictMixin.itervalues
483 483 iteritems = DictMixin.iteritems
484 484
485 485 def __repr__(self):
486 486 if not self:
487 487 return '%s()' % (self.__class__.__name__,)
488 488 return '%s(%r)' % (self.__class__.__name__, self.items())
489 489
490 490 def copy(self):
491 491 return self.__class__(self)
492 492
493 493 @classmethod
494 494 def fromkeys(cls, iterable, value=None):
495 495 d = cls()
496 496 for key in iterable:
497 497 d[key] = value
498 498 return d
499 499
500 500 def __eq__(self, other):
501 501 if isinstance(other, OrderedDict):
502 502 return len(self) == len(other) and self.items() == other.items()
503 503 return dict.__eq__(self, other)
504 504
505 505 def __ne__(self, other):
506 506 return not self == other
507 507
508 508
509 509 #set cache regions for beaker so celery can utilise it
510 510 def add_cache(settings):
511 511 cache_settings = {'regions': None}
512 512 for key in settings.keys():
513 513 for prefix in ['beaker.cache.', 'cache.']:
514 514 if key.startswith(prefix):
515 515 name = key.split(prefix)[1].strip()
516 516 cache_settings[name] = settings[key].strip()
517 517 if cache_settings['regions']:
518 518 for region in cache_settings['regions'].split(','):
519 519 region = region.strip()
520 520 region_settings = {}
521 521 for key, value in cache_settings.items():
522 522 if key.startswith(region):
523 523 region_settings[key.split('.')[1]] = value
524 524 region_settings['expire'] = int(region_settings.get('expire',
525 525 60))
526 526 region_settings.setdefault('lock_dir',
527 527 cache_settings.get('lock_dir'))
528 528 region_settings.setdefault('data_dir',
529 529 cache_settings.get('data_dir'))
530 530
531 531 if 'type' not in region_settings:
532 532 region_settings['type'] = cache_settings.get('type',
533 533 'memory')
534 534 beaker.cache.cache_regions[region] = region_settings
535 535
536 536
537 537 def get_current_revision():
538 538 """Returns tuple of (number, id) from repository containing this package
539 539 or None if repository could not be found.
540 540 """
541 541
542 542 try:
543 543 from vcs import get_repo
544 544 from vcs.utils.helpers import get_scm
545 545 from vcs.exceptions import RepositoryError, VCSError
546 546 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
547 547 scm = get_scm(repopath)[0]
548 548 repo = get_repo(path=repopath, alias=scm)
549 549 tip = repo.get_changeset()
550 550 return (tip.revision, tip.short_id)
551 551 except (ImportError, RepositoryError, VCSError), err:
552 552 logging.debug("Cannot retrieve rhodecode's revision. Original error "
553 553 "was: %s" % err)
554 554 return None
555 555
556 556
557 557 #==============================================================================
558 558 # TEST FUNCTIONS AND CREATORS
559 559 #==============================================================================
560 560 def create_test_index(repo_location, full_index):
561 561 """Makes default test index
562 562 :param repo_location:
563 563 :param full_index:
564 564 """
565 565 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
566 566 from rhodecode.lib.pidlock import DaemonLock, LockHeld
567 567 import shutil
568 568
569 569 index_location = os.path.join(repo_location, 'index')
570 570 if os.path.exists(index_location):
571 571 shutil.rmtree(index_location)
572 572
573 573 try:
574 574 l = DaemonLock()
575 575 WhooshIndexingDaemon(index_location=index_location,
576 576 repo_location=repo_location)\
577 577 .run(full_index=full_index)
578 578 l.release()
579 579 except LockHeld:
580 580 pass
581 581
582 582
583 583 def create_test_env(repos_test_path, config):
584 584 """Makes a fresh database and
585 585 install test repository into tmp dir
586 586 """
587 587 from rhodecode.lib.db_manage import DbManage
588 588 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
589 589 HG_FORK, GIT_FORK, TESTS_TMP_PATH
590 590 import tarfile
591 591 import shutil
592 592 from os.path import dirname as dn, join as jn, abspath
593 593
594 594 log = logging.getLogger('TestEnvCreator')
595 595 # create logger
596 596 log.setLevel(logging.DEBUG)
597 597 log.propagate = True
598 598 # create console handler and set level to debug
599 599 ch = logging.StreamHandler()
600 600 ch.setLevel(logging.DEBUG)
601 601
602 602 # create formatter
603 603 formatter = logging.Formatter("%(asctime)s - %(name)s -"
604 604 " %(levelname)s - %(message)s")
605 605
606 606 # add formatter to ch
607 607 ch.setFormatter(formatter)
608 608
609 609 # add ch to logger
610 610 log.addHandler(ch)
611 611
612 612 #PART ONE create db
613 613 dbconf = config['sqlalchemy.db1.url']
614 614 log.debug('making test db %s', dbconf)
615 615
616 616 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
617 617 tests=True)
618 618 dbmanage.create_tables(override=True)
619 619 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
620 620 dbmanage.create_default_user()
621 621 dbmanage.admin_prompt()
622 622 dbmanage.create_permissions()
623 623 dbmanage.populate_default_permissions()
624 624
625 625 #PART TWO make test repo
626 626 log.debug('making test vcs repositories')
627 627
628 628 #remove old one from previos tests
629 629 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
630 630
631 631 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
632 632 log.debug('removing %s', r)
633 633 shutil.rmtree(jn(TESTS_TMP_PATH, r))
634 634
635 635 #CREATE DEFAULT HG REPOSITORY
636 636 cur_dir = dn(dn(abspath(__file__)))
637 637 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
638 638 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
639 639 tar.close()
640 640
641 641
642 642 #==============================================================================
643 643 # PASTER COMMANDS
644 644 #==============================================================================
645 645 class BasePasterCommand(Command):
646 646 """
647 647 Abstract Base Class for paster commands.
648 648
649 649 The celery commands are somewhat aggressive about loading
650 650 celery.conf, and since our module sets the `CELERY_LOADER`
651 651 environment variable to our loader, we have to bootstrap a bit and
652 652 make sure we've had a chance to load the pylons config off of the
653 653 command line, otherwise everything fails.
654 654 """
655 655 min_args = 1
656 656 min_args_error = "Please provide a paster config file as an argument."
657 657 takes_config_file = 1
658 658 requires_config_file = True
659 659
660 660 def notify_msg(self, msg, log=False):
661 661 """Make a notification to user, additionally if logger is passed
662 662 it logs this action using given logger
663 663
664 664 :param msg: message that will be printed to user
665 665 :param log: logging instance, to use to additionally log this message
666 666
667 667 """
668 668 if log and isinstance(log, logging):
669 669 log(msg)
670 670
671 671 def run(self, args):
672 672 """
673 673 Overrides Command.run
674 674
675 675 Checks for a config file argument and loads it.
676 676 """
677 677 if len(args) < self.min_args:
678 678 raise BadCommand(
679 679 self.min_args_error % {'min_args': self.min_args,
680 680 'actual_args': len(args)})
681 681
682 682 # Decrement because we're going to lob off the first argument.
683 683 # @@ This is hacky
684 684 self.min_args -= 1
685 685 self.bootstrap_config(args[0])
686 686 self.update_parser()
687 687 return super(BasePasterCommand, self).run(args[1:])
688 688
689 689 def update_parser(self):
690 690 """
691 691 Abstract method. Allows for the class's parser to be updated
692 692 before the superclass's `run` method is called. Necessary to
693 693 allow options/arguments to be passed through to the underlying
694 694 celery command.
695 695 """
696 696 raise NotImplementedError("Abstract Method.")
697 697
698 698 def bootstrap_config(self, conf):
699 699 """
700 700 Loads the pylons configuration.
701 701 """
702 702 from pylons import config as pylonsconfig
703 703
704 704 path_to_ini_file = os.path.realpath(conf)
705 705 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
706 706 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,526 +1,531 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 from datetime import date
30 30
31 31 from sqlalchemy import *
32 32 from sqlalchemy.exc import DatabaseError
33 33 from sqlalchemy.orm import relationship, backref
34 34 from sqlalchemy.orm.interfaces import MapperExtension
35 35
36 36 from rhodecode.lib import str2bool
37 37 from rhodecode.model.meta import Base, Session
38 38 from rhodecode.model.caching_query import FromCache
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42 #==============================================================================
43 43 # MAPPER EXTENSIONS
44 44 #==============================================================================
45 45
46 46 class RepositoryMapper(MapperExtension):
47 47 def after_update(self, mapper, connection, instance):
48 48 pass
49 49
50 50
51 51 class RhodeCodeSettings(Base):
52 52 __tablename__ = 'rhodecode_settings'
53 53 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
54 54 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
55 55 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
56 56 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
57 57
58 58 def __init__(self, k='', v=''):
59 59 self.app_settings_name = k
60 60 self.app_settings_value = v
61 61
62 62 def __repr__(self):
63 63 return "<%s('%s:%s')>" % (self.__class__.__name__,
64 64 self.app_settings_name, self.app_settings_value)
65 65
66 66
67 67 @classmethod
68 def get_by_name(cls, ldap_key):
69 return Session.query(cls)\
70 .filter(cls.app_settings_name == ldap_key).scalar()
71
72 @classmethod
68 73 def get_app_settings(cls, cache=False):
69 74
70 75 ret = Session.query(cls)
71 76
72 77 if cache:
73 78 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
74 79
75 80 if not ret:
76 81 raise Exception('Could not get application settings !')
77 82 settings = {}
78 83 for each in ret:
79 84 settings['rhodecode_' + each.app_settings_name] = \
80 85 each.app_settings_value
81 86
82 87 return settings
83 88
84 89 @classmethod
85 90 def get_ldap_settings(cls, cache=False):
86 91 ret = Session.query(cls)\
87 92 .filter(cls.app_settings_name.startswith('ldap_'))\
88 93 .all()
89 94 fd = {}
90 95 for row in ret:
91 fd.update({row.app_settings_name:str2bool(row.app_settings_value)})
96 fd.update({row.app_settings_name:row.app_settings_value})
92 97 return fd
93 98
94 99
95 100 class RhodeCodeUi(Base):
96 101 __tablename__ = 'rhodecode_ui'
97 102 __table_args__ = {'useexisting':True}
98 103 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
99 104 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
100 105 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
101 106 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
102 107 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
103 108
104 109
105 110 class User(Base):
106 111 __tablename__ = 'users'
107 112 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
108 113 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
109 114 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
110 115 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
111 116 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
112 117 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
113 118 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
114 119 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
115 120 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
116 121 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
117 122 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
118 123 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
119 124
120 125 user_log = relationship('UserLog', cascade='all')
121 126 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
122 127
123 128 repositories = relationship('Repository')
124 129 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
125 130
126 131 group_member = relationship('UsersGroupMember', cascade='all')
127 132
128 133 @property
129 134 def full_contact(self):
130 135 return '%s %s <%s>' % (self.name, self.lastname, self.email)
131 136
132 137 @property
133 138 def short_contact(self):
134 139 return '%s %s' % (self.name, self.lastname)
135 140
136 141
137 142 @property
138 143 def is_admin(self):
139 144 return self.admin
140 145
141 146 def __repr__(self):
142 147 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
143 148 self.user_id, self.username)
144 149
145 150 @classmethod
146 151 def by_username(cls, username):
147 152 return Session.query(cls).filter(cls.username == username).one()
148 153
149 154
150 155 def update_lastlogin(self):
151 156 """Update user lastlogin"""
152 157
153 158 try:
154 159 session = Session.object_session(self)
155 160 self.last_login = datetime.datetime.now()
156 161 session.add(self)
157 162 session.commit()
158 163 log.debug('updated user %s lastlogin', self.username)
159 164 except (DatabaseError,):
160 165 session.rollback()
161 166
162 167
163 168 class UserLog(Base):
164 169 __tablename__ = 'user_logs'
165 170 __table_args__ = {'useexisting':True}
166 171 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
167 172 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
168 173 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
169 174 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
170 175 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
171 176 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
172 177 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
173 178
174 179 @property
175 180 def action_as_day(self):
176 181 return date(*self.action_date.timetuple()[:3])
177 182
178 183 user = relationship('User')
179 184 repository = relationship('Repository')
180 185
181 186
182 187 class UsersGroup(Base):
183 188 __tablename__ = 'users_groups'
184 189 __table_args__ = {'useexisting':True}
185 190
186 191 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
187 192 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
188 193 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
189 194
190 195 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
191 196
192 197
193 198 @classmethod
194 199 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
195 200 if case_insensitive:
196 201 gr = Session.query(cls)\
197 202 .filter(cls.users_group_name.ilike(group_name))
198 203 else:
199 204 gr = Session.query(UsersGroup)\
200 205 .filter(UsersGroup.users_group_name == group_name)
201 206 if cache:
202 207 gr = gr.options(FromCache("sql_cache_short",
203 208 "get_user_%s" % group_name))
204 209 return gr.scalar()
205 210
206 211 class UsersGroupMember(Base):
207 212 __tablename__ = 'users_groups_members'
208 213 __table_args__ = {'useexisting':True}
209 214
210 215 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
211 216 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
212 217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
213 218
214 219 user = relationship('User', lazy='joined')
215 220 users_group = relationship('UsersGroup')
216 221
217 222 def __init__(self, gr_id='', u_id=''):
218 223 self.users_group_id = gr_id
219 224 self.user_id = u_id
220 225
221 226 class Repository(Base):
222 227 __tablename__ = 'repositories'
223 228 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
224 229 __mapper_args__ = {'extension':RepositoryMapper()}
225 230
226 231 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
227 232 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
228 233 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
229 234 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
230 235 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
231 236 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
232 237 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
233 238 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
234 239 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
235 240 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
236 241 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
237 242
238 243
239 244 user = relationship('User')
240 245 fork = relationship('Repository', remote_side=repo_id)
241 246 group = relationship('Group')
242 247 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
243 248 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
244 249 stats = relationship('Statistics', cascade='all', uselist=False)
245 250
246 251 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
247 252
248 253 logs = relationship('UserLog', cascade='all')
249 254
250 255 def __repr__(self):
251 256 return "<%s('%s:%s')>" % (self.__class__.__name__,
252 257 self.repo_id, self.repo_name)
253 258
254 259 @classmethod
255 260 def by_repo_name(cls, repo_name):
256 261 return Session.query(cls).filter(cls.repo_name == repo_name).one()
257 262
258 263 @property
259 264 def just_name(self):
260 265 return self.repo_name.split(os.sep)[-1]
261 266
262 267 @property
263 268 def groups_with_parents(self):
264 269 groups = []
265 270 if self.group is None:
266 271 return groups
267 272
268 273 cur_gr = self.group
269 274 groups.insert(0, cur_gr)
270 275 while 1:
271 276 gr = getattr(cur_gr, 'parent_group', None)
272 277 cur_gr = cur_gr.parent_group
273 278 if gr is None:
274 279 break
275 280 groups.insert(0, gr)
276 281
277 282 return groups
278 283
279 284 @property
280 285 def groups_and_repo(self):
281 286 return self.groups_with_parents, self.just_name
282 287
283 288
284 289 class Group(Base):
285 290 __tablename__ = 'groups'
286 291 __table_args__ = (UniqueConstraint('group_name'), {'useexisting':True},)
287 292
288 293 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
289 294 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
290 295 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
291 296
292 297 parent_group = relationship('Group', remote_side=group_id)
293 298
294 299
295 300 def __init__(self, group_name='', parent_group=None):
296 301 self.group_name = group_name
297 302 self.parent_group = parent_group
298 303
299 304 def __repr__(self):
300 305 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
301 306 self.group_name)
302 307
303 308 @property
304 309 def parents(self):
305 310 groups = []
306 311 if self.parent_group is None:
307 312 return groups
308 313 cur_gr = self.parent_group
309 314 groups.insert(0, cur_gr)
310 315 while 1:
311 316 gr = getattr(cur_gr, 'parent_group', None)
312 317 cur_gr = cur_gr.parent_group
313 318 if gr is None:
314 319 break
315 320 groups.insert(0, gr)
316 321 return groups
317 322
318 323 @property
319 324 def repositories(self):
320 325 return Session.query(Repository).filter(Repository.group == self).all()
321 326
322 327 class Permission(Base):
323 328 __tablename__ = 'permissions'
324 329 __table_args__ = {'useexisting':True}
325 330 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
326 331 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 332 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 333
329 334 def __repr__(self):
330 335 return "<%s('%s:%s')>" % (self.__class__.__name__,
331 336 self.permission_id, self.permission_name)
332 337
333 338 @classmethod
334 339 def get_by_key(cls, key):
335 340 return Session.query(cls).filter(cls.permission_name == key).scalar()
336 341
337 342 class RepoToPerm(Base):
338 343 __tablename__ = 'repo_to_perm'
339 344 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
340 345 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
341 346 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
342 347 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
343 348 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
344 349
345 350 user = relationship('User')
346 351 permission = relationship('Permission')
347 352 repository = relationship('Repository')
348 353
349 354 class UserToPerm(Base):
350 355 __tablename__ = 'user_to_perm'
351 356 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
352 357 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
353 358 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
354 359 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
355 360
356 361 user = relationship('User')
357 362 permission = relationship('Permission')
358 363
359 364 @classmethod
360 365 def has_perm(cls, user_id, perm):
361 366 if not isinstance(perm, Permission):
362 367 raise Exception('perm needs to be an instance of Permission class')
363 368
364 369 return Session.query(cls).filter(cls.user_id == user_id)\
365 370 .filter(cls.permission == perm).scalar() is not None
366 371
367 372 @classmethod
368 373 def grant_perm(cls, user_id, perm):
369 374 if not isinstance(perm, Permission):
370 375 raise Exception('perm needs to be an instance of Permission class')
371 376
372 377 new = cls()
373 378 new.user_id = user_id
374 379 new.permission = perm
375 380 try:
376 381 Session.add(new)
377 382 Session.commit()
378 383 except:
379 384 Session.rollback()
380 385
381 386
382 387 @classmethod
383 388 def revoke_perm(cls, user_id, perm):
384 389 if not isinstance(perm, Permission):
385 390 raise Exception('perm needs to be an instance of Permission class')
386 391
387 392 try:
388 393 Session.query(cls).filter(cls.user_id == user_id)\
389 394 .filter(cls.permission == perm).delete()
390 395 Session.commit()
391 396 except:
392 397 Session.rollback()
393 398
394 399 class UsersGroupRepoToPerm(Base):
395 400 __tablename__ = 'users_group_repo_to_perm'
396 401 __table_args__ = (UniqueConstraint('users_group_id', 'permission_id'), {'useexisting':True})
397 402 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
398 403 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
399 404 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
400 405 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
401 406
402 407 users_group = relationship('UsersGroup')
403 408 permission = relationship('Permission')
404 409 repository = relationship('Repository')
405 410
406 411
407 412 class UsersGroupToPerm(Base):
408 413 __tablename__ = 'users_group_to_perm'
409 414 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
410 415 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
411 416 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
412 417
413 418 users_group = relationship('UsersGroup')
414 419 permission = relationship('Permission')
415 420
416 421
417 422 @classmethod
418 423 def has_perm(cls, users_group_id, perm):
419 424 if not isinstance(perm, Permission):
420 425 raise Exception('perm needs to be an instance of Permission class')
421 426
422 427 return Session.query(cls).filter(cls.users_group_id ==
423 428 users_group_id)\
424 429 .filter(cls.permission == perm)\
425 430 .scalar() is not None
426 431
427 432 @classmethod
428 433 def grant_perm(cls, users_group_id, perm):
429 434 if not isinstance(perm, Permission):
430 435 raise Exception('perm needs to be an instance of Permission class')
431 436
432 437 new = cls()
433 438 new.users_group_id = users_group_id
434 439 new.permission = perm
435 440 try:
436 441 Session.add(new)
437 442 Session.commit()
438 443 except:
439 444 Session.rollback()
440 445
441 446
442 447 @classmethod
443 448 def revoke_perm(cls, users_group_id, perm):
444 449 if not isinstance(perm, Permission):
445 450 raise Exception('perm needs to be an instance of Permission class')
446 451
447 452 try:
448 453 Session.query(cls).filter(cls.users_group_id == users_group_id)\
449 454 .filter(cls.permission == perm).delete()
450 455 Session.commit()
451 456 except:
452 457 Session.rollback()
453 458
454 459
455 460 class GroupToPerm(Base):
456 461 __tablename__ = 'group_to_perm'
457 462 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'useexisting':True})
458 463
459 464 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
460 465 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
461 466 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
462 467 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
463 468
464 469 user = relationship('User')
465 470 permission = relationship('Permission')
466 471 group = relationship('Group')
467 472
468 473 class Statistics(Base):
469 474 __tablename__ = 'statistics'
470 475 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
471 476 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
472 477 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
473 478 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
474 479 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
475 480 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
476 481 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
477 482
478 483 repository = relationship('Repository', single_parent=True)
479 484
480 485 class UserFollowing(Base):
481 486 __tablename__ = 'user_followings'
482 487 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
483 488 UniqueConstraint('user_id', 'follows_user_id')
484 489 , {'useexisting':True})
485 490
486 491 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
487 492 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
488 493 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
489 494 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
490 495 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
491 496
492 497 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
493 498
494 499 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
495 500 follows_repository = relationship('Repository', order_by='Repository.repo_name')
496 501
497 502
498 503
499 504 @classmethod
500 505 def get_repo_followers(cls, repo_id):
501 506 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
502 507
503 508 class CacheInvalidation(Base):
504 509 __tablename__ = 'cache_invalidation'
505 510 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
506 511 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
507 512 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
508 513 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
509 514 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
510 515
511 516
512 517 def __init__(self, cache_key, cache_args=''):
513 518 self.cache_key = cache_key
514 519 self.cache_args = cache_args
515 520 self.cache_active = False
516 521
517 522 def __repr__(self):
518 523 return "<%s('%s:%s')>" % (self.__class__.__name__,
519 524 self.cache_id, self.cache_key)
520 525
521 526 class DbMigrateVersion(Base):
522 527 __tablename__ = 'db_migrate_version'
523 528 __table_args__ = {'useexisting':True}
524 529 repository_id = Column('repository_id', String(250), primary_key=True)
525 530 repository_path = Column('repository_path', Text)
526 531 version = Column('version', Integer)
@@ -1,7 +1,21 b''
1 1 from rhodecode.tests import *
2 2
3 3 class TestLdapSettingsController(TestController):
4 4
5 5 def test_index(self):
6 response = self.app.get(url(controller='admin/ldap_settings', action='index'))
6 self.log_user()
7 response = self.app.get(url(controller='admin/ldap_settings',
8 action='index'))
7 9 # Test response...
10
11 def test_ldap_save_settings(self):
12 pass
13
14 def test_ldap_error_form(self):
15 pass
16
17 def test_ldap_login(self):
18 pass
19
20 def test_ldap_login_incorrect(self):
21 pass
@@ -1,194 +1,193 b''
1 1 from rhodecode.lib.auth import get_crypt_password, check_password
2 from rhodecode.model.db import User
2 from rhodecode.model.db import User, RhodeCodeSettings
3 3 from rhodecode.tests import *
4 from rhodecode.model.settings import SettingsModel
5 4
6 5 class TestAdminSettingsController(TestController):
7 6
8 7 def test_index(self):
9 8 response = self.app.get(url('admin_settings'))
10 9 # Test response...
11 10
12 11 def test_index_as_xml(self):
13 12 response = self.app.get(url('formatted_admin_settings', format='xml'))
14 13
15 14 def test_create(self):
16 15 response = self.app.post(url('admin_settings'))
17 16
18 17 def test_new(self):
19 18 response = self.app.get(url('admin_new_setting'))
20 19
21 20 def test_new_as_xml(self):
22 21 response = self.app.get(url('formatted_admin_new_setting', format='xml'))
23 22
24 23 def test_update(self):
25 24 response = self.app.put(url('admin_setting', setting_id=1))
26 25
27 26 def test_update_browser_fakeout(self):
28 27 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='put'))
29 28
30 29 def test_delete(self):
31 30 response = self.app.delete(url('admin_setting', setting_id=1))
32 31
33 32 def test_delete_browser_fakeout(self):
34 33 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='delete'))
35 34
36 35 def test_show(self):
37 36 response = self.app.get(url('admin_setting', setting_id=1))
38 37
39 38 def test_show_as_xml(self):
40 39 response = self.app.get(url('formatted_admin_setting', setting_id=1, format='xml'))
41 40
42 41 def test_edit(self):
43 42 response = self.app.get(url('admin_edit_setting', setting_id=1))
44 43
45 44 def test_edit_as_xml(self):
46 45 response = self.app.get(url('formatted_admin_edit_setting', setting_id=1, format='xml'))
47 46
48 47
49 48 def test_ga_code_active(self):
50 49 self.log_user()
51 50 old_title = 'RhodeCode'
52 51 old_realm = 'RhodeCode authentication'
53 52 new_ga_code = 'ga-test-123456789'
54 53 response = self.app.post(url('admin_setting', setting_id='global'),
55 54 params=dict(
56 55 _method='put',
57 56 rhodecode_title=old_title,
58 57 rhodecode_realm=old_realm,
59 58 rhodecode_ga_code=new_ga_code
60 59 ))
61 60
62 61 assert 'Updated application settings' in response.session['flash'][0][1], 'no flash message about success of change'
63 assert SettingsModel(self.sa).get_app_settings()['rhodecode_ga_code'] == new_ga_code, 'change not in database'
62 assert RhodeCodeSettings.get_app_settings()['rhodecode_ga_code'] == new_ga_code, 'change not in database'
64 63
65 64 response = response.follow()
66 65 assert """_gaq.push(['_setAccount', '%s']);""" % new_ga_code in response.body
67 66
68 67 def test_ga_code_inactive(self):
69 68 self.log_user()
70 69 old_title = 'RhodeCode'
71 70 old_realm = 'RhodeCode authentication'
72 71 new_ga_code = ''
73 72 response = self.app.post(url('admin_setting', setting_id='global'),
74 73 params=dict(
75 74 _method='put',
76 75 rhodecode_title=old_title,
77 76 rhodecode_realm=old_realm,
78 77 rhodecode_ga_code=new_ga_code
79 78 ))
80 79
81 80 assert 'Updated application settings' in response.session['flash'][0][1], 'no flash message about success of change'
82 assert SettingsModel(self.sa).get_app_settings()['rhodecode_ga_code'] == new_ga_code, 'change not in database'
81 assert RhodeCodeSettings.get_app_settings()['rhodecode_ga_code'] == new_ga_code, 'change not in database'
83 82
84 83 response = response.follow()
85 84 assert """_gaq.push(['_setAccount', '%s']);""" % new_ga_code not in response.body
86 85
87 86
88 87 def test_title_change(self):
89 88 self.log_user()
90 89 old_title = 'RhodeCode'
91 90 new_title = old_title + '_changed'
92 91 old_realm = 'RhodeCode authentication'
93 92 response = self.app.post(url('admin_setting', setting_id='global'),
94 93 params=dict(
95 94 _method='put',
96 95 rhodecode_title=new_title,
97 96 rhodecode_realm=old_realm,
98 97 rhodecode_ga_code=''
99 98 ))
100 99
101 100
102 101 assert 'Updated application settings' in response.session['flash'][0][1], 'no flash message about success of change'
103 assert SettingsModel(self.sa).get_app_settings()['rhodecode_title'] == new_title, 'change not in database'
102 assert RhodeCodeSettings.get_app_settings()['rhodecode_title'] == new_title, 'change not in database'
104 103
105 104 response = response.follow()
106 105 assert """<h1><a href="/">%s</a></h1>""" % new_title in response.body
107 106
108 107
109 108 def test_my_account(self):
110 109 self.log_user()
111 110 response = self.app.get(url('admin_settings_my_account'))
112 111 print response
113 112 assert 'value="test_admin' in response.body
114 113
115 114 def test_my_account_update(self):
116 115 self.log_user()
117 116
118 117 new_email = 'new@mail.pl'
119 118 new_name = 'NewName'
120 119 new_lastname = 'NewLastname'
121 120 new_password = 'test123'
122 121
123 122
124 123 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
125 124 _method='put',
126 125 username='test_admin',
127 126 new_password=new_password,
128 127 password='',
129 128 name=new_name,
130 129 lastname=new_lastname,
131 130 email=new_email,))
132 131 response.follow()
133 132
134 133 assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
135 134 user = self.sa.query(User).filter(User.username == 'test_admin').one()
136 135 assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email)
137 136 assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name)
138 137 assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname)
139 138 assert check_password(new_password, user.password) is True, 'password field mismatch %s vs %s' % (user.password, new_password)
140 139
141 140 #bring back the admin settings
142 141 old_email = 'test_admin@mail.com'
143 142 old_name = 'RhodeCode'
144 143 old_lastname = 'Admin'
145 144 old_password = 'test12'
146 145
147 146 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
148 147 _method='put',
149 148 username='test_admin',
150 149 new_password=old_password,
151 150 password='',
152 151 name=old_name,
153 152 lastname=old_lastname,
154 153 email=old_email,))
155 154
156 155 response.follow()
157 156 assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
158 157 user = self.sa.query(User).filter(User.username == 'test_admin').one()
159 158 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
160 159
161 160 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
162 161 assert user.name == old_name, 'updated field mismatch %s vs %s' % (user.name, old_name)
163 162 assert user.lastname == old_lastname, 'updated field mismatch %s vs %s' % (user.lastname, old_lastname)
164 163 assert check_password(old_password, user.password) is True , 'password updated field mismatch %s vs %s' % (user.password, old_password)
165 164
166 165
167 166 def test_my_account_update_err_email_exists(self):
168 167 self.log_user()
169 168
170 169 new_email = 'test_regular@mail.com'#already exisitn email
171 170 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
172 171 _method='put',
173 172 username='test_admin',
174 173 new_password='test12',
175 174 name='NewName',
176 175 lastname='NewLastname',
177 176 email=new_email,))
178 177
179 178 assert 'This e-mail address is already taken' in response.body, 'Missing error message about existing email'
180 179
181 180
182 181 def test_my_account_update_err(self):
183 182 self.log_user('test_regular2', 'test12')
184 183
185 184 new_email = 'newmail.pl'
186 185 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
187 186 _method='put',
188 187 username='test_admin',
189 188 new_password='test12',
190 189 name='NewName',
191 190 lastname='NewLastname',
192 191 email=new_email,))
193 192 assert 'An email address must contain a single @' in response.body, 'Missing error message about wrong email'
194 193 assert 'This username already exists' in response.body, 'Missing error message about existing user'
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now