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