##// END OF EJS Templates
merged upto rev 019026a8cf67
marcink -
r1639:95c3e33e merge default
parent child Browse files
Show More
@@ -0,0 +1,63 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.model.users_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 repository permission model for RhodeCode
7
8 :created_on: Oct 1, 2011
9 :author: nvinot
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 :license: GPLv3, see COPYING for more details.
12 """
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
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
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/>.
25
26 import logging
27 from rhodecode.model.db import BaseModel, RepoToPerm, Permission
28 from rhodecode.model.meta import Session
29
30 log = logging.getLogger(__name__)
31
32 class RepositoryPermissionModel(BaseModel):
33 def get_user_permission(self, repository, user):
34 return RepoToPerm.query() \
35 .filter(RepoToPerm.user == user) \
36 .filter(RepoToPerm.repository == repository) \
37 .scalar()
38
39 def update_user_permission(self, repository, user, permission):
40 permission = Permission.get_by_key(permission)
41 current = self.get_user_permission(repository, user)
42 if current:
43 if not current.permission is permission:
44 current.permission = permission
45 else:
46 p = RepoToPerm()
47 p.user = user
48 p.repository = repository
49 p.permission = permission
50 Session.add(p)
51 Session.commit()
52
53 def delete_user_permission(self, repository, user):
54 current = self.get_user_permission(repository, user)
55 if current:
56 Session.delete(current)
57 Session.commit()
58
59 def update_or_delete_user_permission(self, repository, user, permission):
60 if permission:
61 self.update_user_permission(repository, user, permission)
62 else:
63 self.delete_user_permission(repository, user)
@@ -0,0 +1,89 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.model.users_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 users group model for RhodeCode
7
8 :created_on: Oct 1, 2011
9 :author: nvinot
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 :license: GPLv3, see COPYING for more details.
12 """
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
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
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/>.
25
26 import logging
27 import traceback
28
29 from rhodecode.model import BaseModel
30 from rhodecode.model.caching_query import FromCache
31 from rhodecode.model.db import UsersGroupMember, UsersGroup
32
33 log = logging.getLogger(__name__)
34
35 class UsersGroupModel(BaseModel):
36
37 def get(self, users_group_id, cache = False):
38 users_group = UsersGroup.query()
39 if cache:
40 users_group = users_group.options(FromCache("sql_cache_short",
41 "get_users_group_%s" % users_group_id))
42 return users_group.get(users_group_id)
43
44 def get_by_name(self, name, cache = False, case_insensitive = False):
45 users_group = UsersGroup.query()
46 if case_insensitive:
47 users_group = users_group.filter(UsersGroup.users_group_name.ilike(name))
48 else:
49 users_group = users_group.filter(UsersGroup.users_group_name == name)
50 if cache:
51 users_group = users_group.options(FromCache("sql_cache_short",
52 "get_users_group_%s" % name))
53 return users_group.scalar()
54
55 def create(self, form_data):
56 try:
57 new_users_group = UsersGroup()
58 for k, v in form_data.items():
59 setattr(new_users_group, k, v)
60
61 self.sa.add(new_users_group)
62 self.sa.commit()
63 return new_users_group
64 except:
65 log.error(traceback.format_exc())
66 self.sa.rollback()
67 raise
68
69 def add_user_to_group(self, users_group, user):
70 for m in users_group.members:
71 u = m.user
72 if u.user_id == user.user_id:
73 return m
74
75 try:
76 users_group_member = UsersGroupMember()
77 users_group_member.user = user
78 users_group_member.users_group = users_group
79
80 users_group.members.append(users_group_member)
81 user.group_member.append(users_group_member)
82
83 self.sa.add(users_group_member)
84 self.sa.commit()
85 return users_group_member
86 except:
87 log.error(traceback.format_exc())
88 self.sa.rollback()
89 raise
@@ -1,233 +1,235 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 10 pdebug = false
11 11 ################################################################################
12 12 ## Uncomment and replace with the address which should receive ##
13 13 ## any error reports after application crash ##
14 14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 15 ################################################################################
16 16 #email_to = admin@localhost
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 20
21 21 #smtp_server = mail.server.com
22 22 #smtp_username =
23 23 #smtp_password =
24 24 #smtp_port =
25 25 #smtp_use_tls = false
26 26 #smtp_use_ssl = true
27 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 #smtp_auth =
27 29
28 30 [server:main]
29 31 ##nr of threads to spawn
30 32 threadpool_workers = 5
31 33
32 34 ##max request before thread respawn
33 35 threadpool_max_requests = 6
34 36
35 37 ##option to use threads of process
36 38 use_threadpool = true
37 39
38 40 use = egg:Paste#http
39 41 host = 0.0.0.0
40 42 port = 5000
41 43
42 44 [app:main]
43 45 use = egg:rhodecode
44 46 full_stack = true
45 47 static_files = true
46 48 lang=en
47 49 cache_dir = %(here)s/data
48 50 index_dir = %(here)s/data/index
49 51 app_instance_uuid = develop
50 52 cut_off_limit = 256000
51 53 force_https = false
52 54 commit_parse_limit = 25
53 55 use_gravatar = true
54 56
55 57 ####################################
56 58 ### CELERY CONFIG ####
57 59 ####################################
58 60 use_celery = false
59 61 broker.host = localhost
60 62 broker.vhost = rabbitmqhost
61 63 broker.port = 5672
62 64 broker.user = rabbitmq
63 65 broker.password = qweqwe
64 66
65 67 celery.imports = rhodecode.lib.celerylib.tasks
66 68
67 69 celery.result.backend = amqp
68 70 celery.result.dburi = amqp://
69 71 celery.result.serialier = json
70 72
71 73 #celery.send.task.error.emails = true
72 74 #celery.amqp.task.result.expires = 18000
73 75
74 76 celeryd.concurrency = 2
75 77 #celeryd.log.file = celeryd.log
76 78 celeryd.log.level = debug
77 79 celeryd.max.tasks.per.child = 1
78 80
79 81 #tasks will never be sent to the queue, but executed locally instead.
80 82 celery.always.eager = false
81 83
82 84 ####################################
83 85 ### BEAKER CACHE ####
84 86 ####################################
85 87 beaker.cache.data_dir=%(here)s/data/cache/data
86 88 beaker.cache.lock_dir=%(here)s/data/cache/lock
87 89
88 90 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
89 91
90 92 beaker.cache.super_short_term.type=memory
91 93 beaker.cache.super_short_term.expire=10
92 94
93 95 beaker.cache.short_term.type=memory
94 96 beaker.cache.short_term.expire=60
95 97
96 98 beaker.cache.long_term.type=memory
97 99 beaker.cache.long_term.expire=36000
98 100
99 101 beaker.cache.sql_cache_short.type=memory
100 102 beaker.cache.sql_cache_short.expire=10
101 103
102 104 beaker.cache.sql_cache_med.type=memory
103 105 beaker.cache.sql_cache_med.expire=360
104 106
105 107 beaker.cache.sql_cache_long.type=file
106 108 beaker.cache.sql_cache_long.expire=3600
107 109
108 110 ####################################
109 111 ### BEAKER SESSION ####
110 112 ####################################
111 113 ## Type of storage used for the session, current types are
112 114 ## dbm, file, memcached, database, and memory.
113 115 ## The storage uses the Container API
114 116 ##that is also used by the cache system.
115 117 beaker.session.type = file
116 118
117 119 beaker.session.key = rhodecode
118 120 beaker.session.secret = g654dcno0-9873jhgfreyu
119 121 beaker.session.timeout = 36000
120 122
121 123 ##auto save the session to not to use .save()
122 124 beaker.session.auto = False
123 125
124 126 ##true exire at browser close
125 127 #beaker.session.cookie_expires = 3600
126 128
127 129
128 130 ################################################################################
129 131 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
130 132 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
131 133 ## execute malicious code after an exception is raised. ##
132 134 ################################################################################
133 135 #set debug = false
134 136
135 137 ##################################
136 138 ### LOGVIEW CONFIG ###
137 139 ##################################
138 140 logview.sqlalchemy = #faa
139 141 logview.pylons.templating = #bfb
140 142 logview.pylons.util = #eee
141 143
142 144 #########################################################
143 145 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
144 146 #########################################################
145 147 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
146 148 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
147 149 sqlalchemy.db1.echo = false
148 150 sqlalchemy.db1.pool_recycle = 3600
149 151 sqlalchemy.convert_unicode = true
150 152
151 153 ################################
152 154 ### LOGGING CONFIGURATION ####
153 155 ################################
154 156 [loggers]
155 157 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
156 158
157 159 [handlers]
158 160 keys = console, console_sql
159 161
160 162 [formatters]
161 163 keys = generic, color_formatter, color_formatter_sql
162 164
163 165 #############
164 166 ## LOGGERS ##
165 167 #############
166 168 [logger_root]
167 169 level = NOTSET
168 170 handlers = console
169 171
170 172 [logger_routes]
171 173 level = DEBUG
172 174 handlers =
173 175 qualname = routes.middleware
174 176 # "level = DEBUG" logs the route matched and routing variables.
175 177 propagate = 1
176 178
177 179 [logger_beaker]
178 180 level = DEBUG
179 181 handlers =
180 182 qualname = beaker.container
181 183 propagate = 1
182 184
183 185 [logger_templates]
184 186 level = INFO
185 187 handlers =
186 188 qualname = pylons.templating
187 189 propagate = 1
188 190
189 191 [logger_rhodecode]
190 192 level = DEBUG
191 193 handlers =
192 194 qualname = rhodecode
193 195 propagate = 1
194 196
195 197 [logger_sqlalchemy]
196 198 level = INFO
197 199 handlers = console_sql
198 200 qualname = sqlalchemy.engine
199 201 propagate = 0
200 202
201 203 ##############
202 204 ## HANDLERS ##
203 205 ##############
204 206
205 207 [handler_console]
206 208 class = StreamHandler
207 209 args = (sys.stderr,)
208 210 level = DEBUG
209 211 formatter = color_formatter
210 212
211 213 [handler_console_sql]
212 214 class = StreamHandler
213 215 args = (sys.stderr,)
214 216 level = DEBUG
215 217 formatter = color_formatter_sql
216 218
217 219 ################
218 220 ## FORMATTERS ##
219 221 ################
220 222
221 223 [formatter_generic]
222 224 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
223 225 datefmt = %Y-%m-%d %H:%M:%S
224 226
225 227 [formatter_color_formatter]
226 228 class=rhodecode.lib.colored_formatter.ColorFormatter
227 229 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
228 230 datefmt = %Y-%m-%d %H:%M:%S
229 231
230 232 [formatter_color_formatter_sql]
231 233 class=rhodecode.lib.colored_formatter.ColorFormatterSql
232 234 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
233 235 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,111 +1,362 b''
1 1 .. _api:
2 2
3 3
4 4 API
5 5 ===
6 6
7 7
8 8 Starting from RhodeCode version 1.2 a simple API was implemented.
9 9 There's a single schema for calling all api methods. API is implemented
10 with JSON protocol both ways. An url to send API request in RhodeCode is
10 with JSON protocol both ways. An url to send API request in RhodeCode is
11 11 <your_server>/_admin/api
12 12
13 13
14 14 All clients need to send JSON data in such format::
15 15
16 16 {
17 17 "api_key":"<api_key>",
18 18 "method":"<method_name>",
19 19 "args":{"<arg_key>":"<arg_val>"}
20 20 }
21 21
22 22 Example call for autopulling remotes repos using curl::
23 23 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
24 24
25 Simply provide
25 Simply provide
26 26 - *api_key* for access and permission validation.
27 27 - *method* is name of method to call
28 28 - *args* is an key:value list of arguments to pass to method
29
29
30 30 .. note::
31
32 api_key can be found in your user account page
33
34
31
32 api_key can be found in your user account page
33
34
35 35 RhodeCode API will return always a JSON formatted answer::
36
36
37 37 {
38 "result": "<result>",
38 "result": "<result>",
39 39 "error": null
40 40 }
41 41
42 42 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
43 calling api *error* key from response will contain failure description
43 calling api *error* key from response will contain failure description
44 44 and result will be null.
45 45
46 46 API METHODS
47 47 +++++++++++
48 48
49
49
50 50 pull
51 51 ----
52 52
53 Pulls given repo from remote location. Can be used to automatically keep
54 remote repos up to date. This command can be executed only using api_key
55 belonging to user with admin rights
56
57 INPUT::
58
59 api_key:"<api_key>"
60 method: "pull"
61 args: {"repo":<repo_name>}
62
63 OUTPUT::
64
65 result:"Pulled from <repo_name>"
66 error:null
67
68
69 create_user
70 -----------
71
72 Creates new user in RhodeCode. This command can be executed only using api_key
53 Pulls given repo from remote location. Can be used to automatically keep
54 remote repos up to date. This command can be executed only using api_key
73 55 belonging to user with admin rights
74 56
75 57 INPUT::
76 58
77 api_key:"<api_key>"
78 method: "create_user"
79 args: {"username": "<username>",
80 "password": "<password>",
81 "active": "<bool>",
82 "admin": "<bool>",
83 "name": "<firstname>",
84 "lastname": "<lastname>",
85 "email": "<useremail>"}
59 api_key : "<api_key>"
60 method : "pull"
61 args : {
62 "repo" : "<repo_name>"
63 }
64
65 OUTPUT::
66
67 result : "Pulled from <repo_name>"
68 error : null
69
70
71 get_users
72 ---------
73
74 Lists all existing users. This command can be executed only using api_key
75 belonging to user with admin rights.
76
77 INPUT::
78
79 api_key : "<api_key>"
80 method : "get_users"
81 args : { }
82
83 OUTPUT::
84
85 result: [
86 {
87 "id" : "<id>",
88 "username" : "<username>",
89 "firstname": "<firstname>",
90 "lastname" : "<lastname>",
91 "email" : "<email>",
92 "active" : "<bool>",
93 "admin" :  "<bool>",
94 "ldap" : "<ldap_dn>"
95 },
96 …
97 ]
98 error: null
99
100 create_user
101 -----------
102
103 Creates new user in RhodeCode. This command can be executed only using api_key
104 belonging to user with admin rights.
105
106 INPUT::
107
108 api_key : "<api_key>"
109 method : "create_user"
110 args : {
111 "username" : "<username>",
112 "password" : "<password>",
113 "firstname" : "<firstname>",
114 "lastname" : "<lastname>",
115 "email" : "<useremail>"
116 "active" : "<bool> = True",
117 "admin" : "<bool> = False",
118 "ldap_dn" : "<ldap_dn> = None"
119 }
86 120
87 121 OUTPUT::
88 122
89 result:{"id": <newuserid>,
90 "msg":"created new user <username>"}
91 error:null
92
93
123 result: {
124 "msg" : "created new user <username>"
125 }
126 error: null
127
128 get_users_groups
129 ----------------
130
131 Lists all existing users groups. This command can be executed only using api_key
132 belonging to user with admin rights.
133
134 INPUT::
135
136 api_key : "<api_key>"
137 method : "get_users_groups"
138 args : { }
139
140 OUTPUT::
141
142 result : [
143 {
144 "id" : "<id>",
145 "name" : "<name>",
146 "active": "<bool>",
147 "members" : [
148 {
149 "id" : "<userid>",
150 "username" : "<username>",
151 "firstname": "<firstname>",
152 "lastname" : "<lastname>",
153 "email" : "<email>",
154 "active" : "<bool>",
155 "admin" :  "<bool>",
156 "ldap" : "<ldap_dn>"
157 },
158 …
159 ]
160 }
161 ]
162 error : null
163
164 get_users_group
165 ---------------
166
167 Gets an existing users group. This command can be executed only using api_key
168 belonging to user with admin rights.
169
170 INPUT::
171
172 api_key : "<api_key>"
173 method : "get_users_group"
174 args : {
175 "group_name" : "<name>"
176 }
177
178 OUTPUT::
179
180 result : None if group not exist
181 {
182 "id" : "<id>",
183 "name" : "<name>",
184 "active": "<bool>",
185 "members" : [
186 { "id" : "<userid>",
187 "username" : "<username>",
188 "firstname": "<firstname>",
189 "lastname" : "<lastname>",
190 "email" : "<email>",
191 "active" : "<bool>",
192 "admin" :  "<bool>",
193 "ldap" : "<ldap_dn>"
194 },
195 …
196 ]
197 }
198 error : null
199
94 200 create_users_group
95 201 ------------------
96 202
97 creates new users group. This command can be executed only using api_key
203 Creates new users group. This command can be executed only using api_key
98 204 belonging to user with admin rights
99 205
100 206 INPUT::
101 207
102 api_key:"<api_key>"
103 method: "create_user"
104 args: {"name": "<groupname>",
105 "active":"<bool>"}
208 api_key : "<api_key>"
209 method : "create_users_group"
210 args: {
211 "name": "<name>",
212 "active":"<bool> = True"
213 }
214
215 OUTPUT::
216
217 result: {
218 "id": "<newusersgroupid>",
219 "msg": "created new users group <name>"
220 }
221 error: null
222
223 add_user_to_users_groups
224 ------------------------
225
226 Adds a user to a users group. This command can be executed only using api_key
227 belonging to user with admin rights
228
229 INPUT::
230
231 api_key : "<api_key>"
232 method : "add_user_users_group"
233 args: {
234 "group_name" : "<groupname>",
235 "user_name" : "<username>"
236 }
237
238 OUTPUT::
239
240 result: {
241 "id": "<newusersgroupmemberid>",
242 "msg": "created new users group member"
243 }
244 error: null
245
246 get_repos
247 ---------
248
249 Lists all existing repositories. This command can be executed only using api_key
250 belonging to user with admin rights
251
252 INPUT::
253
254 api_key : "<api_key>"
255 method : "get_repos"
256 args: { }
106 257
107 258 OUTPUT::
108 259
109 result:{"id": <newusersgroupid>,
110 "msg":"created new users group <groupname>"}
111 error:null
260 result: [
261 {
262 "id" : "<id>",
263 "name" : "<name>"
264 "type" : "<type>",
265 "description" : "<description>"
266 },
267 …
268 ]
269 error: null
270
271 get_repo
272 --------
273
274 Gets an existing repository. This command can be executed only using api_key
275 belonging to user with admin rights
276
277 INPUT::
278
279 api_key : "<api_key>"
280 method : "get_repo"
281 args: {
282 "name" : "<name>"
283 }
284
285 OUTPUT::
286
287 result: None if repository not exist
288 {
289 "id" : "<id>",
290 "name" : "<name>"
291 "type" : "<type>",
292 "description" : "<description>",
293 "members" : [
294 { "id" : "<userid>",
295 "username" : "<username>",
296 "firstname": "<firstname>",
297 "lastname" : "<lastname>",
298 "email" : "<email>",
299 "active" : "<bool>",
300 "admin" :  "<bool>",
301 "ldap" : "<ldap_dn>",
302 "permission" : "repository_(read|write|admin)"
303 },
304 …
305 {
306 "id" : "<usersgroupid>",
307 "name" : "<usersgroupname>",
308 "active": "<bool>",
309 "permission" : "repository_(read|write|admin)"
310 },
311 …
312 ]
313 }
314 error: null
315
316 create_repo
317 -----------
318
319 Creates a repository. This command can be executed only using api_key
320 belonging to user with admin rights.
321 If repository name contains "/", all needed repository groups will be created.
322 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
323 and create "baz" repository with "bar" as group.
324
325 INPUT::
326
327 api_key : "<api_key>"
328 method : "create_repo"
329 args: {
330 "name" : "<name>",
331 "owner_name" : "<ownername>",
332 "description" : "<description> = ''",
333 "repo_type" : "<type> = 'hg'",
334 "private" : "<bool> = False"
335 }
336
337 OUTPUT::
338
339 result: None
340 error: null
341
342 add_user_to_repo
343 ----------------
344
345 Add a user to a repository. This command can be executed only using api_key
346 belonging to user with admin rights.
347 If "perm" is None, user will be removed from the repository.
348
349 INPUT::
350
351 api_key : "<api_key>"
352 method : "add_user_to_repo"
353 args: {
354 "repo_name" : "<reponame>",
355 "user_name" : "<username>",
356 "perm" : "(None|repository_(read|write|admin))",
357 }
358
359 OUTPUT::
360
361 result: None
362 error: null
@@ -1,233 +1,235 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 10 pdebug = false
11 11 ################################################################################
12 12 ## Uncomment and replace with the address which should receive ##
13 13 ## any error reports after application crash ##
14 14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 15 ################################################################################
16 16 #email_to = admin@localhost
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 20
21 21 #smtp_server = mail.server.com
22 22 #smtp_username =
23 23 #smtp_password =
24 24 #smtp_port =
25 25 #smtp_use_tls = false
26 26 #smtp_use_ssl = true
27 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 #smtp_auth =
27 29
28 30 [server:main]
29 31 ##nr of threads to spawn
30 32 threadpool_workers = 5
31 33
32 34 ##max request before thread respawn
33 35 threadpool_max_requests = 10
34 36
35 37 ##option to use threads of process
36 38 use_threadpool = true
37 39
38 40 use = egg:Paste#http
39 41 host = 127.0.0.1
40 42 port = 8001
41 43
42 44 [app:main]
43 45 use = egg:rhodecode
44 46 full_stack = true
45 47 static_files = true
46 48 lang=en
47 49 cache_dir = %(here)s/data
48 50 index_dir = %(here)s/data/index
49 51 app_instance_uuid = prod1234
50 52 cut_off_limit = 256000
51 53 force_https = false
52 54 commit_parse_limit = 50
53 55 use_gravatar = true
54 56
55 57 ####################################
56 58 ### CELERY CONFIG ####
57 59 ####################################
58 60 use_celery = false
59 61 broker.host = localhost
60 62 broker.vhost = rabbitmqhost
61 63 broker.port = 5672
62 64 broker.user = rabbitmq
63 65 broker.password = qweqwe
64 66
65 67 celery.imports = rhodecode.lib.celerylib.tasks
66 68
67 69 celery.result.backend = amqp
68 70 celery.result.dburi = amqp://
69 71 celery.result.serialier = json
70 72
71 73 #celery.send.task.error.emails = true
72 74 #celery.amqp.task.result.expires = 18000
73 75
74 76 celeryd.concurrency = 2
75 77 #celeryd.log.file = celeryd.log
76 78 celeryd.log.level = debug
77 79 celeryd.max.tasks.per.child = 1
78 80
79 81 #tasks will never be sent to the queue, but executed locally instead.
80 82 celery.always.eager = false
81 83
82 84 ####################################
83 85 ### BEAKER CACHE ####
84 86 ####################################
85 87 beaker.cache.data_dir=%(here)s/data/cache/data
86 88 beaker.cache.lock_dir=%(here)s/data/cache/lock
87 89
88 90 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
89 91
90 92 beaker.cache.super_short_term.type=memory
91 93 beaker.cache.super_short_term.expire=10
92 94
93 95 beaker.cache.short_term.type=memory
94 96 beaker.cache.short_term.expire=60
95 97
96 98 beaker.cache.long_term.type=memory
97 99 beaker.cache.long_term.expire=36000
98 100
99 101 beaker.cache.sql_cache_short.type=memory
100 102 beaker.cache.sql_cache_short.expire=10
101 103
102 104 beaker.cache.sql_cache_med.type=memory
103 105 beaker.cache.sql_cache_med.expire=360
104 106
105 107 beaker.cache.sql_cache_long.type=file
106 108 beaker.cache.sql_cache_long.expire=3600
107 109
108 110 ####################################
109 111 ### BEAKER SESSION ####
110 112 ####################################
111 113 ## Type of storage used for the session, current types are
112 114 ## dbm, file, memcached, database, and memory.
113 115 ## The storage uses the Container API
114 116 ##that is also used by the cache system.
115 117 beaker.session.type = file
116 118
117 119 beaker.session.key = rhodecode
118 120 beaker.session.secret = g654dcno0-9873jhgfreyu
119 121 beaker.session.timeout = 36000
120 122
121 123 ##auto save the session to not to use .save()
122 124 beaker.session.auto = False
123 125
124 126 ##true exire at browser close
125 127 #beaker.session.cookie_expires = 3600
126 128
127 129
128 130 ################################################################################
129 131 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
130 132 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
131 133 ## execute malicious code after an exception is raised. ##
132 134 ################################################################################
133 135 set debug = false
134 136
135 137 ##################################
136 138 ### LOGVIEW CONFIG ###
137 139 ##################################
138 140 logview.sqlalchemy = #faa
139 141 logview.pylons.templating = #bfb
140 142 logview.pylons.util = #eee
141 143
142 144 #########################################################
143 145 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
144 146 #########################################################
145 147 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
146 148 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
147 149 sqlalchemy.db1.echo = false
148 150 sqlalchemy.db1.pool_recycle = 3600
149 151 sqlalchemy.convert_unicode = true
150 152
151 153 ################################
152 154 ### LOGGING CONFIGURATION ####
153 155 ################################
154 156 [loggers]
155 157 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
156 158
157 159 [handlers]
158 160 keys = console, console_sql
159 161
160 162 [formatters]
161 163 keys = generic, color_formatter, color_formatter_sql
162 164
163 165 #############
164 166 ## LOGGERS ##
165 167 #############
166 168 [logger_root]
167 169 level = NOTSET
168 170 handlers = console
169 171
170 172 [logger_routes]
171 173 level = DEBUG
172 174 handlers =
173 175 qualname = routes.middleware
174 176 # "level = DEBUG" logs the route matched and routing variables.
175 177 propagate = 1
176 178
177 179 [logger_beaker]
178 180 level = DEBUG
179 181 handlers =
180 182 qualname = beaker.container
181 183 propagate = 1
182 184
183 185 [logger_templates]
184 186 level = INFO
185 187 handlers =
186 188 qualname = pylons.templating
187 189 propagate = 1
188 190
189 191 [logger_rhodecode]
190 192 level = DEBUG
191 193 handlers =
192 194 qualname = rhodecode
193 195 propagate = 1
194 196
195 197 [logger_sqlalchemy]
196 198 level = INFO
197 199 handlers = console_sql
198 200 qualname = sqlalchemy.engine
199 201 propagate = 0
200 202
201 203 ##############
202 204 ## HANDLERS ##
203 205 ##############
204 206
205 207 [handler_console]
206 208 class = StreamHandler
207 209 args = (sys.stderr,)
208 210 level = INFO
209 211 formatter = generic
210 212
211 213 [handler_console_sql]
212 214 class = StreamHandler
213 215 args = (sys.stderr,)
214 216 level = WARN
215 217 formatter = generic
216 218
217 219 ################
218 220 ## FORMATTERS ##
219 221 ################
220 222
221 223 [formatter_generic]
222 224 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
223 225 datefmt = %Y-%m-%d %H:%M:%S
224 226
225 227 [formatter_color_formatter]
226 228 class=rhodecode.lib.colored_formatter.ColorFormatter
227 229 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
228 230 datefmt = %Y-%m-%d %H:%M:%S
229 231
230 232 [formatter_color_formatter_sql]
231 233 class=rhodecode.lib.colored_formatter.ColorFormatterSql
232 234 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
233 235 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
@@ -1,242 +1,244 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 10 pdebug = false
11 11 ################################################################################
12 12 ## Uncomment and replace with the address which should receive ##
13 13 ## any error reports after application crash ##
14 14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 15 ################################################################################
16 16 #email_to = admin@localhost
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 20
21 21 #smtp_server = mail.server.com
22 22 #smtp_username =
23 23 #smtp_password =
24 24 #smtp_port =
25 25 #smtp_use_tls = false
26 26 #smtp_use_ssl = true
27 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 #smtp_auth =
27 29
28 30 [server:main]
29 31 ##nr of threads to spawn
30 32 threadpool_workers = 5
31 33
32 34 ##max request before thread respawn
33 35 threadpool_max_requests = 10
34 36
35 37 ##option to use threads of process
36 38 use_threadpool = true
37 39
38 40 use = egg:Paste#http
39 41 host = 127.0.0.1
40 42 port = 5000
41 43
42 44 [app:main]
43 45 use = egg:rhodecode
44 46 full_stack = true
45 47 static_files = true
46 48 lang=en
47 49 cache_dir = %(here)s/data
48 50 index_dir = %(here)s/data/index
49 51 app_instance_uuid = ${app_instance_uuid}
50 52 cut_off_limit = 256000
51 53 force_https = false
52 54 commit_parse_limit = 50
53 55 use_gravatar = true
54 56
55 57 ####################################
56 58 ### CELERY CONFIG ####
57 59 ####################################
58 60 use_celery = false
59 61 broker.host = localhost
60 62 broker.vhost = rabbitmqhost
61 63 broker.port = 5672
62 64 broker.user = rabbitmq
63 65 broker.password = qweqwe
64 66
65 67 celery.imports = rhodecode.lib.celerylib.tasks
66 68
67 69 celery.result.backend = amqp
68 70 celery.result.dburi = amqp://
69 71 celery.result.serialier = json
70 72
71 73 #celery.send.task.error.emails = true
72 74 #celery.amqp.task.result.expires = 18000
73 75
74 76 celeryd.concurrency = 2
75 77 #celeryd.log.file = celeryd.log
76 78 celeryd.log.level = debug
77 79 celeryd.max.tasks.per.child = 1
78 80
79 81 #tasks will never be sent to the queue, but executed locally instead.
80 82 celery.always.eager = false
81 83
82 84 ####################################
83 85 ### BEAKER CACHE ####
84 86 ####################################
85 87 beaker.cache.data_dir=%(here)s/data/cache/data
86 88 beaker.cache.lock_dir=%(here)s/data/cache/lock
87 89
88 90 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
89 91
90 92 beaker.cache.super_short_term.type=memory
91 93 beaker.cache.super_short_term.expire=10
92 94
93 95 beaker.cache.short_term.type=memory
94 96 beaker.cache.short_term.expire=60
95 97
96 98 beaker.cache.long_term.type=memory
97 99 beaker.cache.long_term.expire=36000
98 100
99 101 beaker.cache.sql_cache_short.type=memory
100 102 beaker.cache.sql_cache_short.expire=10
101 103
102 104 beaker.cache.sql_cache_med.type=memory
103 105 beaker.cache.sql_cache_med.expire=360
104 106
105 107 beaker.cache.sql_cache_long.type=file
106 108 beaker.cache.sql_cache_long.expire=3600
107 109
108 110 ####################################
109 111 ### BEAKER SESSION ####
110 112 ####################################
111 113 ## Type of storage used for the session, current types are
112 114 ## dbm, file, memcached, database, and memory.
113 115 ## The storage uses the Container API
114 116 ##that is also used by the cache system.
115 117 beaker.session.type = file
116 118
117 119 beaker.session.key = rhodecode
118 120 beaker.session.secret = ${app_instance_secret}
119 121 beaker.session.timeout = 36000
120 122
121 123 ##auto save the session to not to use .save()
122 124 beaker.session.auto = False
123 125
124 126 ##true exire at browser close
125 127 #beaker.session.cookie_expires = 3600
126 128
127 129
128 130 ################################################################################
129 131 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
130 132 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
131 133 ## execute malicious code after an exception is raised. ##
132 134 ################################################################################
133 135 set debug = false
134 136
135 137 ##################################
136 138 ### LOGVIEW CONFIG ###
137 139 ##################################
138 140 logview.sqlalchemy = #faa
139 141 logview.pylons.templating = #bfb
140 142 logview.pylons.util = #eee
141 143
142 144 #########################################################
143 145 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
144 146 #########################################################
145 147
146 148 # SQLITE [default]
147 149 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
148 150
149 # POSTGRES
151 # POSTGRESQL
150 152 # sqlalchemy.db1.url = postgresql://user:pass@localhost/rhodecode
151 153
152 154 # MySQL
153 155 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
154 156
155 157
156 158 sqlalchemy.db1.echo = false
157 159 sqlalchemy.db1.pool_recycle = 3600
158 160 sqlalchemy.convert_unicode = true
159 161
160 162 ################################
161 163 ### LOGGING CONFIGURATION ####
162 164 ################################
163 165 [loggers]
164 166 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
165 167
166 168 [handlers]
167 169 keys = console, console_sql
168 170
169 171 [formatters]
170 172 keys = generic, color_formatter, color_formatter_sql
171 173
172 174 #############
173 175 ## LOGGERS ##
174 176 #############
175 177 [logger_root]
176 178 level = NOTSET
177 179 handlers = console
178 180
179 181 [logger_routes]
180 182 level = DEBUG
181 183 handlers =
182 184 qualname = routes.middleware
183 185 # "level = DEBUG" logs the route matched and routing variables.
184 186 propagate = 1
185 187
186 188 [logger_beaker]
187 189 level = DEBUG
188 190 handlers =
189 191 qualname = beaker.container
190 192 propagate = 1
191 193
192 194 [logger_templates]
193 195 level = INFO
194 196 handlers =
195 197 qualname = pylons.templating
196 198 propagate = 1
197 199
198 200 [logger_rhodecode]
199 201 level = DEBUG
200 202 handlers =
201 203 qualname = rhodecode
202 204 propagate = 1
203 205
204 206 [logger_sqlalchemy]
205 207 level = INFO
206 208 handlers = console_sql
207 209 qualname = sqlalchemy.engine
208 210 propagate = 0
209 211
210 212 ##############
211 213 ## HANDLERS ##
212 214 ##############
213 215
214 216 [handler_console]
215 217 class = StreamHandler
216 218 args = (sys.stderr,)
217 219 level = INFO
218 220 formatter = color_formatter
219 221
220 222 [handler_console_sql]
221 223 class = StreamHandler
222 224 args = (sys.stderr,)
223 225 level = WARN
224 226 formatter = color_formatter_sql
225 227
226 228 ################
227 229 ## FORMATTERS ##
228 230 ################
229 231
230 232 [formatter_generic]
231 233 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
232 234 datefmt = %Y-%m-%d %H:%M:%S
233 235
234 236 [formatter_color_formatter]
235 237 class=rhodecode.lib.colored_formatter.ColorFormatter
236 238 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
237 239 datefmt = %Y-%m-%d %H:%M:%S
238 240
239 241 [formatter_color_formatter_sql]
240 242 class=rhodecode.lib.colored_formatter.ColorFormatterSql
241 243 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
242 244 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
@@ -1,421 +1,397 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Admin controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 from operator import itemgetter
30 29 from formencode import htmlfill
31 30
32 31 from paste.httpexceptions import HTTPInternalServerError
33 32 from pylons import request, response, session, tmpl_context as c, url
34 33 from pylons.controllers.util import abort, redirect
35 34 from pylons.i18n.translation import _
36 35
37 36 from rhodecode.lib import helpers as h
38 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 38 HasPermissionAnyDecorator
40 39 from rhodecode.lib.base import BaseController, render
41 40 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 41 from rhodecode.lib.helpers import get_token
43 42 from rhodecode.model.db import User, Repository, UserFollowing, Group
44 43 from rhodecode.model.forms import RepoForm
45 44 from rhodecode.model.scm import ScmModel
46 45 from rhodecode.model.repo import RepoModel
47 46 from sqlalchemy.exc import IntegrityError
48 47
49 48 log = logging.getLogger(__name__)
50 49
51 50
52 51 class ReposController(BaseController):
53 52 """
54 53 REST Controller styled on the Atom Publishing Protocol"""
55 54 # To properly map this controller, ensure your config/routing.py
56 55 # file has a resource setup:
57 56 # map.resource('repo', 'repos')
58 57
59 58 @LoginRequired()
60 59 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 60 def __before__(self):
62 61 c.admin_user = session.get('admin_user')
63 62 c.admin_username = session.get('admin_username')
64 63 super(ReposController, self).__before__()
65 64
66 65 def __load_defaults(self):
67 66 c.repo_groups = Group.groups_choices()
68 67 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
69 68
70 69 repo_model = RepoModel()
71 70 c.users_array = repo_model.get_users_js()
72 71 c.users_groups_array = repo_model.get_users_groups_js()
73 72
74 73 def __load_data(self, repo_name=None):
75 74 """
76 75 Load defaults settings for edit, and update
77 76
78 77 :param repo_name:
79 78 """
80 79 self.__load_defaults()
81 80
82 81 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
83 82 repo = db_repo.scm_instance
84 83
85 84 if c.repo_info is None:
86 85 h.flash(_('%s repository is not mapped to db perhaps'
87 86 ' it was created or renamed from the filesystem'
88 87 ' please run the application again'
89 88 ' in order to rescan repositories') % repo_name,
90 89 category='error')
91 90
92 91 return redirect(url('repos'))
93 92
94 93 c.default_user_id = User.get_by_username('default').user_id
95 c.in_public_journal = self.sa.query(UserFollowing)\
94 c.in_public_journal = UserFollowing.query()\
96 95 .filter(UserFollowing.user_id == c.default_user_id)\
97 96 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
98 97
99 98 if c.repo_info.stats:
100 99 last_rev = c.repo_info.stats.stat_on_revision
101 100 else:
102 101 last_rev = 0
103 102 c.stats_revision = last_rev
104 103
105 104 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
106 105
107 106 if last_rev == 0 or c.repo_last_rev == 0:
108 107 c.stats_percentage = 0
109 108 else:
110 109 c.stats_percentage = '%.2f' % ((float((last_rev)) /
111 110 c.repo_last_rev) * 100)
112 111
113 defaults = c.repo_info.get_dict()
114 group, repo_name = c.repo_info.groups_and_repo
115 defaults['repo_name'] = repo_name
116 defaults['repo_group'] = getattr(group[-1] if group else None,
117 'group_id', None)
118
119 #fill owner
120 if c.repo_info.user:
121 defaults.update({'user': c.repo_info.user.username})
122 else:
123 replacement_user = self.sa.query(User)\
124 .filter(User.admin == True).first().username
125 defaults.update({'user': replacement_user})
126
127 #fill repository users
128 for p in c.repo_info.repo_to_perm:
129 defaults.update({'u_perm_%s' % p.user.username:
130 p.permission.permission_name})
131
132 #fill repository groups
133 for p in c.repo_info.users_group_to_perm:
134 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
135 p.permission.permission_name})
136
112 defaults = RepoModel()._get_defaults(repo_name)
137 113 return defaults
138 114
139 115 @HasPermissionAllDecorator('hg.admin')
140 116 def index(self, format='html'):
141 117 """GET /repos: All items in the collection"""
142 118 # url('repos')
143 119
144 120 c.repos_list = ScmModel().get_repos(Repository.query()
145 121 .order_by(Repository.repo_name)
146 122 .all(), sort_key='name_sort')
147 123 return render('admin/repos/repos.html')
148 124
149 125 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
150 126 def create(self):
151 127 """
152 128 POST /repos: Create a new item"""
153 129 # url('repos')
154 130 repo_model = RepoModel()
155 131 self.__load_defaults()
156 132 form_result = {}
157 133 try:
158 134 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
159 135 .to_python(dict(request.POST))
160 136 repo_model.create(form_result, self.rhodecode_user)
161 137 if form_result['clone_uri']:
162 138 h.flash(_('created repository %s from %s') \
163 139 % (form_result['repo_name'], form_result['clone_uri']),
164 140 category='success')
165 141 else:
166 142 h.flash(_('created repository %s') % form_result['repo_name'],
167 143 category='success')
168 144
169 145 if request.POST.get('user_created'):
170 146 #created by regular non admin user
171 147 action_logger(self.rhodecode_user, 'user_created_repo',
172 148 form_result['repo_name_full'], '', self.sa)
173 149 else:
174 150 action_logger(self.rhodecode_user, 'admin_created_repo',
175 151 form_result['repo_name_full'], '', self.sa)
176 152
177 153 except formencode.Invalid, errors:
178 154
179 155 c.new_repo = errors.value['repo_name']
180 156
181 157 if request.POST.get('user_created'):
182 158 r = render('admin/repos/repo_add_create_repository.html')
183 159 else:
184 160 r = render('admin/repos/repo_add.html')
185 161
186 162 return htmlfill.render(
187 163 r,
188 164 defaults=errors.value,
189 165 errors=errors.error_dict or {},
190 166 prefix_error=False,
191 167 encoding="UTF-8")
192 168
193 169 except Exception:
194 170 log.error(traceback.format_exc())
195 171 msg = _('error occurred during creation of repository %s') \
196 172 % form_result.get('repo_name')
197 173 h.flash(msg, category='error')
198 174 if request.POST.get('user_created'):
199 175 return redirect(url('home'))
200 176 return redirect(url('repos'))
201 177
202 178 @HasPermissionAllDecorator('hg.admin')
203 179 def new(self, format='html'):
204 180 """GET /repos/new: Form to create a new item"""
205 181 new_repo = request.GET.get('repo', '')
206 182 c.new_repo = repo_name_slug(new_repo)
207 183 self.__load_defaults()
208 184 return render('admin/repos/repo_add.html')
209 185
210 186 @HasPermissionAllDecorator('hg.admin')
211 187 def update(self, repo_name):
212 188 """
213 189 PUT /repos/repo_name: Update an existing item"""
214 190 # Forms posted to this method should contain a hidden field:
215 191 # <input type="hidden" name="_method" value="PUT" />
216 192 # Or using helpers:
217 193 # h.form(url('repo', repo_name=ID),
218 194 # method='put')
219 195 # url('repo', repo_name=ID)
220 196 self.__load_defaults()
221 197 repo_model = RepoModel()
222 198 changed_name = repo_name
223 199 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
224 200 repo_groups=c.repo_groups_choices)()
225 201 try:
226 202 form_result = _form.to_python(dict(request.POST))
227 203 repo = repo_model.update(repo_name, form_result)
228 204 invalidate_cache('get_repo_cached_%s' % repo_name)
229 205 h.flash(_('Repository %s updated successfully' % repo_name),
230 206 category='success')
231 207 changed_name = repo.repo_name
232 208 action_logger(self.rhodecode_user, 'admin_updated_repo',
233 209 changed_name, '', self.sa)
234 210
235 211 except formencode.Invalid, errors:
236 212 defaults = self.__load_data(repo_name)
237 213 defaults.update(errors.value)
238 214 return htmlfill.render(
239 215 render('admin/repos/repo_edit.html'),
240 216 defaults=defaults,
241 217 errors=errors.error_dict or {},
242 218 prefix_error=False,
243 219 encoding="UTF-8")
244 220
245 221 except Exception:
246 222 log.error(traceback.format_exc())
247 223 h.flash(_('error occurred during update of repository %s') \
248 224 % repo_name, category='error')
249 225 return redirect(url('edit_repo', repo_name=changed_name))
250 226
251 227 @HasPermissionAllDecorator('hg.admin')
252 228 def delete(self, repo_name):
253 229 """
254 230 DELETE /repos/repo_name: Delete an existing item"""
255 231 # Forms posted to this method should contain a hidden field:
256 232 # <input type="hidden" name="_method" value="DELETE" />
257 233 # Or using helpers:
258 234 # h.form(url('repo', repo_name=ID),
259 235 # method='delete')
260 236 # url('repo', repo_name=ID)
261 237
262 238 repo_model = RepoModel()
263 239 repo = repo_model.get_by_repo_name(repo_name)
264 240 if not repo:
265 241 h.flash(_('%s repository is not mapped to db perhaps'
266 242 ' it was moved or renamed from the filesystem'
267 243 ' please run the application again'
268 244 ' in order to rescan repositories') % repo_name,
269 245 category='error')
270 246
271 247 return redirect(url('repos'))
272 248 try:
273 249 action_logger(self.rhodecode_user, 'admin_deleted_repo',
274 250 repo_name, '', self.sa)
275 251 repo_model.delete(repo)
276 252 invalidate_cache('get_repo_cached_%s' % repo_name)
277 253 h.flash(_('deleted repository %s') % repo_name, category='success')
278 254
279 255 except IntegrityError, e:
280 256 if e.message.find('repositories_fork_id_fkey'):
281 257 log.error(traceback.format_exc())
282 258 h.flash(_('Cannot delete %s it still contains attached '
283 259 'forks') % repo_name,
284 260 category='warning')
285 261 else:
286 262 log.error(traceback.format_exc())
287 263 h.flash(_('An error occurred during '
288 264 'deletion of %s') % repo_name,
289 265 category='error')
290 266
291 267 except Exception, e:
292 268 log.error(traceback.format_exc())
293 269 h.flash(_('An error occurred during deletion of %s') % repo_name,
294 270 category='error')
295 271
296 272 return redirect(url('repos'))
297 273
298 274 @HasPermissionAllDecorator('hg.admin')
299 275 def delete_perm_user(self, repo_name):
300 276 """
301 277 DELETE an existing repository permission user
302 278
303 279 :param repo_name:
304 280 """
305 281
306 282 try:
307 283 repo_model = RepoModel()
308 284 repo_model.delete_perm_user(request.POST, repo_name)
309 285 except Exception, e:
310 286 h.flash(_('An error occurred during deletion of repository user'),
311 287 category='error')
312 288 raise HTTPInternalServerError()
313 289
314 290 @HasPermissionAllDecorator('hg.admin')
315 291 def delete_perm_users_group(self, repo_name):
316 292 """
317 293 DELETE an existing repository permission users group
318 294
319 295 :param repo_name:
320 296 """
321 297 try:
322 298 repo_model = RepoModel()
323 299 repo_model.delete_perm_users_group(request.POST, repo_name)
324 300 except Exception, e:
325 301 h.flash(_('An error occurred during deletion of repository'
326 302 ' users groups'),
327 303 category='error')
328 304 raise HTTPInternalServerError()
329 305
330 306 @HasPermissionAllDecorator('hg.admin')
331 307 def repo_stats(self, repo_name):
332 308 """
333 309 DELETE an existing repository statistics
334 310
335 311 :param repo_name:
336 312 """
337 313
338 314 try:
339 315 repo_model = RepoModel()
340 316 repo_model.delete_stats(repo_name)
341 317 except Exception, e:
342 318 h.flash(_('An error occurred during deletion of repository stats'),
343 319 category='error')
344 320 return redirect(url('edit_repo', repo_name=repo_name))
345 321
346 322 @HasPermissionAllDecorator('hg.admin')
347 323 def repo_cache(self, repo_name):
348 324 """
349 325 INVALIDATE existing repository cache
350 326
351 327 :param repo_name:
352 328 """
353 329
354 330 try:
355 331 ScmModel().mark_for_invalidation(repo_name)
356 332 except Exception, e:
357 333 h.flash(_('An error occurred during cache invalidation'),
358 334 category='error')
359 335 return redirect(url('edit_repo', repo_name=repo_name))
360 336
361 337 @HasPermissionAllDecorator('hg.admin')
362 338 def repo_public_journal(self, repo_name):
363 339 """
364 340 Set's this repository to be visible in public journal,
365 341 in other words assing default user to follow this repo
366 342
367 343 :param repo_name:
368 344 """
369 345
370 346 cur_token = request.POST.get('auth_token')
371 347 token = get_token()
372 348 if cur_token == token:
373 349 try:
374 350 repo_id = Repository.get_by_repo_name(repo_name).repo_id
375 351 user_id = User.get_by_username('default').user_id
376 352 self.scm_model.toggle_following_repo(repo_id, user_id)
377 353 h.flash(_('Updated repository visibility in public journal'),
378 354 category='success')
379 355 except:
380 356 h.flash(_('An error occurred during setting this'
381 357 ' repository in public journal'),
382 358 category='error')
383 359
384 360 else:
385 361 h.flash(_('Token mismatch'), category='error')
386 362 return redirect(url('edit_repo', repo_name=repo_name))
387 363
388 364 @HasPermissionAllDecorator('hg.admin')
389 365 def repo_pull(self, repo_name):
390 366 """
391 367 Runs task to update given repository with remote changes,
392 368 ie. make pull on remote location
393 369
394 370 :param repo_name:
395 371 """
396 372 try:
397 373 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
398 374 h.flash(_('Pulled from remote location'), category='success')
399 375 except Exception, e:
400 376 h.flash(_('An error occurred during pull from remote location'),
401 377 category='error')
402 378
403 379 return redirect(url('edit_repo', repo_name=repo_name))
404 380
405 381 @HasPermissionAllDecorator('hg.admin')
406 382 def show(self, repo_name, format='html'):
407 383 """GET /repos/repo_name: Show a specific item"""
408 384 # url('repo', repo_name=ID)
409 385
410 386 @HasPermissionAllDecorator('hg.admin')
411 387 def edit(self, repo_name, format='html'):
412 388 """GET /repos/repo_name/edit: Form to edit an existing item"""
413 389 # url('edit_repo', repo_name=ID)
414 390 defaults = self.__load_data(repo_name)
415 391
416 392 return htmlfill.render(
417 393 render('admin/repos/repo_edit.html'),
418 394 defaults=defaults,
419 395 encoding="UTF-8",
420 396 force_defaults=False
421 397 )
@@ -1,98 +1,374 b''
1 1 import traceback
2 2 import logging
3 3
4 4 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
5 from rhodecode.lib.auth import HasPermissionAllDecorator
5 from rhodecode.lib.auth import HasPermissionAllDecorator, \
6 HasPermissionAnyDecorator
6 7 from rhodecode.model.scm import ScmModel
7 8
8 from rhodecode.model.db import User, UsersGroup, Repository
9 from rhodecode.model.db import User, UsersGroup, Group, Repository
10 from rhodecode.model.repo import RepoModel
11 from rhodecode.model.user import UserModel
12 from rhodecode.model.repo_permission import RepositoryPermissionModel
13 from rhodecode.model.users_group import UsersGroupModel
14 from rhodecode.model import users_group
15 from rhodecode.model.repos_group import ReposGroupModel
16 from sqlalchemy.orm.exc import NoResultFound
9 17
10 18 log = logging.getLogger(__name__)
11 19
12 20
13 21 class ApiController(JSONRPCController):
14 22 """
15 23 API Controller
16
17
24
25
18 26 Each method needs to have USER as argument this is then based on given
19 27 API_KEY propagated as instance of user object
20
28
21 29 Preferably this should be first argument also
22
23
24 Each function should also **raise** JSONRPCError for any
30
31
32 Each function should also **raise** JSONRPCError for any
25 33 errors that happens
26
34
27 35 """
28 36
29 37 @HasPermissionAllDecorator('hg.admin')
30 38 def pull(self, apiuser, repo):
31 39 """
32 40 Dispatch pull action on given repo
33
34
41
42
35 43 :param user:
36 44 :param repo:
37 45 """
38 46
39 47 if Repository.is_valid(repo) is False:
40 48 raise JSONRPCError('Unknown repo "%s"' % repo)
41
49
42 50 try:
43 51 ScmModel().pull_changes(repo, self.rhodecode_user.username)
44 52 return 'Pulled from %s' % repo
45 53 except Exception:
46 54 raise JSONRPCError('Unable to pull changes from "%s"' % repo)
47 55
56 @HasPermissionAllDecorator('hg.admin')
57 def get_user(self, apiuser, username):
58 """"
59 Get a user by username
60
61 :param apiuser
62 :param username
63 """
64
65 user = User.get_by_username(username)
66 if not user:
67 return None
68
69 return dict(id=user.user_id,
70 username=user.username,
71 firstname=user.name,
72 lastname=user.lastname,
73 email=user.email,
74 active=user.active,
75 admin=user.admin,
76 ldap=user.ldap_dn)
48 77
49 78 @HasPermissionAllDecorator('hg.admin')
50 def create_user(self, apiuser, username, password, active, admin, name,
51 lastname, email):
79 def get_users(self, apiuser):
80 """"
81 Get all users
82
83 :param apiuser
52 84 """
53 Creates new user
54
85
86 result = []
87 for user in User.getAll():
88 result.append(dict(id=user.user_id,
89 username=user.username,
90 firstname=user.name,
91 lastname=user.lastname,
92 email=user.email,
93 active=user.active,
94 admin=user.admin,
95 ldap=user.ldap_dn))
96 return result
97
98 @HasPermissionAllDecorator('hg.admin')
99 def create_user(self, apiuser, username, password, firstname,
100 lastname, email, active=True, admin=False, ldap_dn=None):
101 """
102 Create new user
103
55 104 :param apiuser:
56 105 :param username:
57 106 :param password:
58 :param active:
59 :param admin:
60 107 :param name:
61 108 :param lastname:
62 109 :param email:
110 :param active:
111 :param admin:
112 :param ldap_dn:
63 113 """
64
65 form_data = dict(username=username,
66 password=password,
67 active=active,
68 admin=admin,
69 name=name,
70 lastname=lastname,
71 email=email)
114
115 if self.get_user(apiuser, username):
116 raise JSONRPCError("user %s already exist" % username)
117
72 118 try:
73 u = User.create(form_data)
74 return {'id':u.user_id,
75 'msg':'created new user %s' % name}
119 form_data = dict(username=username,
120 password=password,
121 active=active,
122 admin=admin,
123 name=firstname,
124 lastname=lastname,
125 email=email,
126 ldap_dn=ldap_dn)
127 UserModel().create_ldap(username, password, ldap_dn, form_data)
128 return dict(msg='created new user %s' % username)
76 129 except Exception:
77 130 log.error(traceback.format_exc())
78 raise JSONRPCError('failed to create user %s' % name)
131 raise JSONRPCError('failed to create user %s' % username)
132
133 @HasPermissionAllDecorator('hg.admin')
134 def get_users_group(self, apiuser, group_name):
135 """"
136 Get users group by name
137
138 :param apiuser
139 :param group_name
140 """
141
142 users_group = UsersGroup.get_by_group_name(group_name)
143 if not users_group:
144 return None
79 145
146 members = []
147 for user in users_group.members:
148 user = user.user
149 members.append(dict(id=user.user_id,
150 username=user.username,
151 firstname=user.name,
152 lastname=user.lastname,
153 email=user.email,
154 active=user.active,
155 admin=user.admin,
156 ldap=user.ldap_dn))
157
158 return dict(id=users_group.users_group_id,
159 name=users_group.users_group_name,
160 active=users_group.users_group_active,
161 members=members)
80 162
81 163 @HasPermissionAllDecorator('hg.admin')
82 def create_users_group(self, apiuser, name, active):
164 def get_users_groups(self, apiuser):
165 """"
166 Get all users groups
167
168 :param apiuser
169 """
170
171 result = []
172 for users_group in UsersGroup.getAll():
173 members = []
174 for user in users_group.members:
175 user = user.user
176 members.append(dict(id=user.user_id,
177 username=user.username,
178 firstname=user.name,
179 lastname=user.lastname,
180 email=user.email,
181 active=user.active,
182 admin=user.admin,
183 ldap=user.ldap_dn))
184
185 result.append(dict(id=users_group.users_group_id,
186 name=users_group.users_group_name,
187 active=users_group.users_group_active,
188 members=members))
189 return result
190
191 @HasPermissionAllDecorator('hg.admin')
192 def create_users_group(self, apiuser, name, active=True):
83 193 """
84 194 Creates an new usergroup
85
195
86 196 :param name:
87 197 :param active:
88 198 """
89 form_data = {'users_group_name':name,
90 'users_group_active':active}
199
200 if self.get_users_group(apiuser, name):
201 raise JSONRPCError("users group %s already exist" % name)
202
91 203 try:
204 form_data = dict(users_group_name=name,
205 users_group_active=active)
92 206 ug = UsersGroup.create(form_data)
93 return {'id':ug.users_group_id,
94 'msg':'created new users group %s' % name}
207 return dict(id=ug.users_group_id,
208 msg='created new users group %s' % name)
95 209 except Exception:
96 210 log.error(traceback.format_exc())
97 211 raise JSONRPCError('failed to create group %s' % name)
98 No newline at end of file
212
213 @HasPermissionAllDecorator('hg.admin')
214 def add_user_to_users_group(self, apiuser, group_name, user_name):
215 """"
216 Add a user to a group
217
218 :param apiuser
219 :param group_name
220 :param user_name
221 """
222
223 try:
224 users_group = UsersGroup.get_by_group_name(group_name)
225 if not users_group:
226 raise JSONRPCError('unknown users group %s' % group_name)
227
228 try:
229 user = User.get_by_username(user_name)
230 except NoResultFound:
231 raise JSONRPCError('unknown user %s' % user_name)
232
233 ugm = UsersGroupModel().add_user_to_group(users_group, user)
234
235 return dict(id=ugm.users_group_member_id,
236 msg='created new users group member')
237 except Exception:
238 log.error(traceback.format_exc())
239 raise JSONRPCError('failed to create users group member')
240
241 @HasPermissionAnyDecorator('hg.admin')
242 def get_repo(self, apiuser, repo_name):
243 """"
244 Get repository by name
245
246 :param apiuser
247 :param repo_name
248 """
249
250 try:
251 repo = Repository.get_by_repo_name(repo_name)
252 except NoResultFound:
253 return None
254
255 members = []
256 for user in repo.repo_to_perm:
257 perm = user.permission.permission_name
258 user = user.user
259 members.append(dict(type_="user",
260 id=user.user_id,
261 username=user.username,
262 firstname=user.name,
263 lastname=user.lastname,
264 email=user.email,
265 active=user.active,
266 admin=user.admin,
267 ldap=user.ldap_dn,
268 permission=perm))
269 for users_group in repo.users_group_to_perm:
270 perm = users_group.permission.permission_name
271 users_group = users_group.users_group
272 members.append(dict(type_="users_group",
273 id=users_group.users_group_id,
274 name=users_group.users_group_name,
275 active=users_group.users_group_active,
276 permission=perm))
277
278 return dict(id=repo.repo_id,
279 name=repo.repo_name,
280 type=repo.repo_type,
281 description=repo.description,
282 members=members)
283
284 @HasPermissionAnyDecorator('hg.admin')
285 def get_repos(self, apiuser):
286 """"
287 Get all repositories
288
289 :param apiuser
290 """
291
292 result = []
293 for repository in Repository.getAll():
294 result.append(dict(id=repository.repo_id,
295 name=repository.repo_name,
296 type=repository.repo_type,
297 description=repository.description))
298 return result
299
300 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
301 def create_repo(self, apiuser, name, owner_name, description='',
302 repo_type='hg', private=False):
303 """
304 Create a repository
305
306 :param apiuser
307 :param name
308 :param description
309 :param type
310 :param private
311 :param owner_name
312 """
313
314 try:
315 try:
316 owner = User.get_by_username(owner_name)
317 except NoResultFound:
318 raise JSONRPCError('unknown user %s' % owner)
319
320 if self.get_repo(apiuser, name):
321 raise JSONRPCError("repo %s already exist" % name)
322
323 groups = name.split('/')
324 real_name = groups[-1]
325 groups = groups[:-1]
326 parent_id = None
327 for g in groups:
328 group = Group.get_by_group_name(g)
329 if not group:
330 group = ReposGroupModel().create(dict(group_name=g,
331 group_description='',
332 group_parent_id=parent_id))
333 parent_id = group.group_id
334
335 RepoModel().create(dict(repo_name=real_name,
336 repo_name_full=name,
337 description=description,
338 private=private,
339 repo_type=repo_type,
340 repo_group=parent_id,
341 clone_uri=None), owner)
342 except Exception:
343 log.error(traceback.format_exc())
344 raise JSONRPCError('failed to create repository %s' % name)
345
346 @HasPermissionAnyDecorator('hg.admin')
347 def add_user_to_repo(self, apiuser, repo_name, user_name, perm):
348 """
349 Add permission for a user to a repository
350
351 :param apiuser
352 :param repo_name
353 :param user_name
354 :param perm
355 """
356
357 try:
358 try:
359 repo = Repository.get_by_repo_name(repo_name)
360 except NoResultFound:
361 raise JSONRPCError('unknown repository %s' % repo)
362
363 try:
364 user = User.get_by_username(user_name)
365 except NoResultFound:
366 raise JSONRPCError('unknown user %s' % user)
367
368 RepositoryPermissionModel()\
369 .update_or_delete_user_permission(repo, user, perm)
370 except Exception:
371 log.error(traceback.format_exc())
372 raise JSONRPCError('failed to edit permission %(repo)s for %(user)s'
373 % dict(user=user_name, repo=repo_name))
374
@@ -1,214 +1,208 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Settings controller for rhodecode
7 7
8 8 :created_on: Jun 30, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31
32 32 from pylons import tmpl_context as c, request, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 import rhodecode.lib.helpers as h
37 37
38 38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator, \
39 39 HasRepoPermissionAnyDecorator, NotAnonymous
40 40 from rhodecode.lib.base import BaseRepoController, render
41 41 from rhodecode.lib.utils import invalidate_cache, action_logger
42 42
43 43 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
44 44 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.db import User
45 from rhodecode.model.db import Group
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class SettingsController(BaseRepoController):
51 51
52 52 @LoginRequired()
53 53 def __before__(self):
54 54 super(SettingsController, self).__before__()
55
55
56 def __load_defaults(self):
57 c.repo_groups = Group.groups_choices()
58 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
59
60 repo_model = RepoModel()
61 c.users_array = repo_model.get_users_js()
62 c.users_groups_array = repo_model.get_users_groups_js()
63
56 64 @HasRepoPermissionAllDecorator('repository.admin')
57 65 def index(self, repo_name):
58 66 repo_model = RepoModel()
59 67 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
60 68 if not repo:
61 69 h.flash(_('%s repository is not mapped to db perhaps'
62 70 ' it was created or renamed from the file system'
63 71 ' please run the application again'
64 72 ' in order to rescan repositories') % repo_name,
65 73 category='error')
66 74
67 75 return redirect(url('home'))
68 76
69 c.users_array = repo_model.get_users_js()
70 c.users_groups_array = repo_model.get_users_groups_js()
71
72 defaults = c.repo_info.get_dict()
77 self.__load_defaults()
73 78
74 #fill owner
75 if c.repo_info.user:
76 defaults.update({'user': c.repo_info.user.username})
77 else:
78 replacement_user = self.sa.query(User)\
79 .filter(User.admin == True).first().username
80 defaults.update({'user': replacement_user})
81
82 #fill repository users
83 for p in c.repo_info.repo_to_perm:
84 defaults.update({'u_perm_%s' % p.user.username:
85 p.permission.permission_name})
86
87 #fill repository groups
88 for p in c.repo_info.users_group_to_perm:
89 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
90 p.permission.permission_name})
79 defaults = RepoModel()._get_defaults(repo_name)
91 80
92 81 return htmlfill.render(
93 82 render('settings/repo_settings.html'),
94 83 defaults=defaults,
95 84 encoding="UTF-8",
96 85 force_defaults=False
97 86 )
98 87
99 88 @HasRepoPermissionAllDecorator('repository.admin')
100 89 def update(self, repo_name):
101 90 repo_model = RepoModel()
102 91 changed_name = repo_name
92
93 self.__load_defaults()
94
103 95 _form = RepoSettingsForm(edit=True,
104 old_data={'repo_name': repo_name})()
96 old_data={'repo_name': repo_name},
97 repo_groups=c.repo_groups_choices)()
105 98 try:
106 99 form_result = _form.to_python(dict(request.POST))
100
107 101 repo_model.update(repo_name, form_result)
108 102 invalidate_cache('get_repo_cached_%s' % repo_name)
109 103 h.flash(_('Repository %s updated successfully' % repo_name),
110 104 category='success')
111 changed_name = form_result['repo_name']
105 changed_name = form_result['repo_name_full']
112 106 action_logger(self.rhodecode_user, 'user_updated_repo',
113 changed_name, '', self.sa)
107 changed_name, '', self.sa)
114 108 except formencode.Invalid, errors:
115 109 c.repo_info = repo_model.get_by_repo_name(repo_name)
116 110 c.users_array = repo_model.get_users_js()
117 111 errors.value.update({'user': c.repo_info.user.username})
118 112 return htmlfill.render(
119 113 render('settings/repo_settings.html'),
120 114 defaults=errors.value,
121 115 errors=errors.error_dict or {},
122 116 prefix_error=False,
123 117 encoding="UTF-8")
124 118 except Exception:
125 119 log.error(traceback.format_exc())
126 120 h.flash(_('error occurred during update of repository %s') \
127 121 % repo_name, category='error')
128 122
129 123 return redirect(url('repo_settings_home', repo_name=changed_name))
130 124
131 125 @HasRepoPermissionAllDecorator('repository.admin')
132 126 def delete(self, repo_name):
133 127 """DELETE /repos/repo_name: Delete an existing item"""
134 128 # Forms posted to this method should contain a hidden field:
135 129 # <input type="hidden" name="_method" value="DELETE" />
136 130 # Or using helpers:
137 131 # h.form(url('repo_settings_delete', repo_name=ID),
138 132 # method='delete')
139 133 # url('repo_settings_delete', repo_name=ID)
140 134
141 135 repo_model = RepoModel()
142 136 repo = repo_model.get_by_repo_name(repo_name)
143 137 if not repo:
144 138 h.flash(_('%s repository is not mapped to db perhaps'
145 139 ' it was moved or renamed from the filesystem'
146 140 ' please run the application again'
147 141 ' in order to rescan repositories') % repo_name,
148 142 category='error')
149 143
150 144 return redirect(url('home'))
151 145 try:
152 146 action_logger(self.rhodecode_user, 'user_deleted_repo',
153 147 repo_name, '', self.sa)
154 148 repo_model.delete(repo)
155 149 invalidate_cache('get_repo_cached_%s' % repo_name)
156 150 h.flash(_('deleted repository %s') % repo_name, category='success')
157 151 except Exception:
158 152 log.error(traceback.format_exc())
159 153 h.flash(_('An error occurred during deletion of %s') % repo_name,
160 154 category='error')
161 155
162 156 return redirect(url('home'))
163 157
164 158 @NotAnonymous()
165 159 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
166 160 'repository.admin')
167 161 def fork(self, repo_name):
168 162 repo_model = RepoModel()
169 163 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
170 164 if not repo:
171 165 h.flash(_('%s repository is not mapped to db perhaps'
172 166 ' it was created or renamed from the file system'
173 167 ' please run the application again'
174 168 ' in order to rescan repositories') % repo_name,
175 169 category='error')
176 170
177 171 return redirect(url('home'))
178 172
179 173 return render('settings/repo_fork.html')
180 174
181 175 @NotAnonymous()
182 176 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
183 177 'repository.admin')
184 178 def fork_create(self, repo_name):
185 179 repo_model = RepoModel()
186 180 c.repo_info = repo_model.get_by_repo_name(repo_name)
187 181 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type})()
188 182 form_result = {}
189 183 try:
190 184 form_result = _form.to_python(dict(request.POST))
191 185 form_result.update({'repo_name': repo_name})
192 186 repo_model.create_fork(form_result, self.rhodecode_user)
193 187 h.flash(_('forked %s repository as %s') \
194 188 % (repo_name, form_result['fork_name']),
195 189 category='success')
196 190 action_logger(self.rhodecode_user,
197 191 'user_forked_repo:%s' % form_result['fork_name'],
198 192 repo_name, '', self.sa)
199 193 except formencode.Invalid, errors:
200 194 c.new_repo = errors.value['fork_name']
201 195 r = render('settings/repo_fork.html')
202 196
203 197 return htmlfill.render(
204 198 r,
205 199 defaults=errors.value,
206 200 errors=errors.error_dict or {},
207 201 prefix_error=False,
208 202 encoding="UTF-8")
209 203 except Exception:
210 204 log.error(traceback.format_exc())
211 205 h.flash(_('An error occurred during repository forking %s') %
212 206 repo_name, category='error')
213 207
214 208 return redirect(url('home'))
@@ -1,408 +1,407 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Some simple helper functions
7 7
8 8 :created_on: Jan 5, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27
28 28 def __get_lem():
29 29 from pygments import lexers
30 30 from string import lower
31 31 from collections import defaultdict
32 32
33 33 d = defaultdict(lambda: [])
34 34
35 35 def __clean(s):
36 36 s = s.lstrip('*')
37 37 s = s.lstrip('.')
38 38
39 39 if s.find('[') != -1:
40 40 exts = []
41 41 start, stop = s.find('['), s.find(']')
42 42
43 43 for suffix in s[start + 1:stop]:
44 44 exts.append(s[:s.find('[')] + suffix)
45 45 return map(lower, exts)
46 46 else:
47 47 return map(lower, [s])
48 48
49 49 for lx, t in sorted(lexers.LEXERS.items()):
50 50 m = map(__clean, t[-2])
51 51 if m:
52 52 m = reduce(lambda x, y: x + y, m)
53 53 for ext in m:
54 54 desc = lx.replace('Lexer', '')
55 55 d[ext].append(desc)
56 56
57 57 return dict(d)
58 58
59 59 # language map is also used by whoosh indexer, which for those specified
60 60 # extensions will index it's content
61 61 LANGUAGES_EXTENSIONS_MAP = __get_lem()
62 62
63 63 # Additional mappings that are not present in the pygments lexers
64 64 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
65 65 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
66 66
67 67 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
68 68
69 69
70 70 def str2bool(_str):
71 71 """
72 72 returs True/False value from given string, it tries to translate the
73 73 string into boolean
74 74
75 75 :param _str: string value to translate into boolean
76 76 :rtype: boolean
77 77 :returns: boolean from given string
78 78 """
79 79 if _str is None:
80 80 return False
81 81 if _str in (True, False):
82 82 return _str
83 83 _str = str(_str).strip().lower()
84 84 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
85 85
86 86
87 87 def convert_line_endings(line, mode):
88 88 """
89 89 Converts a given line "line end" accordingly to given mode
90 90
91 91 Available modes are::
92 92 0 - Unix
93 93 1 - Mac
94 94 2 - DOS
95 95
96 96 :param line: given line to convert
97 97 :param mode: mode to convert to
98 98 :rtype: str
99 99 :return: converted line according to mode
100 100 """
101 101 from string import replace
102 102
103 103 if mode == 0:
104 104 line = replace(line, '\r\n', '\n')
105 105 line = replace(line, '\r', '\n')
106 106 elif mode == 1:
107 107 line = replace(line, '\r\n', '\r')
108 108 line = replace(line, '\n', '\r')
109 109 elif mode == 2:
110 110 import re
111 111 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
112 112 return line
113 113
114 114
115 115 def detect_mode(line, default):
116 116 """
117 117 Detects line break for given line, if line break couldn't be found
118 118 given default value is returned
119 119
120 120 :param line: str line
121 121 :param default: default
122 122 :rtype: int
123 123 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
124 124 """
125 125 if line.endswith('\r\n'):
126 126 return 2
127 127 elif line.endswith('\n'):
128 128 return 0
129 129 elif line.endswith('\r'):
130 130 return 1
131 131 else:
132 132 return default
133 133
134 134
135 135 def generate_api_key(username, salt=None):
136 136 """
137 137 Generates unique API key for given username, if salt is not given
138 138 it'll be generated from some random string
139 139
140 140 :param username: username as string
141 141 :param salt: salt to hash generate KEY
142 142 :rtype: str
143 143 :returns: sha1 hash from username+salt
144 144 """
145 145 from tempfile import _RandomNameSequence
146 146 import hashlib
147 147
148 148 if salt is None:
149 149 salt = _RandomNameSequence().next()
150 150
151 151 return hashlib.sha1(username + salt).hexdigest()
152 152
153 153
154 154 def safe_unicode(str_, from_encoding='utf8'):
155 155 """
156 156 safe unicode function. Does few trick to turn str_ into unicode
157 157
158 158 In case of UnicodeDecode error we try to return it with encoding detected
159 159 by chardet library if it fails fallback to unicode with errors replaced
160 160
161 161 :param str_: string to decode
162 162 :rtype: unicode
163 163 :returns: unicode object
164 164 """
165 165 if isinstance(str_, unicode):
166 166 return str_
167 167
168 168 try:
169 169 return unicode(str_)
170 170 except UnicodeDecodeError:
171 171 pass
172 172
173 173 try:
174 174 return unicode(str_, from_encoding)
175 175 except UnicodeDecodeError:
176 176 pass
177 177
178 178 try:
179 179 import chardet
180 180 encoding = chardet.detect(str_)['encoding']
181 181 if encoding is None:
182 182 raise Exception()
183 183 return str_.decode(encoding)
184 184 except (ImportError, UnicodeDecodeError, Exception):
185 185 return unicode(str_, from_encoding, 'replace')
186 186
187 187 def safe_str(unicode_, to_encoding='utf8'):
188 188 """
189 189 safe str function. Does few trick to turn unicode_ into string
190 190
191 191 In case of UnicodeEncodeError we try to return it with encoding detected
192 192 by chardet library if it fails fallback to string with errors replaced
193 193
194 194 :param unicode_: unicode to encode
195 195 :rtype: str
196 196 :returns: str object
197 197 """
198 198
199 199 if isinstance(unicode_, str):
200 200 return unicode_
201 201
202 202 try:
203 203 return unicode_.encode(to_encoding)
204 204 except UnicodeEncodeError:
205 205 pass
206 206
207 207 try:
208 208 import chardet
209 209 encoding = chardet.detect(unicode_)['encoding']
210 210 print encoding
211 211 if encoding is None:
212 212 raise UnicodeEncodeError()
213 213
214 214 return unicode_.encode(encoding)
215 215 except (ImportError, UnicodeEncodeError):
216 216 return unicode_.encode(to_encoding, 'replace')
217 217
218 218 return safe_str
219 219
220 220
221 221
222 222 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
223 223 """
224 224 Custom engine_from_config functions that makes sure we use NullPool for
225 225 file based sqlite databases. This prevents errors on sqlite. This only
226 226 applies to sqlalchemy versions < 0.7.0
227 227
228 228 """
229 229 import sqlalchemy
230 230 from sqlalchemy import engine_from_config as efc
231 231 import logging
232 232
233 233 if int(sqlalchemy.__version__.split('.')[1]) < 7:
234 234
235 235 # This solution should work for sqlalchemy < 0.7.0, and should use
236 236 # proxy=TimerProxy() for execution time profiling
237 237
238 238 from sqlalchemy.pool import NullPool
239 239 url = configuration[prefix + 'url']
240 240
241 241 if url.startswith('sqlite'):
242 242 kwargs.update({'poolclass': NullPool})
243 243 return efc(configuration, prefix, **kwargs)
244 244 else:
245 245 import time
246 246 from sqlalchemy import event
247 247 from sqlalchemy.engine import Engine
248 248
249 249 log = logging.getLogger('sqlalchemy.engine')
250 250 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
251 251 engine = efc(configuration, prefix, **kwargs)
252 252
253 253 def color_sql(sql):
254 254 COLOR_SEQ = "\033[1;%dm"
255 255 COLOR_SQL = YELLOW
256 256 normal = '\x1b[0m'
257 257 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
258 258
259 259 if configuration['debug']:
260 260 #attach events only for debug configuration
261 261
262 262 def before_cursor_execute(conn, cursor, statement,
263 263 parameters, context, executemany):
264 264 context._query_start_time = time.time()
265 265 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
266 266
267 267
268 268 def after_cursor_execute(conn, cursor, statement,
269 269 parameters, context, executemany):
270 270 total = time.time() - context._query_start_time
271 271 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
272 272
273 273 event.listen(engine, "before_cursor_execute",
274 274 before_cursor_execute)
275 275 event.listen(engine, "after_cursor_execute",
276 276 after_cursor_execute)
277 277
278 278 return engine
279 279
280 280
281 281 def age(curdate):
282 282 """
283 283 turns a datetime into an age string.
284 284
285 285 :param curdate: datetime object
286 286 :rtype: unicode
287 287 :returns: unicode words describing age
288 288 """
289 289
290 290 from datetime import datetime
291 291 from webhelpers.date import time_ago_in_words
292 292
293 293 _ = lambda s:s
294 294
295 295 if not curdate:
296 296 return ''
297 297
298 298 agescales = [(_(u"year"), 3600 * 24 * 365),
299 299 (_(u"month"), 3600 * 24 * 30),
300 300 (_(u"day"), 3600 * 24),
301 301 (_(u"hour"), 3600),
302 302 (_(u"minute"), 60),
303 303 (_(u"second"), 1), ]
304 304
305 305 age = datetime.now() - curdate
306 306 age_seconds = (age.days * agescales[2][1]) + age.seconds
307 307 pos = 1
308 308 for scale in agescales:
309 309 if scale[1] <= age_seconds:
310 310 if pos == 6:pos = 5
311 311 return '%s %s' % (time_ago_in_words(curdate,
312 312 agescales[pos][0]), _('ago'))
313 313 pos += 1
314 314
315 315 return _(u'just now')
316 316
317 317
318 318 def uri_filter(uri):
319 319 """
320 320 Removes user:password from given url string
321 321
322 322 :param uri:
323 323 :rtype: unicode
324 324 :returns: filtered list of strings
325 325 """
326 326 if not uri:
327 327 return ''
328 328
329 329 proto = ''
330 330
331 331 for pat in ('https://', 'http://'):
332 332 if uri.startswith(pat):
333 333 uri = uri[len(pat):]
334 334 proto = pat
335 335 break
336 336
337 337 # remove passwords and username
338 338 uri = uri[uri.find('@') + 1:]
339 339
340 340 # get the port
341 341 cred_pos = uri.find(':')
342 342 if cred_pos == -1:
343 343 host, port = uri, None
344 344 else:
345 345 host, port = uri[:cred_pos], uri[cred_pos + 1:]
346 346
347 347 return filter(None, [proto, host, port])
348 348
349 349
350 350 def credentials_filter(uri):
351 351 """
352 352 Returns a url with removed credentials
353 353
354 354 :param uri:
355 355 """
356 356
357 357 uri = uri_filter(uri)
358 358 #check if we have port
359 359 if len(uri) > 2 and uri[2]:
360 360 uri[2] = ':' + uri[2]
361 361
362 362 return ''.join(uri)
363 363
364 364 def get_changeset_safe(repo, rev):
365 365 """
366 366 Safe version of get_changeset if this changeset doesn't exists for a
367 367 repo it returns a Dummy one instead
368 368
369 369 :param repo:
370 370 :param rev:
371 371 """
372 372 from vcs.backends.base import BaseRepository
373 373 from vcs.exceptions import RepositoryError
374 374 if not isinstance(repo, BaseRepository):
375 375 raise Exception('You must pass an Repository '
376 376 'object as first argument got %s', type(repo))
377 377
378 378 try:
379 379 cs = repo.get_changeset(rev)
380 380 except RepositoryError:
381 381 from rhodecode.lib.utils import EmptyChangeset
382 382 cs = EmptyChangeset(requested_revision=rev)
383 383 return cs
384 384
385 385
386 386 def get_current_revision(quiet=False):
387 387 """
388 388 Returns tuple of (number, id) from repository containing this package
389 389 or None if repository could not be found.
390 390
391 391 :param quiet: prints error for fetching revision if True
392 392 """
393 393
394 394 try:
395 395 from vcs import get_repo
396 396 from vcs.utils.helpers import get_scm
397 from vcs.exceptions import RepositoryError, VCSError
398 397 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
399 398 scm = get_scm(repopath)[0]
400 399 repo = get_repo(path=repopath, alias=scm)
401 400 tip = repo.get_changeset()
402 401 return (tip.revision, tip.short_id)
403 except (ImportError, RepositoryError, VCSError), err:
402 except Exception, err:
404 403 if not quiet:
405 404 print ("Cannot retrieve rhodecode's revision. Original error "
406 405 "was: %s" % err)
407 406 return None
408 407
@@ -1,147 +1,151 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.changelog
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode authentication library for LDAP
7 7
8 8 :created_on: Created on Nov 17, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27
28 28 from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \
29 29 LdapPasswordError
30 30
31 31 log = logging.getLogger(__name__)
32 32
33 33
34 34 try:
35 35 import ldap
36 36 except ImportError:
37 37 # means that python-ldap is not installed
38 38 pass
39 39
40 40
41 41 class AuthLdap(object):
42 42
43 43 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
44 44 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
45 45 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
46 46 search_scope='SUBTREE',
47 47 attr_login='uid'):
48 48 self.ldap_version = ldap_version
49 49 ldap_server_type = 'ldap'
50 50
51 51 self.TLS_KIND = tls_kind
52 52
53 53 if self.TLS_KIND == 'LDAPS':
54 54 port = port or 689
55 55 ldap_server_type = ldap_server_type + 's'
56
57 self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
56
57 OPT_X_TLS_DEMAND = 2
58 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
59 OPT_X_TLS_DEMAND)
58 60 self.LDAP_SERVER_ADDRESS = server
59 61 self.LDAP_SERVER_PORT = port
60 62
61 63 #USE FOR READ ONLY BIND TO LDAP SERVER
62 64 self.LDAP_BIND_DN = bind_dn
63 65 self.LDAP_BIND_PASS = bind_pass
64 66
65 67 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
66 self.LDAP_SERVER_ADDRESS,
67 self.LDAP_SERVER_PORT)
68 self.LDAP_SERVER_ADDRESS,
69 self.LDAP_SERVER_PORT)
68 70
69 71 self.BASE_DN = base_dn
70 72 self.LDAP_FILTER = ldap_filter
71 self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
73 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
72 74 self.attr_login = attr_login
73 75
74 76 def authenticate_ldap(self, username, password):
75 77 """Authenticate a user via LDAP and return his/her LDAP properties.
76 78
77 79 Raises AuthenticationError if the credentials are rejected, or
78 80 EnvironmentError if the LDAP server can't be reached.
79 81
80 82 :param username: username
81 83 :param password: password
82 84 """
83 85
84 86 from rhodecode.lib.helpers import chop_at
85 87
86 88 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
87 89
88 90 if "," in username:
89 91 raise LdapUsernameError("invalid character in username: ,")
90 92 try:
91 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
93 if hasattr(ldap,'OPT_X_TLS_CACERTDIR'):
94 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
95 '/etc/openldap/cacerts')
92 96 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
93 97 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
94 98 ldap.set_option(ldap.OPT_TIMEOUT, 20)
95 99 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
96 100 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
97 101 if self.TLS_KIND != 'PLAIN':
98 102 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
99 103 server = ldap.initialize(self.LDAP_SERVER)
100 104 if self.ldap_version == 2:
101 105 server.protocol = ldap.VERSION2
102 106 else:
103 107 server.protocol = ldap.VERSION3
104 108
105 109 if self.TLS_KIND == 'START_TLS':
106 110 server.start_tls_s()
107 111
108 112 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
109 113 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
110 114
111 115 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
112 116 username)
113 117 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
114 118 filt, self.LDAP_SERVER)
115 119 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
116 120 filt)
117 121
118 122 if not lobjects:
119 123 raise ldap.NO_SUCH_OBJECT()
120 124
121 125 for (dn, _attrs) in lobjects:
122 126 if dn is None:
123 127 continue
124 128
125 129 try:
126 130 server.simple_bind_s(dn, password)
127 131 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
128 132 '(objectClass=*)')[0][1]
129 133 break
130 134
131 135 except ldap.INVALID_CREDENTIALS, e:
132 136 log.debug("LDAP rejected password for user '%s' (%s): %s",
133 137 uid, username, dn)
134 138
135 139 else:
136 140 log.debug("No matching LDAP objects for authentication "
137 141 "of '%s' (%s)", uid, username)
138 142 raise LdapPasswordError()
139 143
140 144 except ldap.NO_SUCH_OBJECT, e:
141 145 log.debug("LDAP says no such user '%s' (%s)", uid, username)
142 146 raise LdapUsernameError()
143 147 except ldap.SERVER_DOWN, e:
144 148 raise LdapConnectionError("LDAP can't access "
145 149 "authentication server")
146 150
147 151 return (dn, attrs)
@@ -1,411 +1,412 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.celerylib.tasks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode task modules, containing all task that suppose to be run
7 7 by celery daemon
8 8
9 9 :created_on: Oct 6, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 from celery.decorators import task
27 27
28 28 import os
29 29 import traceback
30 30 import logging
31 31 from os.path import dirname as dn, join as jn
32 32
33 33 from time import mktime
34 34 from operator import itemgetter
35 35 from string import lower
36 36
37 37 from pylons import config, url
38 38 from pylons.i18n.translation import _
39 39
40 40 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
41 41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
42 42 __get_lockkey, LockHeld, DaemonLock
43 43 from rhodecode.lib.helpers import person
44 44 from rhodecode.lib.smtp_mailer import SmtpMailer
45 45 from rhodecode.lib.utils import add_cache
46 46 from rhodecode.lib.compat import json, OrderedDict
47 47
48 48 from rhodecode.model import init_model
49 49 from rhodecode.model import meta
50 50 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
51 51
52 52 from vcs.backends import get_repo
53 53
54 54 from sqlalchemy import engine_from_config
55 55
56 56 add_cache(config)
57 57
58 58
59 59
60 60 __all__ = ['whoosh_index', 'get_commits_stats',
61 61 'reset_user_password', 'send_email']
62 62
63 63 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
64 64
65 65
66 66 def get_session():
67 67 if CELERY_ON:
68 68 engine = engine_from_config(config, 'sqlalchemy.db1.')
69 69 init_model(engine)
70 70 sa = meta.Session()
71 71 return sa
72 72
73 73
74 74 def get_repos_path():
75 75 sa = get_session()
76 76 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
77 77 return q.ui_value
78 78
79 79
80 80 @task(ignore_result=True)
81 81 @locked_task
82 82 def whoosh_index(repo_location, full_index):
83 83 #log = whoosh_index.get_logger()
84 84 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
85 85 index_location = config['index_dir']
86 86 WhooshIndexingDaemon(index_location=index_location,
87 87 repo_location=repo_location, sa=get_session())\
88 88 .run(full_index=full_index)
89 89
90 90
91 91 @task(ignore_result=True)
92 92 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
93 93 try:
94 94 log = get_commits_stats.get_logger()
95 95 except:
96 96 log = logging.getLogger(__name__)
97 97
98 98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
99 99 ts_max_y)
100 100 lockkey_path = config['here']
101 101
102 102 log.info('running task with lockkey %s', lockkey)
103 103 try:
104 104 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
105 105
106 106 #for js data compatibilty cleans the key for person from '
107 107 akc = lambda k: person(k).replace('"', "")
108 108
109 109 co_day_auth_aggr = {}
110 110 commits_by_day_aggregate = {}
111 111 repos_path = get_repos_path()
112 112 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
113 113 repo_size = len(repo.revisions)
114 114 #return if repo have no revisions
115 115 if repo_size < 1:
116 116 lock.release()
117 117 return True
118 118
119 119 skip_date_limit = True
120 120 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
121 121 last_rev = 0
122 122 last_cs = None
123 123 timegetter = itemgetter('time')
124 124
125 125 sa = get_session()
126 126
127 127 dbrepo = sa.query(Repository)\
128 128 .filter(Repository.repo_name == repo_name).scalar()
129 129 cur_stats = sa.query(Statistics)\
130 130 .filter(Statistics.repository == dbrepo).scalar()
131 131
132 132 if cur_stats is not None:
133 133 last_rev = cur_stats.stat_on_revision
134 134
135 135 if last_rev == repo.get_changeset().revision and repo_size > 1:
136 136 #pass silently without any work if we're not on first revision or
137 137 #current state of parsing revision(from db marker) is the
138 138 #last revision
139 139 lock.release()
140 140 return True
141 141
142 142 if cur_stats:
143 143 commits_by_day_aggregate = OrderedDict(json.loads(
144 144 cur_stats.commit_activity_combined))
145 145 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
146 146
147 147 log.debug('starting parsing %s', parse_limit)
148 148 lmktime = mktime
149 149
150 150 last_rev = last_rev + 1 if last_rev > 0 else last_rev
151 151
152 152 for cs in repo[last_rev:last_rev + parse_limit]:
153 153 last_cs = cs # remember last parsed changeset
154 154 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
155 155 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
156 156
157 157 if akc(cs.author) in co_day_auth_aggr:
158 158 try:
159 159 l = [timegetter(x) for x in
160 160 co_day_auth_aggr[akc(cs.author)]['data']]
161 161 time_pos = l.index(k)
162 162 except ValueError:
163 163 time_pos = False
164 164
165 165 if time_pos >= 0 and time_pos is not False:
166 166
167 167 datadict = \
168 168 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
169 169
170 170 datadict["commits"] += 1
171 171 datadict["added"] += len(cs.added)
172 172 datadict["changed"] += len(cs.changed)
173 173 datadict["removed"] += len(cs.removed)
174 174
175 175 else:
176 176 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
177 177
178 178 datadict = {"time": k,
179 179 "commits": 1,
180 180 "added": len(cs.added),
181 181 "changed": len(cs.changed),
182 182 "removed": len(cs.removed),
183 183 }
184 184 co_day_auth_aggr[akc(cs.author)]['data']\
185 185 .append(datadict)
186 186
187 187 else:
188 188 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
189 189 co_day_auth_aggr[akc(cs.author)] = {
190 190 "label": akc(cs.author),
191 191 "data": [{"time":k,
192 192 "commits":1,
193 193 "added":len(cs.added),
194 194 "changed":len(cs.changed),
195 195 "removed":len(cs.removed),
196 196 }],
197 197 "schema": ["commits"],
198 198 }
199 199
200 200 #gather all data by day
201 201 if k in commits_by_day_aggregate:
202 202 commits_by_day_aggregate[k] += 1
203 203 else:
204 204 commits_by_day_aggregate[k] = 1
205 205
206 206 overview_data = sorted(commits_by_day_aggregate.items(),
207 207 key=itemgetter(0))
208 208
209 209 if not co_day_auth_aggr:
210 210 co_day_auth_aggr[akc(repo.contact)] = {
211 211 "label": akc(repo.contact),
212 212 "data": [0, 1],
213 213 "schema": ["commits"],
214 214 }
215 215
216 216 stats = cur_stats if cur_stats else Statistics()
217 217 stats.commit_activity = json.dumps(co_day_auth_aggr)
218 218 stats.commit_activity_combined = json.dumps(overview_data)
219 219
220 220 log.debug('last revison %s', last_rev)
221 221 leftovers = len(repo.revisions[last_rev:])
222 222 log.debug('revisions to parse %s', leftovers)
223 223
224 224 if last_rev == 0 or leftovers < parse_limit:
225 225 log.debug('getting code trending stats')
226 226 stats.languages = json.dumps(__get_codes_stats(repo_name))
227 227
228 228 try:
229 229 stats.repository = dbrepo
230 230 stats.stat_on_revision = last_cs.revision if last_cs else 0
231 231 sa.add(stats)
232 232 sa.commit()
233 233 except:
234 234 log.error(traceback.format_exc())
235 235 sa.rollback()
236 236 lock.release()
237 237 return False
238 238
239 239 #final release
240 240 lock.release()
241 241
242 242 #execute another task if celery is enabled
243 243 if len(repo.revisions) > 1 and CELERY_ON:
244 244 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
245 245 return True
246 246 except LockHeld:
247 247 log.info('LockHeld')
248 248 return 'Task with key %s already running' % lockkey
249 249
250 250 @task(ignore_result=True)
251 251 def send_password_link(user_email):
252 252 try:
253 253 log = reset_user_password.get_logger()
254 254 except:
255 255 log = logging.getLogger(__name__)
256 256
257 257 from rhodecode.lib import auth
258 258 from rhodecode.model.db import User
259 259
260 260 try:
261 261 sa = get_session()
262 262 user = sa.query(User).filter(User.email == user_email).scalar()
263 263
264 264 if user:
265 265 link = url('reset_password_confirmation', key=user.api_key,
266 266 qualified=True)
267 267 tmpl = """
268 268 Hello %s
269 269
270 270 We received a request to create a new password for your account.
271 271
272 272 You can generate it by clicking following URL:
273 273
274 274 %s
275 275
276 276 If you didn't request new password please ignore this email.
277 277 """
278 278 run_task(send_email, user_email,
279 279 "RhodeCode password reset link",
280 280 tmpl % (user.short_contact, link))
281 281 log.info('send new password mail to %s', user_email)
282 282
283 283 except:
284 284 log.error('Failed to update user password')
285 285 log.error(traceback.format_exc())
286 286 return False
287 287
288 288 return True
289 289
290 290 @task(ignore_result=True)
291 291 def reset_user_password(user_email):
292 292 try:
293 293 log = reset_user_password.get_logger()
294 294 except:
295 295 log = logging.getLogger(__name__)
296 296
297 297 from rhodecode.lib import auth
298 298 from rhodecode.model.db import User
299 299
300 300 try:
301 301 try:
302 302 sa = get_session()
303 303 user = sa.query(User).filter(User.email == user_email).scalar()
304 304 new_passwd = auth.PasswordGenerator().gen_password(8,
305 305 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
306 306 if user:
307 307 user.password = auth.get_crypt_password(new_passwd)
308 308 user.api_key = auth.generate_api_key(user.username)
309 309 sa.add(user)
310 310 sa.commit()
311 311 log.info('change password for %s', user_email)
312 312 if new_passwd is None:
313 313 raise Exception('unable to generate new password')
314 314
315 315 except:
316 316 log.error(traceback.format_exc())
317 317 sa.rollback()
318 318
319 319 run_task(send_email, user_email,
320 320 "Your new RhodeCode password",
321 321 'Your new RhodeCode password:%s' % (new_passwd))
322 322 log.info('send new password mail to %s', user_email)
323 323
324 324 except:
325 325 log.error('Failed to update user password')
326 326 log.error(traceback.format_exc())
327 327
328 328 return True
329 329
330 330
331 331 @task(ignore_result=True)
332 332 def send_email(recipients, subject, body):
333 333 """
334 334 Sends an email with defined parameters from the .ini files.
335 335
336 336 :param recipients: list of recipients, it this is empty the defined email
337 337 address from field 'email_to' is used instead
338 338 :param subject: subject of the mail
339 339 :param body: body of the mail
340 340 """
341 341 try:
342 342 log = send_email.get_logger()
343 343 except:
344 344 log = logging.getLogger(__name__)
345 345
346 346 email_config = config
347 347
348 348 if not recipients:
349 349 recipients = [email_config.get('email_to')]
350 350
351 351 mail_from = email_config.get('app_email_from')
352 352 user = email_config.get('smtp_username')
353 353 passwd = email_config.get('smtp_password')
354 354 mail_server = email_config.get('smtp_server')
355 355 mail_port = email_config.get('smtp_port')
356 356 tls = str2bool(email_config.get('smtp_use_tls'))
357 357 ssl = str2bool(email_config.get('smtp_use_ssl'))
358 358 debug = str2bool(config.get('debug'))
359 smtp_auth = email_config.get('smtp_auth')
359 360
360 361 try:
361 m = SmtpMailer(mail_from, user, passwd, mail_server,
362 m = SmtpMailer(mail_from, user, passwd, mail_server,smtp_auth,
362 363 mail_port, ssl, tls, debug=debug)
363 364 m.send(recipients, subject, body)
364 365 except:
365 366 log.error('Mail sending failed')
366 367 log.error(traceback.format_exc())
367 368 return False
368 369 return True
369 370
370 371
371 372 @task(ignore_result=True)
372 373 def create_repo_fork(form_data, cur_user):
373 374 from rhodecode.model.repo import RepoModel
374 375 from vcs import get_backend
375 376
376 377 try:
377 378 log = create_repo_fork.get_logger()
378 379 except:
379 380 log = logging.getLogger(__name__)
380 381
381 382 repo_model = RepoModel(get_session())
382 383 repo_model.create(form_data, cur_user, just_db=True, fork=True)
383 384 repo_name = form_data['repo_name']
384 385 repos_path = get_repos_path()
385 386 repo_path = os.path.join(repos_path, repo_name)
386 387 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
387 388 alias = form_data['repo_type']
388 389
389 390 log.info('creating repo fork %s as %s', repo_name, repo_path)
390 391 backend = get_backend(alias)
391 392 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
392 393
393 394
394 395 def __get_codes_stats(repo_name):
395 396 repos_path = get_repos_path()
396 397 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
397 398 tip = repo.get_changeset()
398 399 code_stats = {}
399 400
400 401 def aggregate(cs):
401 402 for f in cs[2]:
402 403 ext = lower(f.extension)
403 404 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
404 405 if ext in code_stats:
405 406 code_stats[ext] += 1
406 407 else:
407 408 code_stats[ext] = 1
408 409
409 410 map(aggregate, tip.walk('/'))
410 411
411 412 return code_stats or {}
@@ -1,289 +1,291 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplegit
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 7 It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import logging
29 29 import traceback
30 30
31 31 from dulwich import server as dulserver
32 32
33 33
34 34 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35 35
36 36 def handle(self):
37 37 write = lambda x: self.proto.write_sideband(1, x)
38 38
39 39 graph_walker = dulserver.ProtocolGraphWalker(self,
40 40 self.repo.object_store,
41 41 self.repo.get_peeled)
42 42 objects_iter = self.repo.fetch_objects(
43 43 graph_walker.determine_wants, graph_walker, self.progress,
44 44 get_tagged=self.get_tagged)
45 45
46 46 # Do they want any objects?
47 47 if objects_iter is None or len(objects_iter) == 0:
48 48 return
49 49
50 50 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 51 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
52 52 objects_iter, len(objects_iter))
53 53 messages = []
54 54 messages.append('thank you for using rhodecode')
55 55
56 56 for msg in messages:
57 57 self.progress(msg + "\n")
58 58 # we are done
59 59 self.proto.write("0000")
60 60
61 61 dulserver.DEFAULT_HANDLERS = {
62 62 'git-upload-pack': SimpleGitUploadPackHandler,
63 63 'git-receive-pack': dulserver.ReceivePackHandler,
64 64 }
65 65
66 66 from dulwich.repo import Repo
67 67 from dulwich.web import HTTPGitApplication
68 68
69 69 from paste.auth.basic import AuthBasicAuthenticator
70 70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71 71
72 72 from rhodecode.lib import safe_str
73 73 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
74 74 from rhodecode.lib.utils import invalidate_cache, is_valid_repo
75 75 from rhodecode.model.db import User
76 76
77 77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
78 78
79 79 log = logging.getLogger(__name__)
80 80
81 81
82 82 def is_git(environ):
83 83 """Returns True if request's target is git server.
84 84 ``HTTP_USER_AGENT`` would then have git client version given.
85 85
86 86 :param environ:
87 87 """
88 88 http_user_agent = environ.get('HTTP_USER_AGENT')
89 89 if http_user_agent and http_user_agent.startswith('git'):
90 90 return True
91 91 return False
92 92
93 93
94 94 class SimpleGit(object):
95 95
96 96 def __init__(self, application, config):
97 97 self.application = application
98 98 self.config = config
99 99 # base path of repo locations
100 100 self.basepath = self.config['base_path']
101 101 #authenticate this mercurial request using authfunc
102 102 self.authenticate = AuthBasicAuthenticator('', authfunc)
103 103
104 104 def __call__(self, environ, start_response):
105 105 if not is_git(environ):
106 106 return self.application(environ, start_response)
107 107
108 108 proxy_key = 'HTTP_X_REAL_IP'
109 109 def_key = 'REMOTE_ADDR'
110 110 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
111 111 username = None
112 112 # skip passing error to error controller
113 113 environ['pylons.status_code_redirect'] = True
114 114
115 115 #======================================================================
116 116 # EXTRACT REPOSITORY NAME FROM ENV
117 117 #======================================================================
118 118 try:
119 119 repo_name = self.__get_repository(environ)
120 120 log.debug('Extracted repo name is %s' % repo_name)
121 121 except:
122 122 return HTTPInternalServerError()(environ, start_response)
123 123
124 124 #======================================================================
125 125 # GET ACTION PULL or PUSH
126 126 #======================================================================
127 127 action = self.__get_action(environ)
128 128
129 129 #======================================================================
130 130 # CHECK ANONYMOUS PERMISSION
131 131 #======================================================================
132 132 if action in ['pull', 'push']:
133 133 anonymous_user = self.__get_user('default')
134 134 username = anonymous_user.username
135 135 anonymous_perm = self.__check_permission(action,
136 136 anonymous_user,
137 137 repo_name)
138 138
139 139 if anonymous_perm is not True or anonymous_user.active is False:
140 140 if anonymous_perm is not True:
141 141 log.debug('Not enough credentials to access this '
142 142 'repository as anonymous user')
143 143 if anonymous_user.active is False:
144 144 log.debug('Anonymous access is disabled, running '
145 145 'authentication')
146 146 #==============================================================
147 147 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
148 148 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
149 149 #==============================================================
150 150
151 151 if not REMOTE_USER(environ):
152 152 self.authenticate.realm = \
153 153 safe_str(self.config['rhodecode_realm'])
154 154 result = self.authenticate(environ)
155 155 if isinstance(result, str):
156 156 AUTH_TYPE.update(environ, 'basic')
157 157 REMOTE_USER.update(environ, result)
158 158 else:
159 159 return result.wsgi_application(environ, start_response)
160 160
161 161 #==============================================================
162 162 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
163 163 # BASIC AUTH
164 164 #==============================================================
165 165
166 166 if action in ['pull', 'push']:
167 167 username = REMOTE_USER(environ)
168 168 try:
169 169 user = self.__get_user(username)
170 if user is None:
171 return HTTPForbidden()(environ, start_response)
170 172 username = user.username
171 173 except:
172 174 log.error(traceback.format_exc())
173 175 return HTTPInternalServerError()(environ,
174 176 start_response)
175 177
176 178 #check permissions for this repository
177 179 perm = self.__check_permission(action, user,
178 180 repo_name)
179 181 if perm is not True:
180 182 return HTTPForbidden()(environ, start_response)
181 183
182 184 extras = {'ip': ipaddr,
183 185 'username': username,
184 186 'action': action,
185 187 'repository': repo_name}
186 188
187 189 #===================================================================
188 190 # GIT REQUEST HANDLING
189 191 #===================================================================
190 192
191 193 repo_path = safe_str(os.path.join(self.basepath, repo_name))
192 194 log.debug('Repository path is %s' % repo_path)
193 195
194 196 # quick check if that dir exists...
195 197 if is_valid_repo(repo_name, self.basepath) is False:
196 198 return HTTPNotFound()(environ, start_response)
197 199
198 200 try:
199 201 #invalidate cache on push
200 202 if action == 'push':
201 203 self.__invalidate_cache(repo_name)
202 204
203 205 app = self.__make_app(repo_name, repo_path)
204 206 return app(environ, start_response)
205 207 except Exception:
206 208 log.error(traceback.format_exc())
207 209 return HTTPInternalServerError()(environ, start_response)
208 210
209 211 def __make_app(self, repo_name, repo_path):
210 212 """
211 213 Make an wsgi application using dulserver
212 214
213 215 :param repo_name: name of the repository
214 216 :param repo_path: full path to the repository
215 217 """
216 218
217 219 _d = {'/' + repo_name: Repo(repo_path)}
218 220 backend = dulserver.DictBackend(_d)
219 221 gitserve = HTTPGitApplication(backend)
220 222
221 223 return gitserve
222 224
223 225 def __check_permission(self, action, user, repo_name):
224 226 """
225 227 Checks permissions using action (push/pull) user and repository
226 228 name
227 229
228 230 :param action: push or pull action
229 231 :param user: user instance
230 232 :param repo_name: repository name
231 233 """
232 234 if action == 'push':
233 235 if not HasPermissionAnyMiddleware('repository.write',
234 236 'repository.admin')(user,
235 237 repo_name):
236 238 return False
237 239
238 240 else:
239 241 #any other action need at least read permission
240 242 if not HasPermissionAnyMiddleware('repository.read',
241 243 'repository.write',
242 244 'repository.admin')(user,
243 245 repo_name):
244 246 return False
245 247
246 248 return True
247 249
248 250 def __get_repository(self, environ):
249 251 """
250 252 Get's repository name out of PATH_INFO header
251 253
252 254 :param environ: environ where PATH_INFO is stored
253 255 """
254 256 try:
255 257 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
256 258 if repo_name.endswith('/'):
257 259 repo_name = repo_name.rstrip('/')
258 260 except:
259 261 log.error(traceback.format_exc())
260 262 raise
261 263 repo_name = repo_name.split('/')[0]
262 264 return repo_name
263 265
264 266 def __get_user(self, username):
265 267 return User.get_by_username(username)
266 268
267 269 def __get_action(self, environ):
268 270 """Maps git request commands into a pull or push command.
269 271
270 272 :param environ:
271 273 """
272 274 service = environ['QUERY_STRING'].split('=')
273 275 if len(service) > 1:
274 276 service_cmd = service[1]
275 277 mapping = {'git-receive-pack': 'push',
276 278 'git-upload-pack': 'pull',
277 279 }
278 280
279 281 return mapping.get(service_cmd,
280 282 service_cmd if service_cmd else 'other')
281 283 else:
282 284 return 'other'
283 285
284 286 def __invalidate_cache(self, repo_name):
285 287 """we know that some change was made to repositories and we should
286 288 invalidate the cache to see the changes right away but only for
287 289 push requests"""
288 290 invalidate_cache('get_repo_cached_%s' % repo_name)
289 291
@@ -1,288 +1,290 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplehg
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleHG middleware for handling mercurial protocol request
7 7 (push/clone etc.). It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import logging
29 29 import traceback
30 30
31 31 from mercurial.error import RepoError
32 32 from mercurial.hgweb import hgweb_mod
33 33
34 34 from paste.auth.basic import AuthBasicAuthenticator
35 35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36 36
37 37 from rhodecode.lib import safe_str
38 38 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
39 39 from rhodecode.lib.utils import make_ui, invalidate_cache, \
40 40 is_valid_repo, ui_sections
41 41 from rhodecode.model.db import User
42 42
43 43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 def is_mercurial(environ):
49 49 """Returns True if request's target is mercurial server - header
50 50 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 51 """
52 52 http_accept = environ.get('HTTP_ACCEPT')
53 53 if http_accept and http_accept.startswith('application/mercurial'):
54 54 return True
55 55 return False
56 56
57 57
58 58 class SimpleHg(object):
59 59
60 60 def __init__(self, application, config):
61 61 self.application = application
62 62 self.config = config
63 63 # base path of repo locations
64 64 self.basepath = self.config['base_path']
65 65 #authenticate this mercurial request using authfunc
66 66 self.authenticate = AuthBasicAuthenticator('', authfunc)
67 67 self.ipaddr = '0.0.0.0'
68 68
69 69 def __call__(self, environ, start_response):
70 70 if not is_mercurial(environ):
71 71 return self.application(environ, start_response)
72 72
73 73 proxy_key = 'HTTP_X_REAL_IP'
74 74 def_key = 'REMOTE_ADDR'
75 75 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
76 76
77 77 # skip passing error to error controller
78 78 environ['pylons.status_code_redirect'] = True
79 79
80 80 #======================================================================
81 81 # EXTRACT REPOSITORY NAME FROM ENV
82 82 #======================================================================
83 83 try:
84 84 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
85 85 log.debug('Extracted repo name is %s' % repo_name)
86 86 except:
87 87 return HTTPInternalServerError()(environ, start_response)
88 88
89 89 #======================================================================
90 90 # GET ACTION PULL or PUSH
91 91 #======================================================================
92 92 action = self.__get_action(environ)
93 93
94 94 #======================================================================
95 95 # CHECK ANONYMOUS PERMISSION
96 96 #======================================================================
97 97 if action in ['pull', 'push']:
98 98 anonymous_user = self.__get_user('default')
99 99
100 100 username = anonymous_user.username
101 101 anonymous_perm = self.__check_permission(action,
102 102 anonymous_user,
103 103 repo_name)
104 104
105 105 if anonymous_perm is not True or anonymous_user.active is False:
106 106 if anonymous_perm is not True:
107 107 log.debug('Not enough credentials to access this '
108 108 'repository as anonymous user')
109 109 if anonymous_user.active is False:
110 110 log.debug('Anonymous access is disabled, running '
111 111 'authentication')
112 112 #==============================================================
113 113 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
114 114 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
115 115 #==============================================================
116 116
117 117 if not REMOTE_USER(environ):
118 118 self.authenticate.realm = \
119 119 safe_str(self.config['rhodecode_realm'])
120 120 result = self.authenticate(environ)
121 121 if isinstance(result, str):
122 122 AUTH_TYPE.update(environ, 'basic')
123 123 REMOTE_USER.update(environ, result)
124 124 else:
125 125 return result.wsgi_application(environ, start_response)
126 126
127 127 #==============================================================
128 128 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
129 129 # BASIC AUTH
130 130 #==============================================================
131 131
132 132 if action in ['pull', 'push']:
133 133 username = REMOTE_USER(environ)
134 134 try:
135 135 user = self.__get_user(username)
136 if user is None:
137 return HTTPForbidden()(environ, start_response)
136 138 username = user.username
137 139 except:
138 140 log.error(traceback.format_exc())
139 141 return HTTPInternalServerError()(environ,
140 142 start_response)
141 143
142 144 #check permissions for this repository
143 145 perm = self.__check_permission(action, user,
144 146 repo_name)
145 147 if perm is not True:
146 148 return HTTPForbidden()(environ, start_response)
147 149
148 150 extras = {'ip': ipaddr,
149 151 'username': username,
150 152 'action': action,
151 153 'repository': repo_name}
152 154
153 155 #======================================================================
154 156 # MERCURIAL REQUEST HANDLING
155 157 #======================================================================
156 158
157 159 repo_path = safe_str(os.path.join(self.basepath, repo_name))
158 160 log.debug('Repository path is %s' % repo_path)
159 161
160 162 baseui = make_ui('db')
161 163 self.__inject_extras(repo_path, baseui, extras)
162 164
163 165
164 166 # quick check if that dir exists...
165 167 if is_valid_repo(repo_name, self.basepath) is False:
166 168 return HTTPNotFound()(environ, start_response)
167 169
168 170 try:
169 171 #invalidate cache on push
170 172 if action == 'push':
171 173 self.__invalidate_cache(repo_name)
172 174
173 175 app = self.__make_app(repo_path, baseui, extras)
174 176 return app(environ, start_response)
175 177 except RepoError, e:
176 178 if str(e).find('not found') != -1:
177 179 return HTTPNotFound()(environ, start_response)
178 180 except Exception:
179 181 log.error(traceback.format_exc())
180 182 return HTTPInternalServerError()(environ, start_response)
181 183
182 184 def __make_app(self, repo_name, baseui, extras):
183 185 """
184 186 Make an wsgi application using hgweb, and inject generated baseui
185 187 instance, additionally inject some extras into ui object
186 188 """
187 189 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
188 190
189 191
190 192 def __check_permission(self, action, user, repo_name):
191 193 """
192 194 Checks permissions using action (push/pull) user and repository
193 195 name
194 196
195 197 :param action: push or pull action
196 198 :param user: user instance
197 199 :param repo_name: repository name
198 200 """
199 201 if action == 'push':
200 202 if not HasPermissionAnyMiddleware('repository.write',
201 203 'repository.admin')(user,
202 204 repo_name):
203 205 return False
204 206
205 207 else:
206 208 #any other action need at least read permission
207 209 if not HasPermissionAnyMiddleware('repository.read',
208 210 'repository.write',
209 211 'repository.admin')(user,
210 212 repo_name):
211 213 return False
212 214
213 215 return True
214 216
215 217 def __get_repository(self, environ):
216 218 """
217 219 Get's repository name out of PATH_INFO header
218 220
219 221 :param environ: environ where PATH_INFO is stored
220 222 """
221 223 try:
222 224 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
223 225 if repo_name.endswith('/'):
224 226 repo_name = repo_name.rstrip('/')
225 227 except:
226 228 log.error(traceback.format_exc())
227 229 raise
228 230
229 231 return repo_name
230 232
231 233 def __get_user(self, username):
232 234 return User.get_by_username(username)
233 235
234 236 def __get_action(self, environ):
235 237 """
236 238 Maps mercurial request commands into a clone,pull or push command.
237 239 This should always return a valid command string
238 240
239 241 :param environ:
240 242 """
241 243 mapping = {'changegroup': 'pull',
242 244 'changegroupsubset': 'pull',
243 245 'stream_out': 'pull',
244 246 'listkeys': 'pull',
245 247 'unbundle': 'push',
246 248 'pushkey': 'push', }
247 249 for qry in environ['QUERY_STRING'].split('&'):
248 250 if qry.startswith('cmd'):
249 251 cmd = qry.split('=')[-1]
250 252 if cmd in mapping:
251 253 return mapping[cmd]
252 254 else:
253 255 return 'pull'
254 256
255 257 def __invalidate_cache(self, repo_name):
256 258 """we know that some change was made to repositories and we should
257 259 invalidate the cache to see the changes right away but only for
258 260 push requests"""
259 261 invalidate_cache('get_repo_cached_%s' % repo_name)
260 262
261 263 def __inject_extras(self, repo_path, baseui, extras={}):
262 264 """
263 265 Injects some extra params into baseui instance
264 266
265 267 also overwrites global settings with those takes from local hgrc file
266 268
267 269 :param baseui: baseui instance
268 270 :param extras: dict with extra params to put into baseui
269 271 """
270 272
271 273 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
272 274
273 275 # make our hgweb quiet so it doesn't print output
274 276 baseui.setconfig('ui', 'quiet', 'true')
275 277
276 278 #inject some additional parameters that will be available in ui
277 279 #for hooks
278 280 for k, v in extras.items():
279 281 baseui.setconfig('rhodecode_extras', k, v)
280 282
281 283 repoui = make_ui('file', hgrc, False)
282 284
283 285 if repoui:
284 286 #overwrite our ui instance with the section from hgrc file
285 287 for section in ui_sections:
286 288 for k, v in repoui.configitems(section):
287 289 baseui.setconfig(section, k, v)
288 290
@@ -1,161 +1,165 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.smtp_mailer
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Simple smtp mailer used in RhodeCode
7 7
8 8 :created_on: Sep 13, 2010
9 9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 10 :license: GPLv3, see COPYING for more details.
11 11 """
12 12 # This program is free software: you can redistribute it and/or modify
13 13 # it under the terms of the GNU General Public License as published by
14 14 # the Free Software Foundation, either version 3 of the License, or
15 15 # (at your option) any later version.
16 16 #
17 17 # This program is distributed in the hope that it will be useful,
18 18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 20 # GNU General Public License for more details.
21 21 #
22 22 # You should have received a copy of the GNU General Public License
23 23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 24
25 25 import logging
26 26 import smtplib
27 27 import mimetypes
28 28 from socket import sslerror
29 29
30 30 from email.mime.multipart import MIMEMultipart
31 31 from email.mime.image import MIMEImage
32 32 from email.mime.audio import MIMEAudio
33 33 from email.mime.base import MIMEBase
34 34 from email.mime.text import MIMEText
35 35 from email.utils import formatdate
36 36 from email import encoders
37 37
38 38
39 39 class SmtpMailer(object):
40 40 """SMTP mailer class
41 41
42 mailer = SmtpMailer(mail_from, user, passwd, mail_server,
42 mailer = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth
43 43 mail_port, ssl, tls)
44 44 mailer.send(recipients, subject, body, attachment_files)
45 45
46 46 :param recipients might be a list of string or single string
47 47 :param attachment_files is a dict of {filename:location}
48 48 it tries to guess the mimetype and attach the file
49 49
50 50 """
51 51
52 def __init__(self, mail_from, user, passwd, mail_server,
53 mail_port=None, ssl=False, tls=False, debug=False):
52 def __init__(self, mail_from, user, passwd, mail_server, smtp_auth=None,
53 mail_port=None, ssl=False, tls=False, debug=False):
54 54
55 55 self.mail_from = mail_from
56 56 self.mail_server = mail_server
57 57 self.mail_port = mail_port
58 58 self.user = user
59 59 self.passwd = passwd
60 60 self.ssl = ssl
61 61 self.tls = tls
62 62 self.debug = debug
63 self.auth = smtp_auth
63 64
64 65 def send(self, recipients=[], subject='', body='', attachment_files=None):
65 66
66 67 if isinstance(recipients, basestring):
67 68 recipients = [recipients]
68 69 if self.ssl:
69 70 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
70 71 else:
71 72 smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
72 73
73 74 if self.tls:
74 75 smtp_serv.ehlo()
75 76 smtp_serv.starttls()
76 77
77 78 if self.debug:
78 79 smtp_serv.set_debuglevel(1)
79 80
80 81 smtp_serv.ehlo()
82 if self.auth:
83 smtp_serv.esmtp_features["auth"] = self.auth
81 84
82 #if server requires authorization you must provide login and password
83 #but only if we have them
85 # if server requires authorization you must provide login and password
86 # but only if we have them
84 87 if self.user and self.passwd:
85 88 smtp_serv.login(self.user, self.passwd)
86 89
87 90 date_ = formatdate(localtime=True)
88 91 msg = MIMEMultipart()
89 92 msg.set_type('multipart/alternative')
90 93 msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
91 94
92 95 text_msg = MIMEText(body)
93 96 text_msg.set_type('text/plain')
94 97 text_msg.set_param('charset', 'UTF-8')
95 98
96 99 msg['From'] = self.mail_from
97 100 msg['To'] = ','.join(recipients)
98 101 msg['Date'] = date_
99 102 msg['Subject'] = subject
100 103
101 104 msg.attach(text_msg)
102 105
103 106 if attachment_files:
104 107 self.__atach_files(msg, attachment_files)
105 108
106 109 smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
107 110 logging.info('MAIL SEND TO: %s' % recipients)
108 111
109 112 try:
110 113 smtp_serv.quit()
111 114 except sslerror:
112 115 # sslerror is raised in tls connections on closing sometimes
113 116 pass
114 117
115 118 def __atach_files(self, msg, attachment_files):
116 119 if isinstance(attachment_files, dict):
117 120 for f_name, msg_file in attachment_files.items():
118 121 ctype, encoding = mimetypes.guess_type(f_name)
119 122 logging.info("guessing file %s type based on %s", ctype,
120 123 f_name)
121 124 if ctype is None or encoding is not None:
122 125 # No guess could be made, or the file is encoded
123 126 # (compressed), so use a generic bag-of-bits type.
124 127 ctype = 'application/octet-stream'
125 128 maintype, subtype = ctype.split('/', 1)
126 129 if maintype == 'text':
127 130 # Note: we should handle calculating the charset
128 131 file_part = MIMEText(self.get_content(msg_file),
129 132 _subtype=subtype)
130 133 elif maintype == 'image':
131 134 file_part = MIMEImage(self.get_content(msg_file),
132 135 _subtype=subtype)
133 136 elif maintype == 'audio':
134 137 file_part = MIMEAudio(self.get_content(msg_file),
135 138 _subtype=subtype)
136 139 else:
137 140 file_part = MIMEBase(maintype, subtype)
138 141 file_part.set_payload(self.get_content(msg_file))
139 142 # Encode the payload using Base64
140 143 encoders.encode_base64(msg)
141 144 # Set the filename parameter
142 145 file_part.add_header('Content-Disposition', 'attachment',
143 146 filename=f_name)
144 147 file_part.add_header('Content-Type', ctype, name=f_name)
145 148 msg.attach(file_part)
146 149 else:
147 150 raise Exception('Attachment files should be'
148 151 'a dict in format {"filename":"filepath"}')
149 152
150 153 def get_content(self, msg_file):
151 154 """Get content based on type, if content is a string do open first
152 155 else just read because it's a probably open file object
153 156
154 157 :param msg_file:
155 158 """
156 159 if isinstance(msg_file, str):
157 160 return open(msg_file, "rb").read()
158 161 else:
159 #just for safe seek to 0
162 # just for safe seek to 0
160 163 msg_file.seek(0)
161 164 return msg_file.read()
165
@@ -1,1051 +1,1069 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 from datetime import date
31 31
32 32 from sqlalchemy import *
33 from sqlalchemy.exc import DatabaseError
34 33 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper, \
36 validates
37 from sqlalchemy.orm.interfaces import MapperExtension
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
38 35 from beaker.cache import cache_region, region_invalidate
39 36
40 37 from vcs import get_backend
41 38 from vcs.utils.helpers import get_scm
42 39 from vcs.exceptions import VCSError
43 40 from vcs.utils.lazy import LazyProperty
44 41
45 42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
46 43 generate_api_key, safe_unicode
47 44 from rhodecode.lib.exceptions import UsersGroupsAssignedException
48 45 from rhodecode.lib.compat import json
49 46
50 47 from rhodecode.model.meta import Base, Session
51 48 from rhodecode.model.caching_query import FromCache
52 49
53 50
54 51 log = logging.getLogger(__name__)
55 52
56 53 #==============================================================================
57 54 # BASE CLASSES
58 55 #==============================================================================
59 56
60 57 class ModelSerializer(json.JSONEncoder):
61 58 """
62 59 Simple Serializer for JSON,
63
60
64 61 usage::
65
62
66 63 to make object customized for serialization implement a __json__
67 64 method that will return a dict for serialization into json
68
65
69 66 example::
70
67
71 68 class Task(object):
72
69
73 70 def __init__(self, name, value):
74 71 self.name = name
75 72 self.value = value
76
73
77 74 def __json__(self):
78 75 return dict(name=self.name,
79 value=self.value)
80
76 value=self.value)
77
81 78 """
82 79
83 80 def default(self, obj):
84 81
85 82 if hasattr(obj, '__json__'):
86 83 return obj.__json__()
87 84 else:
88 85 return json.JSONEncoder.default(self, obj)
89 86
90 87 class BaseModel(object):
91 88 """Base Model for all classess
92 89
93 90 """
94 91
95 92 @classmethod
96 93 def _get_keys(cls):
97 94 """return column names for this model """
98 95 return class_mapper(cls).c.keys()
99 96
100 97 def get_dict(self):
101 98 """return dict with keys and values corresponding
102 99 to this model data """
103 100
104 101 d = {}
105 102 for k in self._get_keys():
106 103 d[k] = getattr(self, k)
107 104 return d
108 105
109 106 def get_appstruct(self):
110 107 """return list with keys and values tupples corresponding
111 108 to this model data """
112 109
113 110 l = []
114 111 for k in self._get_keys():
115 112 l.append((k, getattr(self, k),))
116 113 return l
117 114
118 115 def populate_obj(self, populate_dict):
119 116 """populate model with data from given populate_dict"""
120 117
121 118 for k in self._get_keys():
122 119 if k in populate_dict:
123 120 setattr(self, k, populate_dict[k])
124 121
125 122 @classmethod
126 123 def query(cls):
127 124 return Session.query(cls)
128 125
129 126 @classmethod
130 127 def get(cls, id_):
131 128 if id_:
132 return Session.query(cls).get(id_)
129 return cls.query().get(id_)
130
131 @classmethod
132 def getAll(cls):
133 return cls.query().all()
133 134
134 135 @classmethod
135 136 def delete(cls, id_):
136 obj = Session.query(cls).get(id_)
137 obj = cls.query().get(id_)
137 138 Session.delete(obj)
138 139 Session.commit()
139 140
140 141
141 142 class RhodeCodeSettings(Base, BaseModel):
142 143 __tablename__ = 'rhodecode_settings'
143 144 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
144 145 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
145 146 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
146 147 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
147 148
148 149 def __init__(self, k='', v=''):
149 150 self.app_settings_name = k
150 151 self.app_settings_value = v
151 152
152 153
153 154 @validates('_app_settings_value')
154 155 def validate_settings_value(self, key, val):
155 156 assert type(val) == unicode
156 157 return val
157 158
158 159 @hybrid_property
159 160 def app_settings_value(self):
160 161 v = self._app_settings_value
161 162 if v == 'ldap_active':
162 163 v = str2bool(v)
163 return v
164 return v
164 165
165 166 @app_settings_value.setter
166 def app_settings_value(self,val):
167 def app_settings_value(self, val):
167 168 """
168 169 Setter that will always make sure we use unicode in app_settings_value
169
170
170 171 :param val:
171 172 """
172 173 self._app_settings_value = safe_unicode(val)
173 174
174 175 def __repr__(self):
175 176 return "<%s('%s:%s')>" % (self.__class__.__name__,
176 177 self.app_settings_name, self.app_settings_value)
177 178
178 179
179 180 @classmethod
180 181 def get_by_name(cls, ldap_key):
181 return Session.query(cls)\
182 return cls.query()\
182 183 .filter(cls.app_settings_name == ldap_key).scalar()
183 184
184 185 @classmethod
185 186 def get_app_settings(cls, cache=False):
186 187
187 ret = Session.query(cls)
188 ret = cls.query()
188 189
189 190 if cache:
190 191 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
191 192
192 193 if not ret:
193 194 raise Exception('Could not get application settings !')
194 195 settings = {}
195 196 for each in ret:
196 197 settings['rhodecode_' + each.app_settings_name] = \
197 198 each.app_settings_value
198 199
199 200 return settings
200 201
201 202 @classmethod
202 203 def get_ldap_settings(cls, cache=False):
203 ret = Session.query(cls)\
204 ret = cls.query()\
204 205 .filter(cls.app_settings_name.startswith('ldap_')).all()
205 206 fd = {}
206 207 for row in ret:
207 208 fd.update({row.app_settings_name:row.app_settings_value})
208 209
209 210 return fd
210 211
211 212
212 213 class RhodeCodeUi(Base, BaseModel):
213 214 __tablename__ = 'rhodecode_ui'
214 215 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
215 216
216 217 HOOK_UPDATE = 'changegroup.update'
217 218 HOOK_REPO_SIZE = 'changegroup.repo_size'
218 219 HOOK_PUSH = 'pretxnchangegroup.push_logger'
219 220 HOOK_PULL = 'preoutgoing.pull_logger'
220 221
221 222 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
222 223 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
223 224 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
224 225 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
225 226 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
226 227
227 228
228 229 @classmethod
229 230 def get_by_key(cls, key):
230 return Session.query(cls).filter(cls.ui_key == key)
231 return cls.query().filter(cls.ui_key == key)
231 232
232 233
233 234 @classmethod
234 235 def get_builtin_hooks(cls):
235 236 q = cls.query()
236 237 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
237 238 cls.HOOK_REPO_SIZE,
238 239 cls.HOOK_PUSH, cls.HOOK_PULL]))
239 240 return q.all()
240 241
241 242 @classmethod
242 243 def get_custom_hooks(cls):
243 244 q = cls.query()
244 245 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
245 246 cls.HOOK_REPO_SIZE,
246 247 cls.HOOK_PUSH, cls.HOOK_PULL]))
247 248 q = q.filter(cls.ui_section == 'hooks')
248 249 return q.all()
249 250
250 251 @classmethod
251 252 def create_or_update_hook(cls, key, val):
252 253 new_ui = cls.get_by_key(key).scalar() or cls()
253 254 new_ui.ui_section = 'hooks'
254 255 new_ui.ui_active = True
255 256 new_ui.ui_key = key
256 257 new_ui.ui_value = val
257 258
258 259 Session.add(new_ui)
259 260 Session.commit()
260 261
261 262
262 263 class User(Base, BaseModel):
263 264 __tablename__ = 'users'
264 265 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
265 266 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
266 267 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
267 268 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 269 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
269 270 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
270 271 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 272 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 273 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 274 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
274 275 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
275 276 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
276 277
277 278 user_log = relationship('UserLog', cascade='all')
278 279 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
279 280
280 281 repositories = relationship('Repository')
281 282 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
282 283 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
283 284
284 285 group_member = relationship('UsersGroupMember', cascade='all')
285 286
286 287 @property
287 288 def full_contact(self):
288 289 return '%s %s <%s>' % (self.name, self.lastname, self.email)
289 290
290 291 @property
291 292 def short_contact(self):
292 293 return '%s %s' % (self.name, self.lastname)
293 294
294 295 @property
295 296 def is_admin(self):
296 297 return self.admin
297 298
298 299 def __repr__(self):
299 300 try:
300 301 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
301 302 self.user_id, self.username)
302 303 except:
303 304 return self.__class__.__name__
304 305
305 306 @classmethod
306 307 def get_by_username(cls, username, case_insensitive=False):
307 308 if case_insensitive:
308 309 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
309 310 else:
310 311 return Session.query(cls).filter(cls.username == username).scalar()
311 312
312 313 @classmethod
313 314 def get_by_api_key(cls, api_key):
314 return Session.query(cls).filter(cls.api_key == api_key).one()
315 return cls.query().filter(cls.api_key == api_key).one()
315 316
316 317 def update_lastlogin(self):
317 318 """Update user lastlogin"""
318 319
319 320 self.last_login = datetime.datetime.now()
320 321 Session.add(self)
321 322 Session.commit()
322 323 log.debug('updated user %s lastlogin', self.username)
323 324
324 325 @classmethod
325 326 def create(cls, form_data):
326 327 from rhodecode.lib.auth import get_crypt_password
327 328
328 329 try:
329 330 new_user = cls()
330 331 for k, v in form_data.items():
331 332 if k == 'password':
332 333 v = get_crypt_password(v)
333 334 setattr(new_user, k, v)
334 335
335 336 new_user.api_key = generate_api_key(form_data['username'])
336 337 Session.add(new_user)
337 338 Session.commit()
338 339 return new_user
339 340 except:
340 341 log.error(traceback.format_exc())
341 342 Session.rollback()
342 343 raise
343 344
344 345 class UserLog(Base, BaseModel):
345 346 __tablename__ = 'user_logs'
346 347 __table_args__ = {'extend_existing':True}
347 348 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
348 349 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
349 350 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
350 351 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
351 352 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 353 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
353 354 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
354 355
355 356 @property
356 357 def action_as_day(self):
357 358 return date(*self.action_date.timetuple()[:3])
358 359
359 360 user = relationship('User')
360 361 repository = relationship('Repository')
361 362
362 363
363 364 class UsersGroup(Base, BaseModel):
364 365 __tablename__ = 'users_groups'
365 366 __table_args__ = {'extend_existing':True}
366 367
367 368 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
368 369 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
369 370 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
370 371
371 372 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
372 373
373 374 def __repr__(self):
374 375 return '<userGroup(%s)>' % (self.users_group_name)
375 376
376 377 @classmethod
377 378 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
378 379 if case_insensitive:
379 gr = Session.query(cls)\
380 .filter(cls.users_group_name.ilike(group_name))
380 gr = cls.query()\
381 .filter(cls.users_group_name.ilike(group_name))
381 382 else:
382 gr = Session.query(UsersGroup)\
383 .filter(UsersGroup.users_group_name == group_name)
383 gr = cls.query()\
384 .filter(cls.users_group_name == group_name)
384 385 if cache:
385 386 gr = gr.options(FromCache("sql_cache_short",
386 387 "get_user_%s" % group_name))
387 388 return gr.scalar()
388 389
389 390
390 391 @classmethod
391 392 def get(cls, users_group_id, cache=False):
392 users_group = Session.query(cls)
393 users_group = cls.query()
393 394 if cache:
394 395 users_group = users_group.options(FromCache("sql_cache_short",
395 396 "get_users_group_%s" % users_group_id))
396 397 return users_group.get(users_group_id)
397 398
398 399 @classmethod
399 400 def create(cls, form_data):
400 401 try:
401 402 new_users_group = cls()
402 403 for k, v in form_data.items():
403 404 setattr(new_users_group, k, v)
404 405
405 406 Session.add(new_users_group)
406 407 Session.commit()
407 408 return new_users_group
408 409 except:
409 410 log.error(traceback.format_exc())
410 411 Session.rollback()
411 412 raise
412 413
413 414 @classmethod
414 415 def update(cls, users_group_id, form_data):
415 416
416 417 try:
417 418 users_group = cls.get(users_group_id, cache=False)
418 419
419 420 for k, v in form_data.items():
420 421 if k == 'users_group_members':
421 422 users_group.members = []
422 423 Session.flush()
423 424 members_list = []
424 425 if v:
426 v = [v] if isinstance(v, basestring) else v
425 427 for u_id in set(v):
426 members_list.append(UsersGroupMember(
427 users_group_id,
428 u_id))
428 member = UsersGroupMember(users_group_id, u_id)
429 members_list.append(member)
429 430 setattr(users_group, 'members', members_list)
430 431 setattr(users_group, k, v)
431 432
432 433 Session.add(users_group)
433 434 Session.commit()
434 435 except:
435 436 log.error(traceback.format_exc())
436 437 Session.rollback()
437 438 raise
438 439
439 440 @classmethod
440 441 def delete(cls, users_group_id):
441 442 try:
442 443
443 444 # check if this group is not assigned to repo
444 445 assigned_groups = UsersGroupRepoToPerm.query()\
445 446 .filter(UsersGroupRepoToPerm.users_group_id ==
446 447 users_group_id).all()
447 448
448 449 if assigned_groups:
449 450 raise UsersGroupsAssignedException('Group assigned to %s' %
450 451 assigned_groups)
451 452
452 453 users_group = cls.get(users_group_id, cache=False)
453 454 Session.delete(users_group)
454 455 Session.commit()
455 456 except:
456 457 log.error(traceback.format_exc())
457 458 Session.rollback()
458 459 raise
459 460
460
461 461 class UsersGroupMember(Base, BaseModel):
462 462 __tablename__ = 'users_groups_members'
463 463 __table_args__ = {'extend_existing':True}
464 464
465 465 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
466 466 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
467 467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
468 468
469 469 user = relationship('User', lazy='joined')
470 470 users_group = relationship('UsersGroup')
471 471
472 472 def __init__(self, gr_id='', u_id=''):
473 473 self.users_group_id = gr_id
474 474 self.user_id = u_id
475 475
476 @staticmethod
477 def add_user_to_group(group, user):
478 ugm = UsersGroupMember()
479 ugm.users_group = group
480 ugm.user = user
481 Session.add(ugm)
482 Session.commit()
483 return ugm
484
476 485 class Repository(Base, BaseModel):
477 486 __tablename__ = 'repositories'
478 487 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
479 488
480 489 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
481 490 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
482 491 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
483 492 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
484 493 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
485 494 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
486 495 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
487 496 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
488 497 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
489 498 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
490 499
491 500 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
492 501 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
493 502
494 503
495 504 user = relationship('User')
496 505 fork = relationship('Repository', remote_side=repo_id)
497 506 group = relationship('Group')
498 507 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
499 508 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
500 509 stats = relationship('Statistics', cascade='all', uselist=False)
501 510
502 511 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
503 512
504 513 logs = relationship('UserLog', cascade='all')
505 514
506 515 def __repr__(self):
507 516 return "<%s('%s:%s')>" % (self.__class__.__name__,
508 517 self.repo_id, self.repo_name)
509 518
510 519 @classmethod
511 520 def url_sep(cls):
512 521 return '/'
513
522
514 523 @classmethod
515 524 def get_by_repo_name(cls, repo_name):
516 525 q = Session.query(cls).filter(cls.repo_name == repo_name)
517
518 526 q = q.options(joinedload(Repository.fork))\
519 527 .options(joinedload(Repository.user))\
520 .options(joinedload(Repository.group))\
521
528 .options(joinedload(Repository.group))
522 529 return q.one()
523 530
524 531 @classmethod
525 532 def get_repo_forks(cls, repo_id):
526 return Session.query(cls).filter(Repository.fork_id == repo_id)
533 return cls.query().filter(Repository.fork_id == repo_id)
527 534
528 535 @classmethod
529 536 def base_path(cls):
530 537 """
531 538 Returns base path when all repos are stored
532
539
533 540 :param cls:
534 541 """
535 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
542 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
536 543 cls.url_sep())
537 544 q.options(FromCache("sql_cache_short", "repository_repo_path"))
538 545 return q.one().ui_value
539 546
540 547 @property
541 548 def just_name(self):
542 549 return self.repo_name.split(Repository.url_sep())[-1]
543 550
544 551 @property
545 552 def groups_with_parents(self):
546 553 groups = []
547 554 if self.group is None:
548 555 return groups
549 556
550 557 cur_gr = self.group
551 558 groups.insert(0, cur_gr)
552 559 while 1:
553 560 gr = getattr(cur_gr, 'parent_group', None)
554 561 cur_gr = cur_gr.parent_group
555 562 if gr is None:
556 563 break
557 564 groups.insert(0, gr)
558 565
559 566 return groups
560 567
561 568 @property
562 569 def groups_and_repo(self):
563 570 return self.groups_with_parents, self.just_name
564 571
565 572 @LazyProperty
566 573 def repo_path(self):
567 574 """
568 575 Returns base full path for that repository means where it actually
569 576 exists on a filesystem
570 577 """
571 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
578 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
572 579 Repository.url_sep())
573 580 q.options(FromCache("sql_cache_short", "repository_repo_path"))
574 581 return q.one().ui_value
575 582
576 583 @property
577 584 def repo_full_path(self):
578 585 p = [self.repo_path]
579 586 # we need to split the name by / since this is how we store the
580 587 # names in the database, but that eventually needs to be converted
581 588 # into a valid system path
582 589 p += self.repo_name.split(Repository.url_sep())
583 590 return os.path.join(*p)
584 591
585 592 def get_new_name(self, repo_name):
586 593 """
587 594 returns new full repository name based on assigned group and new new
588
595
589 596 :param group_name:
590 597 """
591 598 path_prefix = self.group.full_path_splitted if self.group else []
592 599 return Repository.url_sep().join(path_prefix + [repo_name])
593 600
594 601 @property
595 602 def _ui(self):
596 603 """
597 604 Creates an db based ui object for this repository
598 605 """
599 606 from mercurial import ui
600 607 from mercurial import config
601 608 baseui = ui.ui()
602 609
603 610 #clean the baseui object
604 611 baseui._ocfg = config.config()
605 612 baseui._ucfg = config.config()
606 613 baseui._tcfg = config.config()
607 614
608 615
609 ret = Session.query(RhodeCodeUi)\
616 ret = RhodeCodeUi.query()\
610 617 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
611 618
612 619 hg_ui = ret
613 620 for ui_ in hg_ui:
614 621 if ui_.ui_active:
615 622 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
616 623 ui_.ui_key, ui_.ui_value)
617 624 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
618 625
619 626 return baseui
620 627
621 628 @classmethod
622 629 def is_valid(cls, repo_name):
623 630 """
624 631 returns True if given repo name is a valid filesystem repository
625
632
626 633 @param cls:
627 634 @param repo_name:
628 635 """
629 636 from rhodecode.lib.utils import is_valid_repo
630 637
631 638 return is_valid_repo(repo_name, cls.base_path())
632 639
633 640
634 641 #==========================================================================
635 642 # SCM PROPERTIES
636 643 #==========================================================================
637 644
638 645 def get_changeset(self, rev):
639 646 return get_changeset_safe(self.scm_instance, rev)
640 647
641 648 @property
642 649 def tip(self):
643 650 return self.get_changeset('tip')
644 651
645 652 @property
646 653 def author(self):
647 654 return self.tip.author
648 655
649 656 @property
650 657 def last_change(self):
651 658 return self.scm_instance.last_change
652 659
653 660 #==========================================================================
654 661 # SCM CACHE INSTANCE
655 662 #==========================================================================
656 663
657 664 @property
658 665 def invalidate(self):
659 666 """
660 667 Returns Invalidation object if this repo should be invalidated
661 668 None otherwise. `cache_active = False` means that this cache
662 669 state is not valid and needs to be invalidated
663 670 """
664 return Session.query(CacheInvalidation)\
671 return CacheInvalidation.query()\
665 672 .filter(CacheInvalidation.cache_key == self.repo_name)\
666 673 .filter(CacheInvalidation.cache_active == False)\
667 674 .scalar()
668 675
669 676 def set_invalidate(self):
670 677 """
671 678 set a cache for invalidation for this instance
672 679 """
673 inv = Session.query(CacheInvalidation)\
680 inv = CacheInvalidation.query()\
674 681 .filter(CacheInvalidation.cache_key == self.repo_name)\
675 682 .scalar()
676 683
677 684 if inv is None:
678 685 inv = CacheInvalidation(self.repo_name)
679 686 inv.cache_active = True
680 687 Session.add(inv)
681 688 Session.commit()
682 689
683 690 @LazyProperty
684 691 def scm_instance(self):
685 692 return self.__get_instance()
686 693
687 694 @property
688 695 def scm_instance_cached(self):
689 696 @cache_region('long_term')
690 697 def _c(repo_name):
691 698 return self.__get_instance()
692 699
693 700 # TODO: remove this trick when beaker 1.6 is released
694 701 # and have fixed this issue with not supporting unicode keys
695 702 rn = safe_str(self.repo_name)
696 703
697 704 inv = self.invalidate
698 705 if inv is not None:
699 706 region_invalidate(_c, None, rn)
700 707 # update our cache
701 708 inv.cache_active = True
702 709 Session.add(inv)
703 710 Session.commit()
704 711
705 712 return _c(rn)
706 713
707 714 def __get_instance(self):
708 715
709 716 repo_full_path = self.repo_full_path
710 717
711 718 try:
712 719 alias = get_scm(repo_full_path)[0]
713 720 log.debug('Creating instance of %s repository', alias)
714 721 backend = get_backend(alias)
715 722 except VCSError:
716 723 log.error(traceback.format_exc())
717 724 log.error('Perhaps this repository is in db and not in '
718 725 'filesystem run rescan repositories with '
719 726 '"destroy old data " option from admin panel')
720 727 return
721 728
722 729 if alias == 'hg':
723 730
724 731 repo = backend(safe_str(repo_full_path), create=False,
725 732 baseui=self._ui)
726 733 #skip hidden web repository
727 734 if repo._get_hidden():
728 735 return
729 736 else:
730 737 repo = backend(repo_full_path, create=False)
731 738
732 739 return repo
733 740
734 741
735 742 class Group(Base, BaseModel):
736 743 __tablename__ = 'groups'
737 744 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
738 745 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
739 746 __mapper_args__ = {'order_by':'group_name'}
740 747
741 748 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
742 749 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
743 750 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
744 751 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
745 752
746 753 parent_group = relationship('Group', remote_side=group_id)
747 754
748 755
749 756 def __init__(self, group_name='', parent_group=None):
750 757 self.group_name = group_name
751 758 self.parent_group = parent_group
752 759
753 760 def __repr__(self):
754 761 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
755 762 self.group_name)
756 763
757 764 @classmethod
758 765 def groups_choices(cls):
759 766 from webhelpers.html import literal as _literal
760 767 repo_groups = [('', '')]
761 768 sep = ' &raquo; '
762 769 _name = lambda k: _literal(sep.join(k))
763 770
764 771 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
765 772 for x in cls.query().all()])
766
767 repo_groups = sorted(repo_groups,key=lambda t: t[1].split(sep)[0])
773
774 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
768 775 return repo_groups
769
776
770 777 @classmethod
771 778 def url_sep(cls):
772 779 return '/'
773 780
774 781 @classmethod
775 def get_by_group_name(cls, group_name):
776 return cls.query().filter(cls.group_name == group_name).scalar()
782 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
783 if case_insensitive:
784 gr = cls.query()\
785 .filter(cls.group_name.ilike(group_name))
786 else:
787 gr = cls.query()\
788 .filter(cls.group_name == group_name)
789 if cache:
790 gr = gr.options(FromCache("sql_cache_short",
791 "get_group_%s" % group_name))
792 return gr.scalar()
777 793
778 794 @property
779 795 def parents(self):
780 796 parents_recursion_limit = 5
781 797 groups = []
782 798 if self.parent_group is None:
783 799 return groups
784 800 cur_gr = self.parent_group
785 801 groups.insert(0, cur_gr)
786 802 cnt = 0
787 803 while 1:
788 804 cnt += 1
789 805 gr = getattr(cur_gr, 'parent_group', None)
790 806 cur_gr = cur_gr.parent_group
791 807 if gr is None:
792 808 break
793 809 if cnt == parents_recursion_limit:
794 810 # this will prevent accidental infinit loops
795 811 log.error('group nested more than %s' %
796 812 parents_recursion_limit)
797 813 break
798 814
799 815 groups.insert(0, gr)
800 816 return groups
801 817
802 818 @property
803 819 def children(self):
804 return Session.query(Group).filter(Group.parent_group == self)
820 return Group.query().filter(Group.parent_group == self)
805 821
806 822 @property
807 823 def name(self):
808 824 return self.group_name.split(Group.url_sep())[-1]
809 825
810 826 @property
811 827 def full_path(self):
812 828 return self.group_name
813 829
814 830 @property
815 831 def full_path_splitted(self):
816 832 return self.group_name.split(Group.url_sep())
817 833
818 834 @property
819 835 def repositories(self):
820 return Session.query(Repository).filter(Repository.group == self)
836 return Repository.query().filter(Repository.group == self)
821 837
822 838 @property
823 839 def repositories_recursive_count(self):
824 840 cnt = self.repositories.count()
825 841
826 842 def children_count(group):
827 843 cnt = 0
828 844 for child in group.children:
829 845 cnt += child.repositories.count()
830 846 cnt += children_count(child)
831 847 return cnt
832 848
833 849 return cnt + children_count(self)
834 850
835 851
836 852 def get_new_name(self, group_name):
837 853 """
838 854 returns new full group name based on parent and new name
839
855
840 856 :param group_name:
841 857 """
842 path_prefix = self.parent_group.full_path_splitted if self.parent_group else []
858 path_prefix = (self.parent_group.full_path_splitted if
859 self.parent_group else [])
843 860 return Group.url_sep().join(path_prefix + [group_name])
844 861
845 862
846 863 class Permission(Base, BaseModel):
847 864 __tablename__ = 'permissions'
848 865 __table_args__ = {'extend_existing':True}
849 866 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
850 867 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
851 868 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
852 869
853 870 def __repr__(self):
854 871 return "<%s('%s:%s')>" % (self.__class__.__name__,
855 872 self.permission_id, self.permission_name)
856 873
857 874 @classmethod
858 875 def get_by_key(cls, key):
859 return Session.query(cls).filter(cls.permission_name == key).scalar()
876 return cls.query().filter(cls.permission_name == key).scalar()
860 877
861 878 class RepoToPerm(Base, BaseModel):
862 879 __tablename__ = 'repo_to_perm'
863 880 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
864 881 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
865 882 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
866 883 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
867 884 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
868 885
869 886 user = relationship('User')
870 887 permission = relationship('Permission')
871 888 repository = relationship('Repository')
872 889
873 890 class UserToPerm(Base, BaseModel):
874 891 __tablename__ = 'user_to_perm'
875 892 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
876 893 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
877 894 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
878 895 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
879 896
880 897 user = relationship('User')
881 898 permission = relationship('Permission')
882 899
883 900 @classmethod
884 901 def has_perm(cls, user_id, perm):
885 902 if not isinstance(perm, Permission):
886 903 raise Exception('perm needs to be an instance of Permission class')
887 904
888 return Session.query(cls).filter(cls.user_id == user_id)\
905 return cls.query().filter(cls.user_id == user_id)\
889 906 .filter(cls.permission == perm).scalar() is not None
890 907
891 908 @classmethod
892 909 def grant_perm(cls, user_id, perm):
893 910 if not isinstance(perm, Permission):
894 911 raise Exception('perm needs to be an instance of Permission class')
895 912
896 913 new = cls()
897 914 new.user_id = user_id
898 915 new.permission = perm
899 916 try:
900 917 Session.add(new)
901 918 Session.commit()
902 919 except:
903 920 Session.rollback()
904 921
905 922
906 923 @classmethod
907 924 def revoke_perm(cls, user_id, perm):
908 925 if not isinstance(perm, Permission):
909 926 raise Exception('perm needs to be an instance of Permission class')
910 927
911 928 try:
912 Session.query(cls).filter(cls.user_id == user_id)\
929 cls.query().filter(cls.user_id == user_id)\
913 930 .filter(cls.permission == perm).delete()
914 931 Session.commit()
915 932 except:
916 933 Session.rollback()
917 934
918 935 class UsersGroupRepoToPerm(Base, BaseModel):
919 936 __tablename__ = 'users_group_repo_to_perm'
920 937 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
921 938 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
922 939 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
923 940 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
924 941 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
925 942
926 943 users_group = relationship('UsersGroup')
927 944 permission = relationship('Permission')
928 945 repository = relationship('Repository')
929 946
930 947 def __repr__(self):
931 948 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
932 949
933 950 class UsersGroupToPerm(Base, BaseModel):
934 951 __tablename__ = 'users_group_to_perm'
935 952 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
936 953 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
937 954 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
938 955
939 956 users_group = relationship('UsersGroup')
940 957 permission = relationship('Permission')
941 958
942 959
943 960 @classmethod
944 961 def has_perm(cls, users_group_id, perm):
945 962 if not isinstance(perm, Permission):
946 963 raise Exception('perm needs to be an instance of Permission class')
947 964
948 return Session.query(cls).filter(cls.users_group_id ==
965 return cls.query().filter(cls.users_group_id ==
949 966 users_group_id)\
950 967 .filter(cls.permission == perm)\
951 968 .scalar() is not None
952 969
953 970 @classmethod
954 971 def grant_perm(cls, users_group_id, perm):
955 972 if not isinstance(perm, Permission):
956 973 raise Exception('perm needs to be an instance of Permission class')
957 974
958 975 new = cls()
959 976 new.users_group_id = users_group_id
960 977 new.permission = perm
961 978 try:
962 979 Session.add(new)
963 980 Session.commit()
964 981 except:
965 982 Session.rollback()
966 983
967 984
968 985 @classmethod
969 986 def revoke_perm(cls, users_group_id, perm):
970 987 if not isinstance(perm, Permission):
971 988 raise Exception('perm needs to be an instance of Permission class')
972 989
973 990 try:
974 Session.query(cls).filter(cls.users_group_id == users_group_id)\
991 cls.query().filter(cls.users_group_id == users_group_id)\
975 992 .filter(cls.permission == perm).delete()
976 993 Session.commit()
977 994 except:
978 995 Session.rollback()
979 996
980 997
981 998 class GroupToPerm(Base, BaseModel):
982 999 __tablename__ = 'group_to_perm'
983 1000 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
984 1001
985 1002 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
986 1003 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
987 1004 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
988 1005 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
989 1006
990 1007 user = relationship('User')
991 1008 permission = relationship('Permission')
992 1009 group = relationship('Group')
993 1010
994 1011 class Statistics(Base, BaseModel):
995 1012 __tablename__ = 'statistics'
996 1013 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
997 1014 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
998 1015 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
999 1016 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1000 1017 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1001 1018 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1002 1019 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1003 1020
1004 1021 repository = relationship('Repository', single_parent=True)
1005 1022
1006 1023 class UserFollowing(Base, BaseModel):
1007 1024 __tablename__ = 'user_followings'
1008 1025 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1009 1026 UniqueConstraint('user_id', 'follows_user_id')
1010 1027 , {'extend_existing':True})
1011 1028
1012 1029 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1013 1030 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1014 1031 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1015 1032 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1016 1033 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1017 1034
1018 1035 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1019 1036
1020 1037 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1021 1038 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1022 1039
1023 1040
1024 1041 @classmethod
1025 1042 def get_repo_followers(cls, repo_id):
1026 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
1043 return cls.query().filter(cls.follows_repo_id == repo_id)
1027 1044
1028 1045 class CacheInvalidation(Base, BaseModel):
1029 1046 __tablename__ = 'cache_invalidation'
1030 1047 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1031 1048 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1032 1049 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1033 1050 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1034 1051 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1035 1052
1036 1053
1037 1054 def __init__(self, cache_key, cache_args=''):
1038 1055 self.cache_key = cache_key
1039 1056 self.cache_args = cache_args
1040 1057 self.cache_active = False
1041 1058
1042 1059 def __repr__(self):
1043 1060 return "<%s('%s:%s')>" % (self.__class__.__name__,
1044 1061 self.cache_id, self.cache_key)
1045 1062
1046 1063 class DbMigrateVersion(Base, BaseModel):
1047 1064 __tablename__ = 'db_migrate_version'
1048 1065 __table_args__ = {'extend_existing':True}
1049 1066 repository_id = Column('repository_id', String(250), primary_key=True)
1050 1067 repository_path = Column('repository_path', Text)
1051 1068 version = Column('version', Integer)
1069
@@ -1,688 +1,694 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import os
23 23 import re
24 24 import logging
25 25 import traceback
26 26
27 27 import formencode
28 28 from formencode import All
29 29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 30 Email, Bool, StringBoolean, Set
31 31
32 32 from pylons.i18n.translation import _
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34
35 35 from rhodecode.config.routing import ADMIN_PREFIX
36 36 from rhodecode.lib.utils import repo_name_slug
37 37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 38 from rhodecode.lib.exceptions import LdapImportError
39 39 from rhodecode.model.user import UserModel
40 40 from rhodecode.model.repo import RepoModel
41 41 from rhodecode.model.db import User, UsersGroup, Group
42 42 from rhodecode import BACKENDS
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46 #this is needed to translate the messages using _() in validators
47 47 class State_obj(object):
48 48 _ = staticmethod(_)
49 49
50 50 #==============================================================================
51 51 # VALIDATORS
52 52 #==============================================================================
53 53 class ValidAuthToken(formencode.validators.FancyValidator):
54 54 messages = {'invalid_token':_('Token mismatch')}
55 55
56 56 def validate_python(self, value, state):
57 57
58 58 if value != authentication_token():
59 59 raise formencode.Invalid(self.message('invalid_token', state,
60 60 search_number=value), value, state)
61 61
62 62 def ValidUsername(edit, old_data):
63 63 class _ValidUsername(formencode.validators.FancyValidator):
64 64
65 65 def validate_python(self, value, state):
66 66 if value in ['default', 'new_user']:
67 67 raise formencode.Invalid(_('Invalid username'), value, state)
68 68 #check if user is unique
69 69 old_un = None
70 70 if edit:
71 71 old_un = UserModel().get(old_data.get('user_id')).username
72 72
73 73 if old_un != value or not edit:
74 74 if User.get_by_username(value, case_insensitive=True):
75 75 raise formencode.Invalid(_('This username already '
76 76 'exists') , value, state)
77 77
78 78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
79 79 raise formencode.Invalid(_('Username may only contain '
80 80 'alphanumeric characters '
81 81 'underscores, periods or dashes '
82 82 'and must begin with alphanumeric '
83 83 'character'), value, state)
84 84
85 85 return _ValidUsername
86 86
87 87
88 88 def ValidUsersGroup(edit, old_data):
89 89
90 90 class _ValidUsersGroup(formencode.validators.FancyValidator):
91 91
92 92 def validate_python(self, value, state):
93 93 if value in ['default']:
94 94 raise formencode.Invalid(_('Invalid group name'), value, state)
95 95 #check if group is unique
96 96 old_ugname = None
97 97 if edit:
98 98 old_ugname = UsersGroup.get(
99 99 old_data.get('users_group_id')).users_group_name
100 100
101 101 if old_ugname != value or not edit:
102 102 if UsersGroup.get_by_group_name(value, cache=False,
103 103 case_insensitive=True):
104 104 raise formencode.Invalid(_('This users group '
105 105 'already exists') , value,
106 106 state)
107 107
108 108
109 109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
110 110 raise formencode.Invalid(_('Group name may only contain '
111 111 'alphanumeric characters '
112 112 'underscores, periods or dashes '
113 113 'and must begin with alphanumeric '
114 114 'character'), value, state)
115 115
116 116 return _ValidUsersGroup
117 117
118 118
119 119 def ValidReposGroup(edit, old_data):
120 120 class _ValidReposGroup(formencode.validators.FancyValidator):
121 121
122 122 def validate_python(self, value, state):
123 123 #TODO WRITE VALIDATIONS
124 124 group_name = value.get('group_name')
125 125 group_parent_id = int(value.get('group_parent_id') or -1)
126 126
127 127 # slugify repo group just in case :)
128 128 slug = repo_name_slug(group_name)
129 129
130 130 # check for parent of self
131 131 if edit and old_data['group_id'] == group_parent_id:
132 132 e_dict = {'group_parent_id':_('Cannot assign this group '
133 133 'as parent')}
134 134 raise formencode.Invalid('', value, state,
135 135 error_dict=e_dict)
136 136
137 137 old_gname = None
138 138 if edit:
139 139 old_gname = Group.get(
140 140 old_data.get('group_id')).group_name
141 141
142 142 if old_gname != group_name or not edit:
143 143 # check filesystem
144 144 gr = Group.query().filter(Group.group_name == slug)\
145 145 .filter(Group.group_parent_id == group_parent_id).scalar()
146 146
147 147 if gr:
148 148 e_dict = {'group_name':_('This group already exists')}
149 149 raise formencode.Invalid('', value, state,
150 150 error_dict=e_dict)
151 151
152 152 return _ValidReposGroup
153 153
154 154 class ValidPassword(formencode.validators.FancyValidator):
155 155
156 156 def to_python(self, value, state):
157 157
158 158 if value:
159 159
160 160 if value.get('password'):
161 161 try:
162 162 value['password'] = get_crypt_password(value['password'])
163 163 except UnicodeEncodeError:
164 164 e_dict = {'password':_('Invalid characters in password')}
165 165 raise formencode.Invalid('', value, state, error_dict=e_dict)
166 166
167 167 if value.get('password_confirmation'):
168 168 try:
169 169 value['password_confirmation'] = \
170 170 get_crypt_password(value['password_confirmation'])
171 171 except UnicodeEncodeError:
172 172 e_dict = {'password_confirmation':_('Invalid characters in password')}
173 173 raise formencode.Invalid('', value, state, error_dict=e_dict)
174 174
175 175 if value.get('new_password'):
176 176 try:
177 177 value['new_password'] = \
178 178 get_crypt_password(value['new_password'])
179 179 except UnicodeEncodeError:
180 180 e_dict = {'new_password':_('Invalid characters in password')}
181 181 raise formencode.Invalid('', value, state, error_dict=e_dict)
182 182
183 183 return value
184 184
185 185 class ValidPasswordsMatch(formencode.validators.FancyValidator):
186 186
187 187 def validate_python(self, value, state):
188
189 if value['password'] != value['password_confirmation']:
188
189 pass_val = value.get('password') or value.get('new_password')
190 if pass_val != value['password_confirmation']:
190 191 e_dict = {'password_confirmation':
191 192 _('Passwords do not match')}
192 193 raise formencode.Invalid('', value, state, error_dict=e_dict)
193 194
194 195 class ValidAuth(formencode.validators.FancyValidator):
195 196 messages = {
196 'invalid_password':_('invalid password'),
197 'invalid_login':_('invalid user name'),
198 'disabled_account':_('Your account is disabled')
199
200 }
201 #error mapping
197 'invalid_password':_('invalid password'),
198 'invalid_login':_('invalid user name'),
199 'disabled_account':_('Your account is disabled')
200 }
201
202 # error mapping
202 203 e_dict = {'username':messages['invalid_login'],
203 204 'password':messages['invalid_password']}
204 205 e_dict_disable = {'username':messages['disabled_account']}
205 206
206 207 def validate_python(self, value, state):
207 208 password = value['password']
208 209 username = value['username']
209 210 user = User.get_by_username(username)
210 211
211 212 if authenticate(username, password):
212 213 return value
213 214 else:
214 215 if user and user.active is False:
215 216 log.warning('user %s is disabled', username)
216 217 raise formencode.Invalid(self.message('disabled_account',
217 218 state=State_obj),
218 219 value, state,
219 220 error_dict=self.e_dict_disable)
220 221 else:
221 222 log.warning('user %s not authenticated', username)
222 223 raise formencode.Invalid(self.message('invalid_password',
223 224 state=State_obj), value, state,
224 225 error_dict=self.e_dict)
225 226
226 227 class ValidRepoUser(formencode.validators.FancyValidator):
227 228
228 229 def to_python(self, value, state):
229 230 try:
230 231 User.query().filter(User.active == True)\
231 232 .filter(User.username == value).one()
232 233 except Exception:
233 234 raise formencode.Invalid(_('This username is not valid'),
234 235 value, state)
235 236 return value
236 237
237 238 def ValidRepoName(edit, old_data):
238 239 class _ValidRepoName(formencode.validators.FancyValidator):
239 240 def to_python(self, value, state):
240 241
241 242 repo_name = value.get('repo_name')
242 243
243 244 slug = repo_name_slug(repo_name)
244 245 if slug in [ADMIN_PREFIX, '']:
245 246 e_dict = {'repo_name': _('This repository name is disallowed')}
246 247 raise formencode.Invalid('', value, state, error_dict=e_dict)
247 248
248 249
249 250 if value.get('repo_group'):
250 251 gr = Group.get(value.get('repo_group'))
251 252 group_path = gr.full_path
252 253 # value needs to be aware of group name in order to check
253 254 # db key This is an actual just the name to store in the
254 255 # database
255 256 repo_name_full = group_path + Group.url_sep() + repo_name
257
256 258 else:
257 259 group_path = ''
258 260 repo_name_full = repo_name
259 261
260 262
261 263 value['repo_name_full'] = repo_name_full
262 264 rename = old_data.get('repo_name') != repo_name_full
263 265 create = not edit
264 266 if rename or create:
265 267
266 268 if group_path != '':
267 269 if RepoModel().get_by_repo_name(repo_name_full,):
268 270 e_dict = {'repo_name':_('This repository already '
269 271 'exists in a group "%s"') %
270 272 gr.group_name}
271 273 raise formencode.Invalid('', value, state,
272 274 error_dict=e_dict)
273 275 elif Group.get_by_group_name(repo_name_full):
274 276 e_dict = {'repo_name':_('There is a group with this'
275 277 ' name already "%s"') %
276 278 repo_name_full}
277 279 raise formencode.Invalid('', value, state,
278 280 error_dict=e_dict)
279 281
280 282 elif RepoModel().get_by_repo_name(repo_name_full):
281 283 e_dict = {'repo_name':_('This repository '
282 284 'already exists')}
283 285 raise formencode.Invalid('', value, state,
284 286 error_dict=e_dict)
285 287
286 288 return value
287 289
288 290 return _ValidRepoName
289 291
290 292 def ValidForkName():
291 293 class _ValidForkName(formencode.validators.FancyValidator):
292 294 def to_python(self, value, state):
293 295
294 296 repo_name = value.get('fork_name')
295 297
296 298 slug = repo_name_slug(repo_name)
297 299 if slug in [ADMIN_PREFIX, '']:
298 300 e_dict = {'repo_name': _('This repository name is disallowed')}
299 301 raise formencode.Invalid('', value, state, error_dict=e_dict)
300 302
301 303 if RepoModel().get_by_repo_name(repo_name):
302 304 e_dict = {'fork_name':_('This repository '
303 305 'already exists')}
304 306 raise formencode.Invalid('', value, state,
305 307 error_dict=e_dict)
306 308 return value
307 309 return _ValidForkName
308 310
309 311
310 312 def SlugifyName():
311 313 class _SlugifyName(formencode.validators.FancyValidator):
312 314
313 315 def to_python(self, value, state):
314 316 return repo_name_slug(value)
315 317
316 318 return _SlugifyName
317 319
318 320 def ValidCloneUri():
319 321 from mercurial.httprepo import httprepository, httpsrepository
320 322 from rhodecode.lib.utils import make_ui
321 323
322 324 class _ValidCloneUri(formencode.validators.FancyValidator):
323 325
324 326 def to_python(self, value, state):
325 327 if not value:
326 328 pass
327 329 elif value.startswith('https'):
328 330 try:
329 331 httpsrepository(make_ui('db'), value).capabilities
330 332 except Exception, e:
331 333 log.error(traceback.format_exc())
332 334 raise formencode.Invalid(_('invalid clone url'), value,
333 335 state)
334 336 elif value.startswith('http'):
335 337 try:
336 338 httprepository(make_ui('db'), value).capabilities
337 339 except Exception, e:
338 340 log.error(traceback.format_exc())
339 341 raise formencode.Invalid(_('invalid clone url'), value,
340 342 state)
341 343 else:
342 344 raise formencode.Invalid(_('Invalid clone url, provide a '
343 345 'valid clone http\s url'), value,
344 346 state)
345 347 return value
346 348
347 349 return _ValidCloneUri
348 350
349 351 def ValidForkType(old_data):
350 352 class _ValidForkType(formencode.validators.FancyValidator):
351 353
352 354 def to_python(self, value, state):
353 355 if old_data['repo_type'] != value:
354 356 raise formencode.Invalid(_('Fork have to be the same '
355 357 'type as original'), value, state)
356 358
357 359 return value
358 360 return _ValidForkType
359 361
360 362 class ValidPerms(formencode.validators.FancyValidator):
361 363 messages = {'perm_new_member_name':_('This username or users group name'
362 364 ' is not valid')}
363 365
364 366 def to_python(self, value, state):
365 367 perms_update = []
366 368 perms_new = []
367 369 #build a list of permission to update and new permission to create
368 370 for k, v in value.items():
369 371 #means new added member to permissions
370 372 if k.startswith('perm_new_member'):
371 373 new_perm = value.get('perm_new_member', False)
372 374 new_member = value.get('perm_new_member_name', False)
373 375 new_type = value.get('perm_new_member_type')
374 376
375 377 if new_member and new_perm:
376 378 if (new_member, new_perm, new_type) not in perms_new:
377 379 perms_new.append((new_member, new_perm, new_type))
378 380 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
379 381 member = k[7:]
380 382 t = {'u':'user',
381 383 'g':'users_group'}[k[0]]
382 384 if member == 'default':
383 385 if value['private']:
384 386 #set none for default when updating to private repo
385 387 v = 'repository.none'
386 388 perms_update.append((member, v, t))
387 389
388 390 value['perms_updates'] = perms_update
389 391 value['perms_new'] = perms_new
390 392
391 393 #update permissions
392 394 for k, v, t in perms_new:
393 395 try:
394 396 if t is 'user':
395 397 self.user_db = User.query()\
396 398 .filter(User.active == True)\
397 399 .filter(User.username == k).one()
398 400 if t is 'users_group':
399 401 self.user_db = UsersGroup.query()\
400 402 .filter(UsersGroup.users_group_active == True)\
401 403 .filter(UsersGroup.users_group_name == k).one()
402 404
403 405 except Exception:
404 406 msg = self.message('perm_new_member_name',
405 407 state=State_obj)
406 408 raise formencode.Invalid(msg, value, state,
407 409 error_dict={'perm_new_member_name':msg})
408 410 return value
409 411
410 412 class ValidSettings(formencode.validators.FancyValidator):
411 413
412 414 def to_python(self, value, state):
413 415 #settings form can't edit user
414 416 if value.has_key('user'):
415 417 del['value']['user']
416 418
417 419 return value
418 420
419 421 class ValidPath(formencode.validators.FancyValidator):
420 422 def to_python(self, value, state):
421 423
422 424 if not os.path.isdir(value):
423 425 msg = _('This is not a valid path')
424 426 raise formencode.Invalid(msg, value, state,
425 427 error_dict={'paths_root_path':msg})
426 428 return value
427 429
428 430 def UniqSystemEmail(old_data):
429 431 class _UniqSystemEmail(formencode.validators.FancyValidator):
430 432 def to_python(self, value, state):
431 433 value = value.lower()
432 434 if old_data.get('email') != value:
433 435 user = User.query().filter(User.email == value).scalar()
434 436 if user:
435 437 raise formencode.Invalid(
436 438 _("This e-mail address is already taken"),
437 439 value, state)
438 440 return value
439 441
440 442 return _UniqSystemEmail
441 443
442 444 class ValidSystemEmail(formencode.validators.FancyValidator):
443 445 def to_python(self, value, state):
444 446 value = value.lower()
445 447 user = User.query().filter(User.email == value).scalar()
446 448 if user is None:
447 449 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
448 450 value, state)
449 451
450 452 return value
451 453
452 454 class LdapLibValidator(formencode.validators.FancyValidator):
453 455
454 456 def to_python(self, value, state):
455 457
456 458 try:
457 459 import ldap
458 460 except ImportError:
459 461 raise LdapImportError
460 462 return value
461 463
462 464 class AttrLoginValidator(formencode.validators.FancyValidator):
463 465
464 466 def to_python(self, value, state):
465 467
466 468 if not value or not isinstance(value, (str, unicode)):
467 469 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
468 470 "must be specified - this is the name "
469 471 "of the attribute that is equivalent "
470 472 "to 'username'"),
471 473 value, state)
472 474
473 475 return value
474 476
475 477 #===============================================================================
476 478 # FORMS
477 479 #===============================================================================
478 480 class LoginForm(formencode.Schema):
479 481 allow_extra_fields = True
480 482 filter_extra_fields = True
481 483 username = UnicodeString(
482 484 strip=True,
483 485 min=1,
484 486 not_empty=True,
485 487 messages={
486 488 'empty':_('Please enter a login'),
487 489 'tooShort':_('Enter a value %(min)i characters long or more')}
488 490 )
489 491
490 492 password = UnicodeString(
491 493 strip=True,
492 494 min=3,
493 495 not_empty=True,
494 496 messages={
495 497 'empty':_('Please enter a password'),
496 498 'tooShort':_('Enter %(min)i characters or more')}
497 499 )
498 500
499
500 #chained validators have access to all data
501 501 chained_validators = [ValidAuth]
502 502
503 503 def UserForm(edit=False, old_data={}):
504 504 class _UserForm(formencode.Schema):
505 505 allow_extra_fields = True
506 506 filter_extra_fields = True
507 507 username = All(UnicodeString(strip=True, min=1, not_empty=True),
508 508 ValidUsername(edit, old_data))
509 509 if edit:
510 510 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
511 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
511 512 admin = StringBoolean(if_missing=False)
512 513 else:
513 514 password = All(UnicodeString(strip=True, min=6, not_empty=True))
515 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
516
514 517 active = StringBoolean(if_missing=False)
515 518 name = UnicodeString(strip=True, min=1, not_empty=True)
516 519 lastname = UnicodeString(strip=True, min=1, not_empty=True)
517 520 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
518 521
519 chained_validators = [ValidPassword]
522 chained_validators = [ValidPasswordsMatch, ValidPassword]
520 523
521 524 return _UserForm
522 525
523 526
524 527 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
525 528 class _UsersGroupForm(formencode.Schema):
526 529 allow_extra_fields = True
527 530 filter_extra_fields = True
528 531
529 532 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
530 533 ValidUsersGroup(edit, old_data))
531 534
532 535 users_group_active = StringBoolean(if_missing=False)
533 536
534 537 if edit:
535 538 users_group_members = OneOf(available_members, hideList=False,
536 539 testValueList=True,
537 540 if_missing=None, not_empty=False)
538 541
539 542 return _UsersGroupForm
540 543
541 544 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
542 545 class _ReposGroupForm(formencode.Schema):
543 546 allow_extra_fields = True
544 547 filter_extra_fields = True
545 548
546 549 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
547 550 SlugifyName())
548 551 group_description = UnicodeString(strip=True, min=1,
549 552 not_empty=True)
550 553 group_parent_id = OneOf(available_groups, hideList=False,
551 554 testValueList=True,
552 555 if_missing=None, not_empty=False)
553 556
554 557 chained_validators = [ValidReposGroup(edit, old_data)]
555 558
556 559 return _ReposGroupForm
557 560
558 561 def RegisterForm(edit=False, old_data={}):
559 562 class _RegisterForm(formencode.Schema):
560 563 allow_extra_fields = True
561 564 filter_extra_fields = True
562 565 username = All(ValidUsername(edit, old_data),
563 566 UnicodeString(strip=True, min=1, not_empty=True))
564 567 password = All(UnicodeString(strip=True, min=6, not_empty=True))
565 568 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
566 569 active = StringBoolean(if_missing=False)
567 570 name = UnicodeString(strip=True, min=1, not_empty=True)
568 571 lastname = UnicodeString(strip=True, min=1, not_empty=True)
569 572 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
570 573
571 574 chained_validators = [ValidPasswordsMatch, ValidPassword]
572 575
573 576 return _RegisterForm
574 577
575 578 def PasswordResetForm():
576 579 class _PasswordResetForm(formencode.Schema):
577 580 allow_extra_fields = True
578 581 filter_extra_fields = True
579 582 email = All(ValidSystemEmail(), Email(not_empty=True))
580 583 return _PasswordResetForm
581 584
582 585 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
583 586 repo_groups=[]):
584 587 class _RepoForm(formencode.Schema):
585 588 allow_extra_fields = True
586 589 filter_extra_fields = False
587 590 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
588 591 SlugifyName())
589 592 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
590 593 ValidCloneUri()())
591 594 repo_group = OneOf(repo_groups, hideList=True)
592 595 repo_type = OneOf(supported_backends)
593 596 description = UnicodeString(strip=True, min=1, not_empty=True)
594 597 private = StringBoolean(if_missing=False)
595 598 enable_statistics = StringBoolean(if_missing=False)
596 599 enable_downloads = StringBoolean(if_missing=False)
597 600
598 601 if edit:
599 602 #this is repo owner
600 603 user = All(UnicodeString(not_empty=True), ValidRepoUser)
601 604
602 605 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
603 606 return _RepoForm
604 607
605 608 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
606 609 class _RepoForkForm(formencode.Schema):
607 610 allow_extra_fields = True
608 611 filter_extra_fields = False
609 612 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
610 613 SlugifyName())
611 614 description = UnicodeString(strip=True, min=1, not_empty=True)
612 615 private = StringBoolean(if_missing=False)
613 616 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
614 617
615 618 chained_validators = [ValidForkName()]
616 619
617 620 return _RepoForkForm
618 621
619 def RepoSettingsForm(edit=False, old_data={}):
622 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
623 repo_groups=[]):
620 624 class _RepoForm(formencode.Schema):
621 625 allow_extra_fields = True
622 626 filter_extra_fields = False
623 627 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
624 628 SlugifyName())
625 629 description = UnicodeString(strip=True, min=1, not_empty=True)
630 repo_group = OneOf(repo_groups, hideList=True)
626 631 private = StringBoolean(if_missing=False)
627 632
628 chained_validators = [ValidRepoName(edit, old_data), ValidPerms, ValidSettings]
633 chained_validators = [ValidRepoName(edit, old_data), ValidPerms,
634 ValidSettings]
629 635 return _RepoForm
630 636
631 637
632 638 def ApplicationSettingsForm():
633 639 class _ApplicationSettingsForm(formencode.Schema):
634 640 allow_extra_fields = True
635 641 filter_extra_fields = False
636 642 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
637 643 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
638 644 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
639 645
640 646 return _ApplicationSettingsForm
641 647
642 648 def ApplicationUiSettingsForm():
643 649 class _ApplicationUiSettingsForm(formencode.Schema):
644 650 allow_extra_fields = True
645 651 filter_extra_fields = False
646 652 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
647 653 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
648 654 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
649 655 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
650 656 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
651 657 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
652 658
653 659 return _ApplicationUiSettingsForm
654 660
655 661 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
656 662 class _DefaultPermissionsForm(formencode.Schema):
657 663 allow_extra_fields = True
658 664 filter_extra_fields = True
659 665 overwrite_default = StringBoolean(if_missing=False)
660 666 anonymous = OneOf(['True', 'False'], if_missing=False)
661 667 default_perm = OneOf(perms_choices)
662 668 default_register = OneOf(register_choices)
663 669 default_create = OneOf(create_choices)
664 670
665 671 return _DefaultPermissionsForm
666 672
667 673
668 674 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
669 675 class _LdapSettingsForm(formencode.Schema):
670 676 allow_extra_fields = True
671 677 filter_extra_fields = True
672 678 pre_validators = [LdapLibValidator]
673 679 ldap_active = StringBoolean(if_missing=False)
674 680 ldap_host = UnicodeString(strip=True,)
675 681 ldap_port = Number(strip=True,)
676 682 ldap_tls_kind = OneOf(tls_kind_choices)
677 683 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
678 684 ldap_dn_user = UnicodeString(strip=True,)
679 685 ldap_dn_pass = UnicodeString(strip=True,)
680 686 ldap_base_dn = UnicodeString(strip=True,)
681 687 ldap_filter = UnicodeString(strip=True,)
682 688 ldap_search_scope = OneOf(search_scope_choices)
683 689 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
684 690 ldap_attr_firstname = UnicodeString(strip=True,)
685 691 ldap_attr_lastname = UnicodeString(strip=True,)
686 692 ldap_attr_email = UnicodeString(strip=True,)
687 693
688 694 return _LdapSettingsForm
@@ -1,370 +1,411 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.repo
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository model for rhodecode
7 7
8 8 :created_on: Jun 5, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import shutil
27 27 import logging
28 28 import traceback
29 29 from datetime import datetime
30 30
31 31 from sqlalchemy.orm import joinedload, make_transient
32 32
33 33 from vcs.utils.lazy import LazyProperty
34 34 from vcs.backends import get_backend
35 35
36 36 from rhodecode.lib import safe_str
37 37
38 38 from rhodecode.model import BaseModel
39 39 from rhodecode.model.caching_query import FromCache
40 40 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
41 41 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
42 42 from rhodecode.model.user import UserModel
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class RepoModel(BaseModel):
48 48
49 49 @LazyProperty
50 50 def repos_path(self):
51 51 """Get's the repositories root path from database
52 52 """
53 53
54 54 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
55 55 return q.ui_value
56 56
57 57 def get(self, repo_id, cache=False):
58 58 repo = self.sa.query(Repository)\
59 59 .filter(Repository.repo_id == repo_id)
60 60
61 61 if cache:
62 62 repo = repo.options(FromCache("sql_cache_short",
63 63 "get_repo_%s" % repo_id))
64 64 return repo.scalar()
65 65
66 66 def get_by_repo_name(self, repo_name, cache=False):
67 67 repo = self.sa.query(Repository)\
68 68 .filter(Repository.repo_name == repo_name)
69 69
70 70 if cache:
71 71 repo = repo.options(FromCache("sql_cache_short",
72 72 "get_repo_%s" % repo_name))
73 73 return repo.scalar()
74 74
75 75
76 76 def get_users_js(self):
77 77
78 78 users = self.sa.query(User).filter(User.active == True).all()
79 79 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
80 80 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
81 81 u.lastname, u.username)
82 82 for u in users])
83 83 return users_array
84 84
85 85 def get_users_groups_js(self):
86 86 users_groups = self.sa.query(UsersGroup)\
87 87 .filter(UsersGroup.users_group_active == True).all()
88 88
89 89 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
90 90
91 91 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
92 92 (gr.users_group_id, gr.users_group_name,
93 93 len(gr.members))
94 94 for gr in users_groups])
95 95 return users_groups_array
96 96
97 def _get_defaults(self, repo_name):
98 """
99 Get's information about repository, and returns a dict for
100 usage in forms
101
102 :param repo_name:
103 """
104
105 repo_info = Repository.get_by_repo_name(repo_name)
106
107 if repo_info is None:
108 return None
109
110 defaults = repo_info.get_dict()
111 group, repo_name = repo_info.groups_and_repo
112 defaults['repo_name'] = repo_name
113 defaults['repo_group'] = getattr(group[-1] if group else None,
114 'group_id', None)
115
116 # fill owner
117 if repo_info.user:
118 defaults.update({'user': repo_info.user.username})
119 else:
120 replacement_user = User.query().filter(User.admin ==
121 True).first().username
122 defaults.update({'user': replacement_user})
123
124 # fill repository users
125 for p in repo_info.repo_to_perm:
126 defaults.update({'u_perm_%s' % p.user.username:
127 p.permission.permission_name})
128
129 # fill repository groups
130 for p in repo_info.users_group_to_perm:
131 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
132 p.permission.permission_name})
133
134 return defaults
135
136
97 137 def update(self, repo_name, form_data):
98 138 try:
99 139 cur_repo = self.get_by_repo_name(repo_name, cache=False)
100 140
101 141 # update permissions
102 142 for member, perm, member_type in form_data['perms_updates']:
103 143 if member_type == 'user':
104 144 r2p = self.sa.query(RepoToPerm)\
105 145 .filter(RepoToPerm.user == User.get_by_username(member))\
106 146 .filter(RepoToPerm.repository == cur_repo)\
107 147 .one()
108 148
109 149 r2p.permission = self.sa.query(Permission)\
110 150 .filter(Permission.permission_name ==
111 151 perm).scalar()
112 152 self.sa.add(r2p)
113 153 else:
114 154 g2p = self.sa.query(UsersGroupRepoToPerm)\
115 155 .filter(UsersGroupRepoToPerm.users_group ==
116 156 UsersGroup.get_by_group_name(member))\
117 157 .filter(UsersGroupRepoToPerm.repository ==
118 158 cur_repo).one()
119 159
120 160 g2p.permission = self.sa.query(Permission)\
121 161 .filter(Permission.permission_name ==
122 162 perm).scalar()
123 163 self.sa.add(g2p)
124 164
125 165 # set new permissions
126 166 for member, perm, member_type in form_data['perms_new']:
127 167 if member_type == 'user':
128 168 r2p = RepoToPerm()
129 169 r2p.repository = cur_repo
130 170 r2p.user = User.get_by_username(member)
131 171
132 172 r2p.permission = self.sa.query(Permission)\
133 173 .filter(Permission.
134 174 permission_name == perm)\
135 175 .scalar()
136 176 self.sa.add(r2p)
137 177 else:
138 178 g2p = UsersGroupRepoToPerm()
139 179 g2p.repository = cur_repo
140 180 g2p.users_group = UsersGroup.get_by_group_name(member)
141 181 g2p.permission = self.sa.query(Permission)\
142 182 .filter(Permission.
143 183 permission_name == perm)\
144 184 .scalar()
145 185 self.sa.add(g2p)
146 186
147 187 # update current repo
148 188 for k, v in form_data.items():
149 189 if k == 'user':
150 190 cur_repo.user = User.get_by_username(v)
151 191 elif k == 'repo_name':
152 192 pass
153 193 elif k == 'repo_group':
154 cur_repo.group_id = v
194 cur_repo.group = Group.get(v)
155 195
156 196 else:
157 197 setattr(cur_repo, k, v)
158 198
159 199 new_name = cur_repo.get_new_name(form_data['repo_name'])
160 200 cur_repo.repo_name = new_name
161 201
162 202 self.sa.add(cur_repo)
163 203
164 204 if repo_name != new_name:
165 205 # rename repository
166 206 self.__rename_repo(old=repo_name, new=new_name)
167 207
168 208 self.sa.commit()
169 209 return cur_repo
170 210 except:
171 211 log.error(traceback.format_exc())
172 212 self.sa.rollback()
173 213 raise
174 214
175 215 def create(self, form_data, cur_user, just_db=False, fork=False):
176 216
177 217 try:
178 218 if fork:
179 219 repo_name = form_data['fork_name']
180 220 org_name = form_data['repo_name']
181 221 org_full_name = org_name
182 222
183 223 else:
184 224 org_name = repo_name = form_data['repo_name']
185 225 repo_name_full = form_data['repo_name_full']
186 226
187 227 new_repo = Repository()
188 228 new_repo.enable_statistics = False
189 229 for k, v in form_data.items():
190 230 if k == 'repo_name':
191 231 if fork:
192 232 v = repo_name
193 233 else:
194 234 v = repo_name_full
195 235 if k == 'repo_group':
196 236 k = 'group_id'
197 237
198 238 if k == 'description':
199 239 v = v or repo_name
200 240
201 241 setattr(new_repo, k, v)
202 242
203 243 if fork:
204 244 parent_repo = self.sa.query(Repository)\
205 245 .filter(Repository.repo_name == org_full_name).one()
206 246 new_repo.fork = parent_repo
207 247
208 248 new_repo.user_id = cur_user.user_id
209 249 self.sa.add(new_repo)
210 250
211 251 #create default permission
212 252 repo_to_perm = RepoToPerm()
213 253 default = 'repository.read'
214 254 for p in User.get_by_username('default').user_perms:
215 255 if p.permission.permission_name.startswith('repository.'):
216 256 default = p.permission.permission_name
217 257 break
218 258
219 259 default_perm = 'repository.none' if form_data['private'] else default
220 260
221 261 repo_to_perm.permission_id = self.sa.query(Permission)\
222 262 .filter(Permission.permission_name == default_perm)\
223 263 .one().permission_id
224 264
225 265 repo_to_perm.repository = new_repo
226 266 repo_to_perm.user_id = User.get_by_username('default').user_id
227 267
228 268 self.sa.add(repo_to_perm)
229 269
230 270 if not just_db:
231 271 self.__create_repo(repo_name, form_data['repo_type'],
232 272 form_data['repo_group'],
233 273 form_data['clone_uri'])
234 274
235 275 self.sa.commit()
236 276
237 277 #now automatically start following this repository as owner
238 278 from rhodecode.model.scm import ScmModel
239 279 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
240 280 cur_user.user_id)
241 281 return new_repo
242 282 except:
243 283 log.error(traceback.format_exc())
244 284 self.sa.rollback()
245 285 raise
246 286
247 287 def create_fork(self, form_data, cur_user):
248 288 from rhodecode.lib.celerylib import tasks, run_task
249 289 run_task(tasks.create_repo_fork, form_data, cur_user)
250 290
251 291 def delete(self, repo):
252 292 try:
253 293 self.sa.delete(repo)
254 294 self.__delete_repo(repo)
255 295 self.sa.commit()
256 296 except:
257 297 log.error(traceback.format_exc())
258 298 self.sa.rollback()
259 299 raise
260 300
261 301 def delete_perm_user(self, form_data, repo_name):
262 302 try:
263 303 self.sa.query(RepoToPerm)\
264 304 .filter(RepoToPerm.repository \
265 305 == self.get_by_repo_name(repo_name))\
266 306 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
267 307 self.sa.commit()
268 308 except:
269 309 log.error(traceback.format_exc())
270 310 self.sa.rollback()
271 311 raise
272 312
273 313 def delete_perm_users_group(self, form_data, repo_name):
274 314 try:
275 315 self.sa.query(UsersGroupRepoToPerm)\
276 316 .filter(UsersGroupRepoToPerm.repository \
277 317 == self.get_by_repo_name(repo_name))\
278 318 .filter(UsersGroupRepoToPerm.users_group_id \
279 319 == form_data['users_group_id']).delete()
280 320 self.sa.commit()
281 321 except:
282 322 log.error(traceback.format_exc())
283 323 self.sa.rollback()
284 324 raise
285 325
286 326 def delete_stats(self, repo_name):
287 327 try:
288 328 self.sa.query(Statistics)\
289 329 .filter(Statistics.repository == \
290 330 self.get_by_repo_name(repo_name)).delete()
291 331 self.sa.commit()
292 332 except:
293 333 log.error(traceback.format_exc())
294 334 self.sa.rollback()
295 335 raise
296 336
297 337 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
298 338 """
299 339 makes repository on filesystem. It's group aware means it'll create
300 340 a repository within a group, and alter the paths accordingly of
301 341 group location
302 342
303 343 :param repo_name:
304 344 :param alias:
305 345 :param parent_id:
306 346 :param clone_uri:
307 347 """
308 from rhodecode.lib.utils import is_valid_repo,is_valid_repos_group
348 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
309 349
310 350 if new_parent_id:
311 351 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
312 352 new_parent_path = os.sep.join(paths)
313 353 else:
314 354 new_parent_path = ''
315 355
316 356 repo_path = os.path.join(*map(lambda x:safe_str(x),
317 357 [self.repos_path, new_parent_path, repo_name]))
318 358
319
359
320 360 # check if this path is not a repository
321 361 if is_valid_repo(repo_path, self.repos_path):
322 362 raise Exception('This path %s is a valid repository' % repo_path)
323 363
324 364 # check if this path is a group
325 365 if is_valid_repos_group(repo_path, self.repos_path):
326 366 raise Exception('This path %s is a valid group' % repo_path)
327
367
328 368 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
329 369 clone_uri)
330 370 backend = get_backend(alias)
331 371
332 372 backend(repo_path, create=True, src_url=clone_uri)
333 373
334 374
335 375 def __rename_repo(self, old, new):
336 376 """
337 377 renames repository on filesystem
338 378
339 379 :param old: old name
340 380 :param new: new name
341 381 """
342 382 log.info('renaming repo from %s to %s', old, new)
343 383
344 384 old_path = os.path.join(self.repos_path, old)
345 385 new_path = os.path.join(self.repos_path, new)
346 386 if os.path.isdir(new_path):
347 387 raise Exception('Was trying to rename to already existing dir %s' \
348 388 % new_path)
349 389 shutil.move(old_path, new_path)
350 390
351 391 def __delete_repo(self, repo):
352 392 """
353 393 removes repo from filesystem, the removal is acctually made by
354 394 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
355 395 repository is no longer valid for rhodecode, can be undeleted later on
356 396 by reverting the renames on this repository
357 397
358 398 :param repo: repo object
359 399 """
360 400 rm_path = os.path.join(self.repos_path, repo.repo_name)
361 401 log.info("Removing %s", rm_path)
362 402 #disable hg/git
363 403 alias = repo.repo_type
364 404 shutil.move(os.path.join(rm_path, '.%s' % alias),
365 405 os.path.join(rm_path, 'rm__.%s' % alias))
366 406 #disable repo
367 407 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
368 408 % (datetime.today()\
369 409 .strftime('%Y%m%d_%H%M%S_%f'),
370 410 repo.repo_name)))
411
@@ -1,164 +1,164 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users groups model for RhodeCode
7 7
8 8 :created_on: Jan 25, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import traceback
29 29 import shutil
30 30
31 31 from pylons.i18n.translation import _
32 32
33 33 from vcs.utils.lazy import LazyProperty
34 34
35 35 from rhodecode.model import BaseModel
36 36 from rhodecode.model.caching_query import FromCache
37 37 from rhodecode.model.db import Group, RhodeCodeUi
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class ReposGroupModel(BaseModel):
43 43
44 44 @LazyProperty
45 45 def repos_path(self):
46 46 """
47 47 Get's the repositories root path from database
48 48 """
49 49
50 50 q = RhodeCodeUi.get_by_key('/').one()
51 51 return q.ui_value
52 52
53 53 def __create_group(self, group_name):
54 54 """
55 55 makes repositories group on filesystem
56 56
57 57 :param repo_name:
58 58 :param parent_id:
59 59 """
60 60
61 61 create_path = os.path.join(self.repos_path, group_name)
62 62 log.debug('creating new group in %s', create_path)
63 63
64 64 if os.path.isdir(create_path):
65 65 raise Exception('That directory already exists !')
66 66
67 67 os.makedirs(create_path)
68 68
69 69 def __rename_group(self, old, new):
70 70 """
71 71 Renames a group on filesystem
72 72
73 73 :param group_name:
74 74 """
75 75
76 76 if old == new:
77 77 log.debug('skipping group rename')
78 78 return
79 79
80 80 log.debug('renaming repos group from %s to %s', old, new)
81 81
82 82
83 83 old_path = os.path.join(self.repos_path, old)
84 84 new_path = os.path.join(self.repos_path, new)
85 85
86 86 log.debug('renaming repos paths from %s to %s', old_path, new_path)
87 87
88 88 if os.path.isdir(new_path):
89 89 raise Exception('Was trying to rename to already '
90 90 'existing dir %s' % new_path)
91 91 shutil.move(old_path, new_path)
92 92
93 93 def __delete_group(self, group):
94 94 """
95 95 Deletes a group from a filesystem
96 96
97 97 :param group: instance of group from database
98 98 """
99 99 paths = group.full_path.split(Group.url_sep())
100 100 paths = os.sep.join(paths)
101 101
102 102 rm_path = os.path.join(self.repos_path, paths)
103 103 if os.path.isdir(rm_path):
104 104 # delete only if that path really exists
105 105 os.rmdir(rm_path)
106 106
107 107 def create(self, form_data):
108 108 try:
109 109 new_repos_group = Group()
110 110 new_repos_group.group_description = form_data['group_description']
111 111 new_repos_group.parent_group = Group.get(form_data['group_parent_id'])
112 112 new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name'])
113 113
114 114 self.sa.add(new_repos_group)
115 115
116 116 self.__create_group(new_repos_group.group_name)
117 117
118 118 self.sa.commit()
119 119 return new_repos_group
120 120 except:
121 121 log.error(traceback.format_exc())
122 122 self.sa.rollback()
123 123 raise
124 124
125 125 def update(self, repos_group_id, form_data):
126 126
127 127 try:
128 128 repos_group = Group.get(repos_group_id)
129 129 old_path = repos_group.full_path
130
131 #change properties
130
131 # change properties
132 132 repos_group.group_description = form_data['group_description']
133 133 repos_group.parent_group = Group.get(form_data['group_parent_id'])
134 134 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
135 135
136 136 new_path = repos_group.full_path
137 137
138 138 self.sa.add(repos_group)
139 139
140 140 self.__rename_group(old_path, new_path)
141 141
142 142 # we need to get all repositories from this new group and
143 143 # rename them accordingly to new group path
144 144 for r in repos_group.repositories:
145 145 r.repo_name = r.get_new_name(r.just_name)
146 146 self.sa.add(r)
147 147
148 148 self.sa.commit()
149 149 return repos_group
150 150 except:
151 151 log.error(traceback.format_exc())
152 152 self.sa.rollback()
153 153 raise
154 154
155 155 def delete(self, users_group_id):
156 156 try:
157 157 users_group = Group.get(users_group_id)
158 158 self.sa.delete(users_group)
159 159 self.__delete_group(users_group)
160 160 self.sa.commit()
161 161 except:
162 162 log.error(traceback.format_exc())
163 163 self.sa.rollback()
164 164 raise
@@ -1,388 +1,390 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 from pylons.i18n.translation import _
30 30
31 31 from rhodecode.lib import safe_unicode
32 32 from rhodecode.model import BaseModel
33 33 from rhodecode.model.caching_query import FromCache
34 34 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
35 35 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember
36 36 from rhodecode.lib.exceptions import DefaultUserException, \
37 37 UserOwnsReposException
38 38
39 39 from sqlalchemy.exc import DatabaseError
40 40 from rhodecode.lib import generate_api_key
41 41 from sqlalchemy.orm import joinedload
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45 PERM_WEIGHTS = {'repository.none': 0,
46 46 'repository.read': 1,
47 47 'repository.write': 3,
48 48 'repository.admin': 3}
49 49
50 50
51 51 class UserModel(BaseModel):
52
53 52 def get(self, user_id, cache=False):
54 53 user = self.sa.query(User)
55 54 if cache:
56 55 user = user.options(FromCache("sql_cache_short",
57 56 "get_user_%s" % user_id))
58 57 return user.get(user_id)
59 58
60 59 def get_by_username(self, username, cache=False, case_insensitive=False):
61 60
62 61 if case_insensitive:
63 62 user = self.sa.query(User).filter(User.username.ilike(username))
64 63 else:
65 64 user = self.sa.query(User)\
66 65 .filter(User.username == username)
67 66 if cache:
68 67 user = user.options(FromCache("sql_cache_short",
69 68 "get_user_%s" % username))
70 69 return user.scalar()
71 70
72 71 def get_by_api_key(self, api_key, cache=False):
73 72
74 73 user = self.sa.query(User)\
75 74 .filter(User.api_key == api_key)
76 75 if cache:
77 76 user = user.options(FromCache("sql_cache_short",
78 77 "get_user_%s" % api_key))
79 78 return user.scalar()
80 79
81 80 def create(self, form_data):
82 81 try:
83 82 new_user = User()
84 83 for k, v in form_data.items():
85 84 setattr(new_user, k, v)
86 85
87 86 new_user.api_key = generate_api_key(form_data['username'])
88 87 self.sa.add(new_user)
89 88 self.sa.commit()
89 return new_user
90 90 except:
91 91 log.error(traceback.format_exc())
92 92 self.sa.rollback()
93 93 raise
94 94
95 95 def create_ldap(self, username, password, user_dn, attrs):
96 96 """
97 97 Checks if user is in database, if not creates this user marked
98 98 as ldap user
99
99 100 :param username:
100 101 :param password:
101 102 :param user_dn:
102 103 :param attrs:
103 104 """
104 105 from rhodecode.lib.auth import get_crypt_password
105 106 log.debug('Checking for such ldap account in RhodeCode database')
106 107 if self.get_by_username(username, case_insensitive=True) is None:
107 108 try:
108 109 new_user = User()
109 110 # add ldap account always lowercase
110 111 new_user.username = username.lower()
111 112 new_user.password = get_crypt_password(password)
112 113 new_user.api_key = generate_api_key(username)
113 114 new_user.email = attrs['email']
114 115 new_user.active = True
115 116 new_user.ldap_dn = safe_unicode(user_dn)
116 117 new_user.name = attrs['name']
117 118 new_user.lastname = attrs['lastname']
118 119
119 120 self.sa.add(new_user)
120 121 self.sa.commit()
121 122 return True
122 123 except (DatabaseError,):
123 124 log.error(traceback.format_exc())
124 125 self.sa.rollback()
125 126 raise
126 127 log.debug('this %s user exists skipping creation of ldap account',
127 128 username)
128 129 return False
129 130
130 131 def create_registration(self, form_data):
131 132 from rhodecode.lib.celerylib import tasks, run_task
132 133 try:
133 134 new_user = User()
134 135 for k, v in form_data.items():
135 136 if k != 'admin':
136 137 setattr(new_user, k, v)
137 138
138 139 self.sa.add(new_user)
139 140 self.sa.commit()
140 141 body = ('New user registration\n'
141 142 'username: %s\n'
142 143 'email: %s\n')
143 144 body = body % (form_data['username'], form_data['email'])
144 145
145 146 run_task(tasks.send_email, None,
146 147 _('[RhodeCode] New User registration'),
147 148 body)
148 149 except:
149 150 log.error(traceback.format_exc())
150 151 self.sa.rollback()
151 152 raise
152 153
153 154 def update(self, user_id, form_data):
154 155 try:
155 156 user = self.get(user_id, cache=False)
156 157 if user.username == 'default':
157 158 raise DefaultUserException(
158 159 _("You can't Edit this user since it's"
159 160 " crucial for entire application"))
160 161
161 162 for k, v in form_data.items():
162 163 if k == 'new_password' and v != '':
163 164 user.password = v
164 165 user.api_key = generate_api_key(user.username)
165 166 else:
166 167 setattr(user, k, v)
167 168
168 169 self.sa.add(user)
169 170 self.sa.commit()
170 171 except:
171 172 log.error(traceback.format_exc())
172 173 self.sa.rollback()
173 174 raise
174 175
175 176 def update_my_account(self, user_id, form_data):
176 177 try:
177 178 user = self.get(user_id, cache=False)
178 179 if user.username == 'default':
179 180 raise DefaultUserException(
180 181 _("You can't Edit this user since it's"
181 182 " crucial for entire application"))
182 183 for k, v in form_data.items():
183 184 if k == 'new_password' and v != '':
184 185 user.password = v
185 186 user.api_key = generate_api_key(user.username)
186 187 else:
187 188 if k not in ['admin', 'active']:
188 189 setattr(user, k, v)
189 190
190 191 self.sa.add(user)
191 192 self.sa.commit()
192 193 except:
193 194 log.error(traceback.format_exc())
194 195 self.sa.rollback()
195 196 raise
196 197
197 198 def delete(self, user_id):
198 199 try:
199 200 user = self.get(user_id, cache=False)
200 201 if user.username == 'default':
201 202 raise DefaultUserException(
202 203 _("You can't remove this user since it's"
203 204 " crucial for entire application"))
204 205 if user.repositories:
205 206 raise UserOwnsReposException(_('This user still owns %s '
206 207 'repositories and cannot be '
207 208 'removed. Switch owners or '
208 209 'remove those repositories') \
209 210 % user.repositories)
210 211 self.sa.delete(user)
211 212 self.sa.commit()
212 213 except:
213 214 log.error(traceback.format_exc())
214 215 self.sa.rollback()
215 216 raise
216 217
217 218 def reset_password_link(self, data):
218 219 from rhodecode.lib.celerylib import tasks, run_task
219 220 run_task(tasks.send_password_link, data['email'])
220 221
221 222 def reset_password(self, data):
222 223 from rhodecode.lib.celerylib import tasks, run_task
223 224 run_task(tasks.reset_user_password, data['email'])
224 225
225 226 def fill_data(self, auth_user, user_id=None, api_key=None):
226 227 """
227 228 Fetches auth_user by user_id,or api_key if present.
228 229 Fills auth_user attributes with those taken from database.
229 230 Additionally set's is_authenitated if lookup fails
230 231 present in database
231 232
232 233 :param auth_user: instance of user to set attributes
233 234 :param user_id: user id to fetch by
234 235 :param api_key: api key to fetch by
235 236 """
236 237 if user_id is None and api_key is None:
237 238 raise Exception('You need to pass user_id or api_key')
238 239
239 240 try:
240 241 if api_key:
241 242 dbuser = self.get_by_api_key(api_key)
242 243 else:
243 244 dbuser = self.get(user_id)
244 245
245 246 if dbuser is not None:
246 247 log.debug('filling %s data', dbuser)
247 248 for k, v in dbuser.get_dict().items():
248 249 setattr(auth_user, k, v)
249 250
250 251 except:
251 252 log.error(traceback.format_exc())
252 253 auth_user.is_authenticated = False
253 254
254 255 return auth_user
255 256
256 257 def fill_perms(self, user):
257 258 """
258 259 Fills user permission attribute with permissions taken from database
259 260 works for permissions given for repositories, and for permissions that
260 261 are granted to groups
261 262
262 263 :param user: user instance to fill his perms
263 264 """
264 265
265 266 user.permissions['repositories'] = {}
266 267 user.permissions['global'] = set()
267 268
268 269 #======================================================================
269 270 # fetch default permissions
270 271 #======================================================================
271 272 default_user = self.get_by_username('default', cache=True)
272 273
273 274 default_perms = self.sa.query(RepoToPerm, Repository, Permission)\
274 275 .join((Repository, RepoToPerm.repository_id ==
275 276 Repository.repo_id))\
276 277 .join((Permission, RepoToPerm.permission_id ==
277 278 Permission.permission_id))\
278 279 .filter(RepoToPerm.user == default_user).all()
279 280
280 281 if user.is_admin:
281 282 #==================================================================
282 283 # #admin have all default rights set to admin
283 284 #==================================================================
284 285 user.permissions['global'].add('hg.admin')
285 286
286 287 for perm in default_perms:
287 288 p = 'repository.admin'
288 289 user.permissions['repositories'][perm.RepoToPerm.
289 290 repository.repo_name] = p
290 291
291 292 else:
292 293 #==================================================================
293 294 # set default permissions
294 295 #==================================================================
295 296 uid = user.user_id
296 297
297 298 #default global
298 299 default_global_perms = self.sa.query(UserToPerm)\
299 300 .filter(UserToPerm.user == default_user)
300 301
301 302 for perm in default_global_perms:
302 303 user.permissions['global'].add(perm.permission.permission_name)
303 304
304 305 #default for repositories
305 306 for perm in default_perms:
306 307 if perm.Repository.private and not (perm.Repository.user_id ==
307 308 uid):
308 309 #diself.sable defaults for private repos,
309 310 p = 'repository.none'
310 311 elif perm.Repository.user_id == uid:
311 312 #set admin if owner
312 313 p = 'repository.admin'
313 314 else:
314 315 p = perm.Permission.permission_name
315 316
316 317 user.permissions['repositories'][perm.RepoToPerm.
317 318 repository.repo_name] = p
318 319
319 320 #==================================================================
320 321 # overwrite default with user permissions if any
321 322 #==================================================================
322 323
323 324 #user global
324 325 user_perms = self.sa.query(UserToPerm)\
325 326 .options(joinedload(UserToPerm.permission))\
326 327 .filter(UserToPerm.user_id == uid).all()
327 328
328 329 for perm in user_perms:
329 330 user.permissions['global'].add(perm.permission.
330 331 permission_name)
331 332
332 333 #user repositories
333 334 user_repo_perms = self.sa.query(RepoToPerm, Permission,
334 335 Repository)\
335 336 .join((Repository, RepoToPerm.repository_id ==
336 337 Repository.repo_id))\
337 338 .join((Permission, RepoToPerm.permission_id ==
338 339 Permission.permission_id))\
339 340 .filter(RepoToPerm.user_id == uid).all()
340 341
341 342 for perm in user_repo_perms:
342 343 # set admin if owner
343 344 if perm.Repository.user_id == uid:
344 345 p = 'repository.admin'
345 346 else:
346 347 p = perm.Permission.permission_name
347 348 user.permissions['repositories'][perm.RepoToPerm.
348 349 repository.repo_name] = p
349 350
350 351 #==================================================================
351 352 # check if user is part of groups for this repository and fill in
352 353 # (or replace with higher) permissions
353 354 #==================================================================
354 355
355 356 #users group global
356 357 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
357 358 .options(joinedload(UsersGroupToPerm.permission))\
358 359 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
359 360 UsersGroupMember.users_group_id))\
360 361 .filter(UsersGroupMember.user_id == uid).all()
361 362
362 363 for perm in user_perms_from_users_groups:
363 364 user.permissions['global'].add(perm.permission.permission_name)
364 365
365 366 #users group repositories
366 367 user_repo_perms_from_users_groups = self.sa.query(
367 368 UsersGroupRepoToPerm,
368 369 Permission, Repository,)\
369 370 .join((Repository, UsersGroupRepoToPerm.repository_id ==
370 371 Repository.repo_id))\
371 372 .join((Permission, UsersGroupRepoToPerm.permission_id ==
372 373 Permission.permission_id))\
373 374 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
374 375 UsersGroupMember.users_group_id))\
375 376 .filter(UsersGroupMember.user_id == uid).all()
376 377
377 378 for perm in user_repo_perms_from_users_groups:
378 379 p = perm.Permission.permission_name
379 380 cur_perm = user.permissions['repositories'][perm.
380 381 UsersGroupRepoToPerm.
381 382 repository.repo_name]
382 383 #overwrite permission only if it's greater than permission
383 384 # given from other sources
384 385 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
385 386 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
386 387 repository.repo_name] = p
387 388
388 389 return user
390
@@ -1,64 +1,64 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Add repos group')} - ${c.rhodecode_name}
6 6 </%def>
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 9 &raquo;
10 10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 11 &raquo;
12 12 ${_('add new repos group')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('admin')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 ${h.form(url('repos_groups'))}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 <label for="users_group_name">${_('Group name')}:</label>
32 <label for="group_name">${_('Group name')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('group_name',class_='medium')}
36 36 </div>
37 37 </div>
38 38
39 39 <div class="field">
40 40 <div class="label label-textarea">
41 <label for="description">${_('Description')}:</label>
41 <label for="group_description">${_('Description')}:</label>
42 42 </div>
43 43 <div class="textarea text-area editor">
44 44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
45 45 </div>
46 46 </div>
47 47
48 48 <div class="field">
49 49 <div class="label">
50 <label for="repo_group">${_('Group parent')}:</label>
50 <label for="group_parent_id">${_('Group parent')}:</label>
51 51 </div>
52 52 <div class="input">
53 53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
54 54 </div>
55 55 </div>
56 56
57 57 <div class="buttons">
58 58 ${h.submit('save',_('save'),class_="ui-button")}
59 59 </div>
60 60 </div>
61 61 </div>
62 62 ${h.end_form()}
63 63 </div>
64 64 </%def>
@@ -1,64 +1,64 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name}
6 6 </%def>
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 9 &raquo;
10 10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 11 &raquo;
12 12 ${_('edit repos group')} "${c.repos_group.name}"
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('admin')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 ${h.form(url('repos_group',id=c.repos_group.group_id),method='put')}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 <label for="users_group_name">${_('Group name')}:</label>
32 <label for="group_name">${_('Group name')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('group_name',class_='medium')}
36 36 </div>
37 37 </div>
38 38
39 39 <div class="field">
40 40 <div class="label label-textarea">
41 <label for="description">${_('Description')}:</label>
41 <label for="group_description">${_('Description')}:</label>
42 42 </div>
43 43 <div class="textarea text-area editor">
44 44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
45 45 </div>
46 46 </div>
47 47
48 48 <div class="field">
49 49 <div class="label">
50 <label for="repo_group">${_('Group parent')}:</label>
50 <label for="group_parent_id">${_('Group parent')}:</label>
51 51 </div>
52 52 <div class="input">
53 53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
54 54 </div>
55 55 </div>
56 56
57 57 <div class="buttons">
58 58 ${h.submit('save',_('save'),class_="ui-button")}
59 59 </div>
60 60 </div>
61 61 </div>
62 62 ${h.end_form()}
63 63 </div>
64 64 </%def>
@@ -1,192 +1,192 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Settings administration')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15
16 16 <%def name="main()">
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22 <!-- end box / title -->
23 23
24 24 <h3>${_('Remap and rescan repositories')}</h3>
25 25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 26 <div class="form">
27 27 <!-- fields -->
28 28
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label label-checkbox">
32 32 <label for="destroy">${_('rescan option')}:</label>
33 33 </div>
34 34 <div class="checkboxes">
35 35 <div class="checkbox">
36 36 ${h.checkbox('destroy',True)}
37 <label for="checkbox-1">
37 <label for="destroy">
38 38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
39 39 ${_('destroy old data')}</span> </label>
40 40 </div>
41 41 </div>
42 42 </div>
43 43
44 44 <div class="buttons">
45 45 ${h.submit('rescan',_('Rescan repositories'),class_="ui-button")}
46 46 </div>
47 47 </div>
48 48 </div>
49 49 ${h.end_form()}
50 50
51 51 <h3>${_('Whoosh indexing')}</h3>
52 52 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
53 53 <div class="form">
54 54 <!-- fields -->
55 55
56 56 <div class="fields">
57 57 <div class="field">
58 58 <div class="label label-checkbox">
59 <label for="destroy">${_('index build option')}:</label>
59 <label>${_('index build option')}:</label>
60 60 </div>
61 61 <div class="checkboxes">
62 62 <div class="checkbox">
63 63 ${h.checkbox('full_index',True)}
64 <label for="checkbox-1">${_('build from scratch')}</label>
64 <label for="full_index">${_('build from scratch')}</label>
65 65 </div>
66 66 </div>
67 67 </div>
68 68
69 69 <div class="buttons">
70 70 ${h.submit('reindex',_('Reindex'),class_="ui-button")}
71 71 </div>
72 72 </div>
73 73 </div>
74 74 ${h.end_form()}
75 75
76 76 <h3>${_('Global application settings')}</h3>
77 77 ${h.form(url('admin_setting', setting_id='global'),method='put')}
78 78 <div class="form">
79 79 <!-- fields -->
80 80
81 81 <div class="fields">
82 82
83 83 <div class="field">
84 84 <div class="label">
85 85 <label for="rhodecode_title">${_('Application name')}:</label>
86 86 </div>
87 87 <div class="input">
88 88 ${h.text('rhodecode_title',size=30)}
89 89 </div>
90 90 </div>
91 91
92 92 <div class="field">
93 93 <div class="label">
94 94 <label for="rhodecode_realm">${_('Realm text')}:</label>
95 95 </div>
96 96 <div class="input">
97 97 ${h.text('rhodecode_realm',size=30)}
98 98 </div>
99 99 </div>
100 100
101 101 <div class="field">
102 102 <div class="label">
103 <label for="ga_code">${_('GA code')}:</label>
103 <label for="rhodecode_ga_code">${_('GA code')}:</label>
104 104 </div>
105 105 <div class="input">
106 106 ${h.text('rhodecode_ga_code',size=30)}
107 107 </div>
108 108 </div>
109 109
110 110 <div class="buttons">
111 111 ${h.submit('save',_('Save settings'),class_="ui-button")}
112 112 ${h.reset('reset',_('Reset'),class_="ui-button")}
113 113 </div>
114 114 </div>
115 115 </div>
116 116 ${h.end_form()}
117 117
118 118 <h3>${_('Mercurial settings')}</h3>
119 119 ${h.form(url('admin_setting', setting_id='mercurial'),method='put')}
120 120 <div class="form">
121 121 <!-- fields -->
122 122
123 123 <div class="fields">
124 124
125 125 <div class="field">
126 126 <div class="label label-checkbox">
127 <label for="web_push_ssl">${_('Web')}:</label>
127 <label>${_('Web')}:</label>
128 128 </div>
129 129 <div class="checkboxes">
130 130 <div class="checkbox">
131 131 ${h.checkbox('web_push_ssl','true')}
132 132 <label for="web_push_ssl">${_('require ssl for pushing')}</label>
133 133 </div>
134 134 </div>
135 135 </div>
136 136
137 137 <div class="field">
138 138 <div class="label label-checkbox">
139 <label for="web_push_ssl">${_('Hooks')}:</label>
139 <label>${_('Hooks')}:</label>
140 140 </div>
141 141 <div class="input">
142 142 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'))}
143 143 </div>
144 144 <div class="checkboxes">
145 145 <div class="checkbox">
146 146 ${h.checkbox('hooks_changegroup_update','True')}
147 147 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
148 148 </div>
149 149 <div class="checkbox">
150 150 ${h.checkbox('hooks_changegroup_repo_size','True')}
151 151 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
152 152 </div>
153 153 <div class="checkbox">
154 154 ${h.checkbox('hooks_pretxnchangegroup_push_logger','True')}
155 155 <label for="hooks_pretxnchangegroup_push_logger">${_('Log user push commands')}</label>
156 156 </div>
157 157 <div class="checkbox">
158 158 ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
159 159 <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
160 160 </div>
161 161 </div>
162 162 </div>
163 163
164 164 <div class="field">
165 165 <div class="label">
166 166 <label for="paths_root_path">${_('Repositories location')}:</label>
167 167 </div>
168 168 <div class="input">
169 169 ${h.text('paths_root_path',size=30,readonly="readonly")}
170 170 <span id="path_unlock" class="tooltip"
171 171 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
172 172 ${_('unlock')}</span>
173 173 </div>
174 174 </div>
175 175
176 176 <div class="buttons">
177 177 ${h.submit('save',_('Save settings'),class_="ui-button")}
178 178 ${h.reset('reset',_('Reset'),class_="ui-button")}
179 179 </div>
180 180 </div>
181 181 </div>
182 182 ${h.end_form()}
183 183
184 184 <script type="text/javascript">
185 185 YAHOO.util.Event.onDOMReady(function(){
186 186 YAHOO.util.Event.addListener('path_unlock','click',function(){
187 187 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
188 188 });
189 189 });
190 190 </script>
191 191 </div>
192 192 </%def>
@@ -1,91 +1,100 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Add user')} - ${c.rhodecode_name}
6 6 </%def>
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 9 &raquo;
10 10 ${h.link_to(_('Users'),h.url('users'))}
11 11 &raquo;
12 12 ${_('add new user')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('admin')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 ${h.form(url('users'))}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 32 <label for="username">${_('Username')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('username',class_='small')}
36 36 </div>
37 37 </div>
38 38
39 39 <div class="field">
40 40 <div class="label">
41 41 <label for="password">${_('Password')}:</label>
42 42 </div>
43 43 <div class="input">
44 44 ${h.password('password',class_='small')}
45 45 </div>
46 46 </div>
47
47
48 <div class="field">
49 <div class="label">
50 <label for="password_confirmation">${_('Password confirmation')}:</label>
51 </div>
52 <div class="input">
53 ${h.password('password_confirmation',class_="small",autocomplete="off")}
54 </div>
55 </div>
56
48 57 <div class="field">
49 58 <div class="label">
50 59 <label for="name">${_('First Name')}:</label>
51 60 </div>
52 61 <div class="input">
53 62 ${h.text('name',class_='small')}
54 63 </div>
55 64 </div>
56 65
57 66 <div class="field">
58 67 <div class="label">
59 68 <label for="lastname">${_('Last Name')}:</label>
60 69 </div>
61 70 <div class="input">
62 71 ${h.text('lastname',class_='small')}
63 72 </div>
64 73 </div>
65 74
66 75 <div class="field">
67 76 <div class="label">
68 77 <label for="email">${_('Email')}:</label>
69 78 </div>
70 79 <div class="input">
71 80 ${h.text('email',class_='small')}
72 81 </div>
73 82 </div>
74 83
75 84 <div class="field">
76 85 <div class="label label-checkbox">
77 86 <label for="active">${_('Active')}:</label>
78 87 </div>
79 88 <div class="checkboxes">
80 89 ${h.checkbox('active',value=True)}
81 90 </div>
82 91 </div>
83 92
84 93 <div class="buttons">
85 94 ${h.submit('save',_('save'),class_="ui-button")}
86 95 </div>
87 96 </div>
88 97 </div>
89 98 ${h.end_form()}
90 99 </div>
91 100 </%def>
@@ -1,149 +1,158 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit user')} ${c.user.username} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('Users'),h.url('users'))}
12 12 &raquo;
13 13 ${_('edit')} "${c.user.username}"
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 <!-- end box / title -->
27 27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
28 28 <div class="form">
29 29 <div class="field">
30 30 <div class="gravatar_box">
31 31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
32 32 <p>
33 33 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong><br/>
34 34 ${_('Using')} ${c.user.email}
35 35 </p>
36 36 </div>
37 37 </div>
38 38 <div class="field">
39 39 <div class="label">
40 40 <label>${_('API key')}</label> ${c.user.api_key}
41 41 </div>
42 42 </div>
43 43
44 44 <div class="fields">
45 45 <div class="field">
46 46 <div class="label">
47 47 <label for="username">${_('Username')}:</label>
48 48 </div>
49 49 <div class="input">
50 50 ${h.text('username',class_='medium')}
51 51 </div>
52 52 </div>
53 53
54 54 <div class="field">
55 55 <div class="label">
56 56 <label for="ldap_dn">${_('LDAP DN')}:</label>
57 57 </div>
58 58 <div class="input">
59 59 ${h.text('ldap_dn',class_='medium')}
60 60 </div>
61 61 </div>
62 62
63 63 <div class="field">
64 64 <div class="label">
65 65 <label for="new_password">${_('New password')}:</label>
66 66 </div>
67 67 <div class="input">
68 68 ${h.password('new_password',class_='medium',autocomplete="off")}
69 69 </div>
70 70 </div>
71
71
72 <div class="field">
73 <div class="label">
74 <label for="password_confirmation">${_('New password confirmation')}:</label>
75 </div>
76 <div class="input">
77 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
78 </div>
79 </div>
80
72 81 <div class="field">
73 82 <div class="label">
74 83 <label for="name">${_('First Name')}:</label>
75 84 </div>
76 85 <div class="input">
77 86 ${h.text('name',class_='medium')}
78 87 </div>
79 88 </div>
80 89
81 90 <div class="field">
82 91 <div class="label">
83 92 <label for="lastname">${_('Last Name')}:</label>
84 93 </div>
85 94 <div class="input">
86 95 ${h.text('lastname',class_='medium')}
87 96 </div>
88 97 </div>
89 98
90 99 <div class="field">
91 100 <div class="label">
92 101 <label for="email">${_('Email')}:</label>
93 102 </div>
94 103 <div class="input">
95 104 ${h.text('email',class_='medium')}
96 105 </div>
97 106 </div>
98 107
99 108 <div class="field">
100 109 <div class="label label-checkbox">
101 110 <label for="active">${_('Active')}:</label>
102 111 </div>
103 112 <div class="checkboxes">
104 113 ${h.checkbox('active',value=True)}
105 114 </div>
106 115 </div>
107 116
108 117 <div class="field">
109 118 <div class="label label-checkbox">
110 119 <label for="admin">${_('Admin')}:</label>
111 120 </div>
112 121 <div class="checkboxes">
113 122 ${h.checkbox('admin',value=True)}
114 123 </div>
115 124 </div>
116 125 <div class="buttons">
117 126 ${h.submit('save',_('Save'),class_="ui-button")}
118 127 ${h.reset('reset',_('Reset'),class_="ui-button")}
119 128 </div>
120 129 </div>
121 130 </div>
122 131 ${h.end_form()}
123 132 </div>
124 133 <div class="box box-right">
125 134 <!-- box / title -->
126 135 <div class="title">
127 136 <h5>${_('Permissions')}</h5>
128 137 </div>
129 138 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
130 139 <div class="form">
131 140 <!-- fields -->
132 141 <div class="fields">
133 142 <div class="field">
134 143 <div class="label label-checkbox">
135 <label for="">${_('Create repositories')}:</label>
144 <label for="create_repo_perm">${_('Create repositories')}:</label>
136 145 </div>
137 146 <div class="checkboxes">
138 147 ${h.checkbox('create_repo_perm',value=True)}
139 148 </div>
140 149 </div>
141 150 <div class="buttons">
142 151 ${h.submit('save',_('Save'),class_="ui-button")}
143 152 ${h.reset('reset',_('Reset'),class_="ui-button")}
144 153 </div>
145 154 </div>
146 155 </div>
147 156 ${h.end_form()}
148 157 </div>
149 158 </%def>
@@ -1,211 +1,222 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${_('My Account')}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15
16 16 <%def name="main()">
17 17
18 18 <div class="box box-left">
19 19 <!-- box / title -->
20 20 <div class="title">
21 21 ${self.breadcrumbs()}
22 22 </div>
23 23 <!-- end box / title -->
24 24 <div>
25 25 ${h.form(url('admin_settings_my_account_update'),method='put')}
26 26 <div class="form">
27 27
28 28 <div class="field">
29 29 <div class="gravatar_box">
30 30 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
31 31 <p>
32 32 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong><br/>
33 33 ${_('Using')} ${c.user.email}
34 34 </p>
35 35 </div>
36 36 </div>
37 37 <div class="field">
38 38 <div class="label">
39 39 <label>${_('API key')}</label> ${c.user.api_key}
40 40 </div>
41 41 </div>
42 42 <div class="fields">
43 43 <div class="field">
44 44 <div class="label">
45 45 <label for="username">${_('Username')}:</label>
46 46 </div>
47 47 <div class="input">
48 48 ${h.text('username',class_="medium")}
49 49 </div>
50 50 </div>
51 51
52 52 <div class="field">
53 53 <div class="label">
54 54 <label for="new_password">${_('New password')}:</label>
55 55 </div>
56 56 <div class="input">
57 57 ${h.password('new_password',class_="medium",autocomplete="off")}
58 58 </div>
59 59 </div>
60
60
61 <div class="field">
62 <div class="label">
63 <label for="password_confirmation">${_('New password confirmation')}:</label>
64 </div>
65 <div class="input">
66 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
67 </div>
68 </div>
69
61 70 <div class="field">
62 71 <div class="label">
63 72 <label for="name">${_('First Name')}:</label>
64 73 </div>
65 74 <div class="input">
66 75 ${h.text('name',class_="medium")}
67 76 </div>
68 77 </div>
69 78
70 79 <div class="field">
71 80 <div class="label">
72 81 <label for="lastname">${_('Last Name')}:</label>
73 82 </div>
74 83 <div class="input">
75 84 ${h.text('lastname',class_="medium")}
76 85 </div>
77 86 </div>
78 87
79 88 <div class="field">
80 89 <div class="label">
81 90 <label for="email">${_('Email')}:</label>
82 91 </div>
83 92 <div class="input">
84 93 ${h.text('email',class_="medium")}
85 94 </div>
86 95 </div>
87 96
88 97 <div class="buttons">
89 98 ${h.submit('save',_('Save'),class_="ui-button")}
90 99 ${h.reset('reset',_('Reset'),class_="ui-button")}
91 100 </div>
92 101 </div>
93 102 </div>
94 103 ${h.end_form()}
95 104 </div>
96 105 </div>
97 106
98 107 <div class="box box-right">
99 108 <!-- box / title -->
100 109 <div class="title">
101 110 <h5>${_('My repositories')}
102 111 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
103 112 </h5>
104 113 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
105 114 <ul class="links">
106 115 <li>
107 116 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
108 117 </li>
109 118 </ul>
110 119 %endif
111 120 </div>
112 121 <!-- end box / title -->
113 122 <div class="table">
114 123 <table>
115 124 <thead>
116 125 <tr>
117 126 <th class="left">${_('Name')}</th>
118 127 <th class="left">${_('revision')}</th>
119 128 <th colspan="2" class="left">${_('action')}</th>
120 129 </thead>
121 130 <tbody>
122 131 %if c.user_repos:
123 132 %for repo in c.user_repos:
124 133 <tr>
125 134 <td>
126 135 %if repo['dbrepo']['repo_type'] =='hg':
127 136 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
128 137 %elif repo['dbrepo']['repo_type'] =='git':
129 138 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
130 139 %else:
131 140
132 141 %endif
133 142 %if repo['dbrepo']['private']:
134 143 <img class="icon" alt="${_('private')}" src="${h.url("/images/icons/lock.png")}"/>
135 144 %else:
136 145 <img class="icon" alt="${_('public')}" src="${h.url("/images/icons/lock_open.png")}"/>
137 146 %endif
138 147
139 148 ${h.link_to(repo['name'], h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
140 149 %if repo['dbrepo_fork']:
141 150 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
142 151 <img class="icon" alt="${_('public')}"
143 152 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
144 153 src="${h.url('/images/icons/arrow_divide.png')}"/></a>
145 154 %endif
146 155 </td>
147 156 <td><span class="tooltip" title="${repo['last_change']}">${("r%s:%s") % (repo['rev'],h.short_id(repo['tip']))}</span></td>
148 157 <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url('/images/icons/application_form_edit.png')}"/></a></td>
149 158 <td>
150 159 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
151 160 ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
152 161 ${h.end_form()}
153 162 </td>
154 163 </tr>
155 164 %endfor
156 165 %else:
166 <div style="padding:5px 0px 10px 0px;">
157 167 ${_('No repositories yet')}
158 168 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
159 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'))}
169 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-button-small")}
160 170 %endif
171 </div>
161 172 %endif
162 173 </tbody>
163 174 </table>
164 175 </div>
165 176
166 177 </div>
167 178 <script type="text/javascript">
168 179 var D = YAHOO.util.Dom;
169 180 var E = YAHOO.util.Event;
170 181 var S = YAHOO.util.Selector;
171 182
172 183 var q_filter = D.get('q_filter');
173 184 var F = YAHOO.namespace('q_filter');
174 185
175 186 E.on(q_filter,'click',function(){
176 187 q_filter.value = '';
177 188 });
178 189
179 190 F.filterTimeout = null;
180 191
181 192 F.updateFilter = function() {
182 193 // Reset timeout
183 194 F.filterTimeout = null;
184 195
185 196 var obsolete = [];
186 197 var nodes = S.query('div.table tr td a.repo_name');
187 198 var req = q_filter.value.toLowerCase();
188 199 for (n in nodes){
189 200 D.setStyle(nodes[n].parentNode.parentNode,'display','')
190 201 }
191 202 if (req){
192 203 for (n in nodes){
193 204 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
194 205 obsolete.push(nodes[n]);
195 206 }
196 207 }
197 208 if(obsolete){
198 209 for (n in obsolete){
199 210 D.setStyle(obsolete[n].parentNode.parentNode,'display','none');
200 211 }
201 212 }
202 213 }
203 214 }
204 215
205 216 E.on(q_filter,'keyup',function(e){
206 217 clearTimeout(F.filterTimeout);
207 218 F.filterTimeout = setTimeout(F.updateFilter,600);
208 219 });
209 220
210 221 </script>
211 222 </%def>
@@ -1,270 +1,270 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit users group')} ${c.users_group.users_group_name} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('UsersGroups'),h.url('users_groups'))}
12 12 &raquo;
13 13 ${_('edit')} "${c.users_group.users_group_name}"
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 <!-- end box / title -->
27 27 ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')}
28 28 <div class="form">
29 29 <!-- fields -->
30 30 <div class="fields">
31 31 <div class="field">
32 32 <div class="label">
33 33 <label for="users_group_name">${_('Group name')}:</label>
34 34 </div>
35 35 <div class="input">
36 36 ${h.text('users_group_name',class_='small')}
37 37 </div>
38 38 </div>
39 39
40 40 <div class="field">
41 41 <div class="label label-checkbox">
42 42 <label for="users_group_active">${_('Active')}:</label>
43 43 </div>
44 44 <div class="checkboxes">
45 45 ${h.checkbox('users_group_active',value=True)}
46 46 </div>
47 47 </div>
48 48 <div class="field">
49 49 <div class="label">
50 50 <label for="users_group_active">${_('Members')}:</label>
51 51 </div>
52 52 <div class="select">
53 53 <table>
54 54 <tr>
55 55 <td>
56 56 <div>
57 57 <div style="float:left">
58 58 <div class="text" style="padding: 0px 0px 6px;">${_('Choosen group members')}</div>
59 59 ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")}
60 60 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
61 61 ${_('Remove all elements')}
62 62 <img alt="remove" style="vertical-align:text-bottom" src="${h.url("/images/icons/arrow_right.png")}"/>
63 63 </div>
64 64 </div>
65 65 <div style="float:left;width:20px;padding-top:50px">
66 66 <img alt="add" id="add_element"
67 67 style="padding:2px;cursor:pointer"
68 68 src="${h.url("/images/icons/arrow_left.png")}"/>
69 69 <br />
70 70 <img alt="remove" id="remove_element"
71 71 style="padding:2px;cursor:pointer"
72 72 src="${h.url("/images/icons/arrow_right.png")}"/>
73 73 </div>
74 74 <div style="float:left">
75 75 <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
76 76 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
77 77 <div id="add_all_elements" style="cursor:pointer;text-align:center">
78 78 <img alt="add" style="vertical-align:text-bottom" src="${h.url("/images/icons/arrow_left.png")}"/>
79 79 ${_('Add all elements')}
80 80 </div>
81 81 </div>
82 82 </div>
83 83 </td>
84 84 </tr>
85 85 </table>
86 86 </div>
87 87
88 88 </div>
89 89 <div class="buttons">
90 90 ${h.submit('save',_('save'),class_="ui-button")}
91 91 </div>
92 92 </div>
93 93 </div>
94 94 ${h.end_form()}
95 95 </div>
96 96
97 97 <script type="text/javascript">
98 98 YAHOO.util.Event.onDOMReady(function(){
99 99 var D = YAHOO.util.Dom;
100 100 var E = YAHOO.util.Event;
101 101
102 102 //definition of containers ID's
103 103 var available_container = 'available_members';
104 104 var selected_container = 'users_group_members';
105 105
106 106 //form containing containers id
107 107 var form_id = 'edit_users_group';
108 108
109 109 //temp container for selected storage.
110 110 var cache = new Array();
111 111 var av_cache = new Array();
112 112 var c = D.get(selected_container);
113 113 var ac = D.get(available_container);
114 114
115 115 //get only selected options for further fullfilment
116 116 for(var i = 0;node =c.options[i];i++){
117 117 if(node.selected){
118 118 //push selected to my temp storage left overs :)
119 119 cache.push(node);
120 120 }
121 121 }
122 122
123 123 //clear 'selected' select
124 124 //c.options.length = 0;
125 125
126 126 //fill it with remembered options
127 127 //for(var i = 0;node = cache[i];i++){
128 128 // c.options[i]=new Option(node.text, node.value, false, false);
129 129 //}
130 130
131 131
132 132 //get all available options to cache
133 133 for(var i = 0;node =ac.options[i];i++){
134 134 //push selected to my temp storage left overs :)
135 135 av_cache.push(node);
136 136 }
137 137
138 138 //fill available only with those not in choosen
139 139 ac.options.length=0;
140 140 tmp_cache = new Array();
141 141
142 142 for(var i = 0;node = av_cache[i];i++){
143 143 var add = true;
144 144 for(var i2 = 0;node_2 = cache[i2];i2++){
145 145 if(node.value == node_2.value){
146 146 add=false;
147 147 break;
148 148 }
149 149 }
150 150 if(add){
151 151 tmp_cache.push(new Option(node.text, node.value, false, false));
152 152 }
153 153 }
154 154
155 155 for(var i = 0;node = tmp_cache[i];i++){
156 156 ac.options[i] = node;
157 157 }
158 158
159 159 function prompts_action_callback(e){
160 160
161 161 var choosen = D.get(selected_container);
162 162 var available = D.get(available_container);
163 163
164 164 //get checked and unchecked options from field
165 165 function get_checked(from_field){
166 166 //temp container for storage.
167 167 var sel_cache = new Array();
168 168 var oth_cache = new Array();
169 169
170 170 for(var i = 0;node = from_field.options[i];i++){
171 171 if(node.selected){
172 172 //push selected fields :)
173 173 sel_cache.push(node);
174 174 }
175 175 else{
176 176 oth_cache.push(node)
177 177 }
178 178 }
179 179
180 180 return [sel_cache,oth_cache]
181 181 }
182 182
183 183 //fill the field with given options
184 184 function fill_with(field,options){
185 185 //clear firtst
186 186 field.options.length=0;
187 187 for(var i = 0;node = options[i];i++){
188 188 field.options[i]=new Option(node.text, node.value,
189 189 false, false);
190 190 }
191 191
192 192 }
193 193 //adds to current field
194 194 function add_to(field,options){
195 195 for(var i = 0;node = options[i];i++){
196 196 field.appendChild(new Option(node.text, node.value,
197 197 false, false));
198 198 }
199 199 }
200 200
201 201 // add action
202 202 if (this.id=='add_element'){
203 203 var c = get_checked(available);
204 204 add_to(choosen,c[0]);
205 205 fill_with(available,c[1]);
206 206 }
207 207 // remove action
208 208 if (this.id=='remove_element'){
209 209 var c = get_checked(choosen);
210 210 add_to(available,c[0]);
211 211 fill_with(choosen,c[1]);
212 212 }
213 213 // add all elements
214 214 if(this.id=='add_all_elements'){
215 215 for(var i=0; node = available.options[i];i++){
216 216 choosen.appendChild(new Option(node.text,
217 217 node.value, false, false));
218 218 }
219 219 available.options.length = 0;
220 220 }
221 221 //remove all elements
222 222 if(this.id=='remove_all_elements'){
223 223 for(var i=0; node = choosen.options[i];i++){
224 224 available.appendChild(new Option(node.text,
225 225 node.value, false, false));
226 226 }
227 227 choosen.options.length = 0;
228 228 }
229 229
230 230 }
231 231
232 232
233 233 E.addListener(['add_element','remove_element',
234 234 'add_all_elements','remove_all_elements'],'click',
235 235 prompts_action_callback)
236 236
237 237 E.addListener(form_id,'submit',function(){
238 238 var choosen = D.get(selected_container);
239 239 for (var i = 0; i < choosen.options.length; i++) {
240 240 choosen.options[i].selected = 'selected';
241 241 }
242 242 })
243 243 });
244 244 </script>
245 245 <div class="box box-right">
246 246 <!-- box / title -->
247 247 <div class="title">
248 248 <h5>${_('Permissions')}</h5>
249 249 </div>
250 250 ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')}
251 251 <div class="form">
252 252 <!-- fields -->
253 253 <div class="fields">
254 254 <div class="field">
255 255 <div class="label label-checkbox">
256 <label for="">${_('Create repositories')}:</label>
256 <label for="create_repo_perm">${_('Create repositories')}:</label>
257 257 </div>
258 258 <div class="checkboxes">
259 259 ${h.checkbox('create_repo_perm',value=True)}
260 260 </div>
261 261 </div>
262 262 <div class="buttons">
263 263 ${h.submit('save',_('Save'),class_="ui-button")}
264 264 ${h.reset('reset',_('Reset'),class_="ui-button")}
265 265 </div>
266 266 </div>
267 267 </div>
268 268 ${h.end_form()}
269 269 </div>
270 270 </%def>
@@ -1,75 +1,82 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${c.repo_name} ${_('Settings')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(u'Home',h.url('/'))}
10 10 &raquo;
11 11 ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
12 12 &raquo;
13 13 ${_('Settings')}
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('settings')}
18 18 </%def>
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 ${h.form(url('repo_settings_update', repo_name=c.repo_info.repo_name),method='put')}
26 26 <div class="form">
27 27 <!-- fields -->
28 28 <div class="fields">
29 29 <div class="field">
30 30 <div class="label">
31 31 <label for="repo_name">${_('Name')}:</label>
32 32 </div>
33 33 <div class="input input-medium">
34 34 ${h.text('repo_name',class_="small")}
35 35 </div>
36 36 </div>
37
37 <div class="field">
38 <div class="label">
39 <label for="repo_group">${_('Repository group')}:</label>
40 </div>
41 <div class="input">
42 ${h.select('repo_group','',c.repo_groups,class_="medium")}
43 </div>
44 </div>
38 45 <div class="field">
39 46 <div class="label label-textarea">
40 47 <label for="description">${_('Description')}:</label>
41 48 </div>
42 49 <div class="textarea text-area editor">
43 50 ${h.textarea('description',cols=23,rows=5)}
44 51 </div>
45 52 </div>
46 53
47 54 <div class="field">
48 55 <div class="label label-checkbox">
49 56 <label for="private">${_('Private')}:</label>
50 57 </div>
51 58 <div class="checkboxes">
52 59 ${h.checkbox('private',value="True")}
53 60 </div>
54 61 </div>
55 62
56 63 <div class="field">
57 64 <div class="label">
58 65 <label for="">${_('Permissions')}:</label>
59 66 </div>
60 67 <div class="input">
61 68 <%include file="../admin/repos/repo_edit_perms.html"/>
62 69 </div>
63 70
64 71 <div class="buttons">
65 72 ${h.submit('save','Save',class_="ui-button")}
66 73 ${h.reset('reset','Reset',class_="ui-button")}
67 74 </div>
68 75 </div>
69 76 </div>
70 77 ${h.end_form()}
71 78 </div>
72 79 </div>
73 80 </%def>
74 81
75 82
@@ -1,85 +1,83 b''
1 1 """Pylons application test package
2 2
3 3 This package assumes the Pylons environment is already loaded, such as
4 4 when this script is imported from the `nosetests --with-pylons=test.ini`
5 5 command.
6 6
7 7 This module initializes the application via ``websetup`` (`paster
8 8 setup-app`) and provides the base testing objects.
9 9 """
10 10 import os
11 11 from os.path import join as jn
12 12
13 13 from unittest import TestCase
14 14
15 15 from paste.deploy import loadapp
16 16 from paste.script.appinstall import SetupCommand
17 17 from pylons import config, url
18 18 from routes.util import URLGenerator
19 19 from webtest import TestApp
20 20
21 21 from rhodecode.model import meta
22 22 import logging
23 23
24 24
25 25 log = logging.getLogger(__name__)
26 26
27 27 import pylons.test
28 28
29 29 __all__ = ['environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
30 30 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK',
31 31 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS' ]
32 32
33 33 # Invoke websetup with the current config file
34 34 #SetupCommand('setup-app').run([config_file])
35 35
36 36 ##RUNNING DESIRED TESTS
37 37 # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
38 38 # nosetests --pdb --pdb-failures
39 39 environ = {}
40 40
41 41 #SOME GLOBALS FOR TESTS
42 42 from tempfile import _RandomNameSequence
43 43 TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
44 44 TEST_USER_ADMIN_LOGIN = 'test_admin'
45 45 TEST_USER_ADMIN_PASS = 'test12'
46 46 HG_REPO = 'vcs_test_hg'
47 47 GIT_REPO = 'vcs_test_git'
48 48
49 49 NEW_HG_REPO = 'vcs_test_hg_new'
50 50 NEW_GIT_REPO = 'vcs_test_git_new'
51 51
52 52 HG_FORK = 'vcs_test_hg_fork'
53 53 GIT_FORK = 'vcs_test_git_fork'
54 54
55 55 class TestController(TestCase):
56 56
57 57 def __init__(self, *args, **kwargs):
58 58 wsgiapp = pylons.test.pylonsapp
59 59 config = wsgiapp.config
60 60
61 61 self.app = TestApp(wsgiapp)
62 62 url._push_object(URLGenerator(config['routes.map'], environ))
63 63 self.sa = meta.Session
64 64 self.index_location = config['app_conf']['index_dir']
65 65 TestCase.__init__(self, *args, **kwargs)
66 66
67 67 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
68 68 password=TEST_USER_ADMIN_PASS):
69 69 response = self.app.post(url(controller='login', action='index'),
70 70 {'username':username,
71 71 'password':password})
72 72
73 73 if 'invalid user name' in response.body:
74 74 self.fail('could not login using %s %s' % (username, password))
75 75
76 76 self.assertEqual(response.status, '302 Found')
77 77 self.assertEqual(response.session['rhodecode_user'].username, username)
78 78 return response.follow()
79 79
80
81
82 80 def checkSessionFlash(self, response, msg):
83 81 self.assertTrue('flash' in response.session)
84 82 self.assertTrue(msg in response.session['flash'][0][1])
85 83
@@ -1,208 +1,212 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 from rhodecode.lib.auth import get_crypt_password, check_password
4 4 from rhodecode.model.db import User, RhodeCodeSettings
5 5 from rhodecode.tests import *
6 6
7 7 class TestAdminSettingsController(TestController):
8 8
9 9 def test_index(self):
10 10 response = self.app.get(url('admin_settings'))
11 11 # Test response...
12 12
13 13 def test_index_as_xml(self):
14 14 response = self.app.get(url('formatted_admin_settings', format='xml'))
15 15
16 16 def test_create(self):
17 17 response = self.app.post(url('admin_settings'))
18 18
19 19 def test_new(self):
20 20 response = self.app.get(url('admin_new_setting'))
21 21
22 22 def test_new_as_xml(self):
23 23 response = self.app.get(url('formatted_admin_new_setting', format='xml'))
24 24
25 25 def test_update(self):
26 26 response = self.app.put(url('admin_setting', setting_id=1))
27 27
28 28 def test_update_browser_fakeout(self):
29 29 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='put'))
30 30
31 31 def test_delete(self):
32 32 response = self.app.delete(url('admin_setting', setting_id=1))
33 33
34 34 def test_delete_browser_fakeout(self):
35 35 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='delete'))
36 36
37 37 def test_show(self):
38 38 response = self.app.get(url('admin_setting', setting_id=1))
39 39
40 40 def test_show_as_xml(self):
41 41 response = self.app.get(url('formatted_admin_setting', setting_id=1, format='xml'))
42 42
43 43 def test_edit(self):
44 44 response = self.app.get(url('admin_edit_setting', setting_id=1))
45 45
46 46 def test_edit_as_xml(self):
47 47 response = self.app.get(url('formatted_admin_edit_setting',
48 48 setting_id=1, format='xml'))
49 49
50 50
51 51 def test_ga_code_active(self):
52 52 self.log_user()
53 53 old_title = 'RhodeCode'
54 54 old_realm = 'RhodeCode authentication'
55 55 new_ga_code = 'ga-test-123456789'
56 56 response = self.app.post(url('admin_setting', setting_id='global'),
57 57 params=dict(
58 58 _method='put',
59 59 rhodecode_title=old_title,
60 60 rhodecode_realm=old_realm,
61 61 rhodecode_ga_code=new_ga_code
62 62 ))
63 63
64 64 self.checkSessionFlash(response, 'Updated application settings')
65 65
66 66 self.assertEqual(RhodeCodeSettings
67 67 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
68 68
69 69 response = response.follow()
70 70 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
71 71 in response.body)
72 72
73 73 def test_ga_code_inactive(self):
74 74 self.log_user()
75 75 old_title = 'RhodeCode'
76 76 old_realm = 'RhodeCode authentication'
77 77 new_ga_code = ''
78 78 response = self.app.post(url('admin_setting', setting_id='global'),
79 79 params=dict(
80 80 _method='put',
81 81 rhodecode_title=old_title,
82 82 rhodecode_realm=old_realm,
83 83 rhodecode_ga_code=new_ga_code
84 84 ))
85 85
86 86 self.assertTrue('Updated application settings' in
87 87 response.session['flash'][0][1])
88 88 self.assertEqual(RhodeCodeSettings
89 89 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
90 90
91 91 response = response.follow()
92 92 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
93 93 not in response.body)
94 94
95 95
96 96 def test_title_change(self):
97 97 self.log_user()
98 98 old_title = 'RhodeCode'
99 99 new_title = old_title + '_changed'
100 100 old_realm = 'RhodeCode authentication'
101 101
102 102 for new_title in ['Changed', 'Żółwik', old_title]:
103 103 response = self.app.post(url('admin_setting', setting_id='global'),
104 104 params=dict(
105 105 _method='put',
106 106 rhodecode_title=new_title,
107 107 rhodecode_realm=old_realm,
108 108 rhodecode_ga_code=''
109 109 ))
110 110
111 111 self.checkSessionFlash(response, 'Updated application settings')
112 112 self.assertEqual(RhodeCodeSettings
113 113 .get_app_settings()['rhodecode_title'],
114 114 new_title.decode('utf-8'))
115 115
116 116 response = response.follow()
117 117 self.assertTrue("""<h1><a href="/">%s</a></h1>""" % new_title
118 118 in response.body)
119 119
120 120
121 121 def test_my_account(self):
122 122 self.log_user()
123 123 response = self.app.get(url('admin_settings_my_account'))
124 124
125 125 self.assertTrue('value="test_admin' in response.body)
126 126
127 127 def test_my_account_update(self):
128 128 self.log_user()
129 129
130 130 new_email = 'new@mail.pl'
131 131 new_name = 'NewName'
132 132 new_lastname = 'NewLastname'
133 133 new_password = 'test123'
134 134
135 135
136 136 response = self.app.post(url('admin_settings_my_account_update'),
137 137 params=dict(_method='put',
138 138 username='test_admin',
139 139 new_password=new_password,
140 password_confirmation = new_password,
140 141 password='',
141 142 name=new_name,
142 143 lastname=new_lastname,
143 144 email=new_email,))
144 145 response.follow()
145 146
146 147 assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
147 148 user = self.sa.query(User).filter(User.username == 'test_admin').one()
148 149 assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email)
149 150 assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name)
150 151 assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname)
151 152 assert check_password(new_password, user.password) is True, 'password field mismatch %s vs %s' % (user.password, new_password)
152 153
153 154 #bring back the admin settings
154 155 old_email = 'test_admin@mail.com'
155 156 old_name = 'RhodeCode'
156 157 old_lastname = 'Admin'
157 158 old_password = 'test12'
158 159
159 160 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
160 161 _method='put',
161 162 username='test_admin',
162 163 new_password=old_password,
164 password_confirmation = old_password,
163 165 password='',
164 166 name=old_name,
165 167 lastname=old_lastname,
166 168 email=old_email,))
167 169
168 170 response.follow()
169 171 self.checkSessionFlash(response,
170 172 'Your account was updated successfully')
171 173
172 174 user = self.sa.query(User).filter(User.username == 'test_admin').one()
173 175 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
174 176
175 177 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
176 178 assert user.name == old_name, 'updated field mismatch %s vs %s' % (user.name, old_name)
177 179 assert user.lastname == old_lastname, 'updated field mismatch %s vs %s' % (user.lastname, old_lastname)
178 180 assert check_password(old_password, user.password) is True , 'password updated field mismatch %s vs %s' % (user.password, old_password)
179 181
180 182
181 183 def test_my_account_update_err_email_exists(self):
182 184 self.log_user()
183 185
184 186 new_email = 'test_regular@mail.com'#already exisitn email
185 187 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
186 188 _method='put',
187 189 username='test_admin',
188 190 new_password='test12',
191 password_confirmation = 'test122',
189 192 name='NewName',
190 193 lastname='NewLastname',
191 194 email=new_email,))
192 195
193 196 assert 'This e-mail address is already taken' in response.body, 'Missing error message about existing email'
194 197
195 198
196 199 def test_my_account_update_err(self):
197 200 self.log_user('test_regular2', 'test12')
198 201
199 202 new_email = 'newmail.pl'
200 203 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
201 204 _method='put',
202 205 username='test_admin',
203 206 new_password='test12',
207 password_confirmation = 'test122',
204 208 name='NewName',
205 209 lastname='NewLastname',
206 210 email=new_email,))
207 211 assert 'An email address must contain a single @' in response.body, 'Missing error message about wrong email'
208 212 assert 'This username already exists' in response.body, 'Missing error message about existing user'
@@ -1,119 +1,122 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import User
3 3 from rhodecode.lib.auth import check_password
4 4 from sqlalchemy.orm.exc import NoResultFound
5 5
6 6 class TestAdminUsersController(TestController):
7 7
8 8 def test_index(self):
9 9 response = self.app.get(url('users'))
10 10 # Test response...
11 11
12 12 def test_index_as_xml(self):
13 13 response = self.app.get(url('formatted_users', format='xml'))
14 14
15 15 def test_create(self):
16 16 self.log_user()
17 17 username = 'newtestuser'
18 18 password = 'test12'
19 password_confirmation = password
19 20 name = 'name'
20 21 lastname = 'lastname'
21 22 email = 'mail@mail.com'
22 23
23 24 response = self.app.post(url('users'), {'username':username,
24 25 'password':password,
26 'password_confirmation':password_confirmation,
25 27 'name':name,
26 28 'active':True,
27 29 'lastname':lastname,
28 30 'email':email})
29 31
30 32
31 33 assert '''created user %s''' % (username) in response.session['flash'][0], 'No flash message about new user'
32 34
33 35 new_user = self.sa.query(User).filter(User.username == username).one()
34 36
35 37
36 38 assert new_user.username == username, 'wrong info about username'
37 39 assert check_password(password, new_user.password) == True , 'wrong info about password'
38 40 assert new_user.name == name, 'wrong info about name'
39 41 assert new_user.lastname == lastname, 'wrong info about lastname'
40 42 assert new_user.email == email, 'wrong info about email'
41 43
42 44
43 45 response.follow()
44 46 response = response.follow()
45 47 assert """edit">newtestuser</a>""" in response.body
46 48
47 49 def test_create_err(self):
48 50 self.log_user()
49 51 username = 'new_user'
50 52 password = ''
51 53 name = 'name'
52 54 lastname = 'lastname'
53 55 email = 'errmail.com'
54 56
55 57 response = self.app.post(url('users'), {'username':username,
56 58 'password':password,
57 59 'name':name,
58 60 'active':False,
59 61 'lastname':lastname,
60 62 'email':email})
61 63
62 64 assert """<span class="error-message">Invalid username</span>""" in response.body
63 65 assert """<span class="error-message">Please enter a value</span>""" in response.body
64 66 assert """<span class="error-message">An email address must contain a single @</span>""" in response.body
65 67
66 68 def get_user():
67 69 self.sa.query(User).filter(User.username == username).one()
68 70
69 71 self.assertRaises(NoResultFound, get_user), 'found user in database'
70 72
71 73 def test_new(self):
72 74 response = self.app.get(url('new_user'))
73 75
74 76 def test_new_as_xml(self):
75 77 response = self.app.get(url('formatted_new_user', format='xml'))
76 78
77 79 def test_update(self):
78 80 response = self.app.put(url('user', id=1))
79 81
80 82 def test_update_browser_fakeout(self):
81 83 response = self.app.post(url('user', id=1), params=dict(_method='put'))
82 84
83 85 def test_delete(self):
84 86 self.log_user()
85 87 username = 'newtestuserdeleteme'
86 88 password = 'test12'
87 89 name = 'name'
88 90 lastname = 'lastname'
89 91 email = 'todeletemail@mail.com'
90 92
91 93 response = self.app.post(url('users'), {'username':username,
92 94 'password':password,
95 'password_confirmation':password,
93 96 'name':name,
94 97 'active':True,
95 98 'lastname':lastname,
96 99 'email':email})
97 100
98 101 response = response.follow()
99 102
100 103 new_user = self.sa.query(User).filter(User.username == username).one()
101 104 response = self.app.delete(url('user', id=new_user.user_id))
102 105
103 106 assert """successfully deleted user""" in response.session['flash'][0], 'No info about user deletion'
104 107
105 108
106 109 def test_delete_browser_fakeout(self):
107 110 response = self.app.post(url('user', id=1), params=dict(_method='delete'))
108 111
109 112 def test_show(self):
110 113 response = self.app.get(url('user', id=1))
111 114
112 115 def test_show_as_xml(self):
113 116 response = self.app.get(url('formatted_user', id=1, format='xml'))
114 117
115 118 def test_edit(self):
116 119 response = self.app.get(url('edit_user', id=1))
117 120
118 121 def test_edit_as_xml(self):
119 122 response = self.app.get(url('formatted_edit_user', id=1, format='xml'))
@@ -1,412 +1,401 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.tests.test_hg_operations
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Test suite for making push/pull operations
7 7
8 8 :created_on: Dec 30, 2010
9 9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 10 :license: GPLv3, see COPYING for more details.
11 11 """
12 12 # This program is free software: you can redistribute it and/or modify
13 13 # it under the terms of the GNU General Public License as published by
14 14 # the Free Software Foundation, either version 3 of the License, or
15 15 # (at your option) any later version.
16 16 #
17 17 # This program is distributed in the hope that it will be useful,
18 18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 20 # GNU General Public License for more details.
21 21 #
22 22 # You should have received a copy of the GNU General Public License
23 23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 24
25 25 import os
26 26 import time
27 27 import sys
28 28 import shutil
29 29 import logging
30 30
31 31 from os.path import join as jn
32 32 from os.path import dirname as dn
33 33
34 34 from tempfile import _RandomNameSequence
35 35 from subprocess import Popen, PIPE
36 36
37 37 from paste.deploy import appconfig
38 38 from pylons import config
39 39 from sqlalchemy import engine_from_config
40 40
41 41 from rhodecode.lib.utils import add_cache
42 42 from rhodecode.model import init_model
43 43 from rhodecode.model import meta
44 44 from rhodecode.model.db import User, Repository, UserLog
45 45 from rhodecode.lib.auth import get_crypt_password
46 46
47 47 from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO
48 48 from rhodecode.config.environment import load_environment
49 49
50 50 rel_path = dn(dn(dn(os.path.abspath(__file__))))
51 conf = appconfig('config:development.ini', relative_to=rel_path)
51
52 conf = appconfig('config:%s' % sys.argv[1], relative_to=rel_path)
52 53 load_environment(conf.global_conf, conf.local_conf)
53 54
54 55 add_cache(conf)
55 56
56 57 USER = 'test_admin'
57 58 PASS = 'test12'
58 59 HOST = '127.0.0.1:5000'
59 DEBUG = True if sys.argv[1:] else False
60 DEBUG = False
60 61 print 'DEBUG:', DEBUG
61 62 log = logging.getLogger(__name__)
62 63
64 engine = engine_from_config(conf, 'sqlalchemy.db1.')
65 init_model(engine)
66 sa = meta.Session
63 67
64 68 class Command(object):
65 69
66 70 def __init__(self, cwd):
67 71 self.cwd = cwd
68 72
69 73 def execute(self, cmd, *args):
70 74 """Runs command on the system with given ``args``.
71 75 """
72 76
73 77 command = cmd + ' ' + ' '.join(args)
74 78 log.debug('Executing %s' % command)
75 79 if DEBUG:
76 80 print command
77 81 p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
78 82 stdout, stderr = p.communicate()
79 83 if DEBUG:
80 84 print stdout, stderr
81 85 return stdout, stderr
82 86
83 87
84 88 def test_wrapp(func):
85 89
86 90 def __wrapp(*args, **kwargs):
87 91 print '>>>%s' % func.__name__
88 92 try:
89 93 res = func(*args, **kwargs)
90 94 except Exception, e:
91 95 print ('###############\n-'
92 96 '--%s failed %s--\n'
93 97 '###############\n' % (func.__name__, e))
94 98 sys.exit()
95 99 print '++OK++'
96 100 return res
97 101 return __wrapp
98 102
99 def get_session():
100 engine = engine_from_config(conf, 'sqlalchemy.db1.')
101 init_model(engine)
102 sa = meta.Session
103 return sa
104
105 103
106 104 def create_test_user(force=True):
107 105 print '\tcreating test user'
108 sa = get_session()
109 106
110 user = sa.query(User).filter(User.username == USER).scalar()
107 user = User.get_by_username(USER)
111 108
112 109 if force and user is not None:
113 110 print '\tremoving current user'
114 for repo in sa.query(Repository).filter(Repository.user == user).all():
111 for repo in Repository.query().filter(Repository.user == user).all():
115 112 sa.delete(repo)
116 113 sa.delete(user)
117 114 sa.commit()
118 115
119 116 if user is None or force:
120 117 print '\tcreating new one'
121 118 new_usr = User()
122 119 new_usr.username = USER
123 120 new_usr.password = get_crypt_password(PASS)
124 121 new_usr.email = 'mail@mail.com'
125 122 new_usr.name = 'test'
126 123 new_usr.lastname = 'lasttestname'
127 124 new_usr.active = True
128 125 new_usr.admin = True
129 126 sa.add(new_usr)
130 127 sa.commit()
131 128
132 129 print '\tdone'
133 130
134 131
135 132 def create_test_repo(force=True):
136 133 from rhodecode.model.repo import RepoModel
137 sa = get_session()
138 134
139 user = sa.query(User).filter(User.username == USER).scalar()
135 user = User.get_by_username(USER)
140 136 if user is None:
141 137 raise Exception('user not found')
142 138
143 139
144 140 repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar()
145 141
146 142 if repo is None:
147 143 print '\trepo not found creating'
148 144
149 145 form_data = {'repo_name':HG_REPO,
150 146 'repo_type':'hg',
151 147 'private':False,
152 148 'clone_uri':'' }
153 149 rm = RepoModel(sa)
154 150 rm.base_path = '/home/hg'
155 151 rm.create(form_data, user)
156 152
157 153
158 154 def set_anonymous_access(enable=True):
159 sa = get_session()
160 user = sa.query(User).filter(User.username == 'default').one()
161 sa.expire(user)
155 user = User.get_by_username('default')
162 156 user.active = enable
163 157 sa.add(user)
164 158 sa.commit()
165 sa.remove()
166 import time;time.sleep(3)
167 159 print '\tanonymous access is now:', enable
168
160 if enable != User.get_by_username('default').active:
161 raise Exception('Cannot set anonymous access')
169 162
170 163 def get_anonymous_access():
171 sa = get_session()
172 obj1 = sa.query(User).filter(User.username == 'default').one()
173 sa.expire(obj1)
174 return obj1.active
164 user = User.get_by_username('default')
165 return user.active
175 166
176 167
177 168 #==============================================================================
178 169 # TESTS
179 170 #==============================================================================
180 171 @test_wrapp
181 172 def test_clone_with_credentials(no_errors=False):
182 173 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
183 174
184 175 try:
185 176 shutil.rmtree(path, ignore_errors=True)
186 177 os.makedirs(path)
187 178 #print 'made dirs %s' % jn(path)
188 179 except OSError:
189 180 raise
190 181
191 182 print '\tchecking if anonymous access is enabled'
192 183 anonymous_access = get_anonymous_access()
193 184 if anonymous_access:
194 185 print '\tenabled, disabling it '
195 186 set_anonymous_access(enable=False)
196 187 time.sleep(1)
197 188
198 189 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
199 190 {'user':USER,
200 191 'pass':PASS,
201 192 'host':HOST,
202 193 'cloned_repo':HG_REPO,
203 194 'dest':path}
204 195
205 196 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
206 197
207 198 if no_errors is False:
208 199 assert """adding file changes""" in stdout, 'no messages about cloning'
209 200 assert """abort""" not in stderr , 'got error from clone'
210 201
211 202
212 203 @test_wrapp
213 204 def test_clone_anonymous():
214 205 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
215 206
216 207 try:
217 208 shutil.rmtree(path, ignore_errors=True)
218 209 os.makedirs(path)
219 210 #print 'made dirs %s' % jn(path)
220 211 except OSError:
221 212 raise
222 213
223 214
224 215 print '\tchecking if anonymous access is enabled'
225 216 anonymous_access = get_anonymous_access()
226 217 if not anonymous_access:
227 218 print '\tnot enabled, enabling it '
228 219 set_anonymous_access(enable=True)
229 220 time.sleep(1)
230 221
231 222 clone_url = 'http://%(host)s/%(cloned_repo)s %(dest)s' % \
232 223 {'user':USER,
233 224 'pass':PASS,
234 225 'host':HOST,
235 226 'cloned_repo':HG_REPO,
236 227 'dest':path}
237 228
238 229 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
239 230
240 231 assert """adding file changes""" in stdout, 'no messages about cloning'
241 232 assert """abort""" not in stderr , 'got error from clone'
242 233
243 234 #disable if it was enabled
244 235 if not anonymous_access:
245 236 print '\tdisabling anonymous access'
246 237 set_anonymous_access(enable=False)
247 238
248 239 @test_wrapp
249 240 def test_clone_wrong_credentials():
250 241 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
251 242
252 243 try:
253 244 shutil.rmtree(path, ignore_errors=True)
254 245 os.makedirs(path)
255 246 #print 'made dirs %s' % jn(path)
256 247 except OSError:
257 248 raise
258 249
259 250 print '\tchecking if anonymous access is enabled'
260 251 anonymous_access = get_anonymous_access()
261 252 if anonymous_access:
262 253 print '\tenabled, disabling it '
263 254 set_anonymous_access(enable=False)
264 255
265 256 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
266 257 {'user':USER + 'error',
267 258 'pass':PASS,
268 259 'host':HOST,
269 260 'cloned_repo':HG_REPO,
270 261 'dest':path}
271 262
272 263 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
273 264
274 265 if not """abort: authorization failed""" in stderr:
275 266 raise Exception('Failure')
276 267
277 268 @test_wrapp
278 269 def test_pull():
279 270 pass
280 271
281 272 @test_wrapp
282 273 def test_push_modify_file(f_name='setup.py'):
283 274 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
284 275 modified_file = jn(TESTS_TMP_PATH, HG_REPO, f_name)
285 276 for i in xrange(5):
286 277 cmd = """echo 'added_line%s' >> %s""" % (i, modified_file)
287 278 Command(cwd).execute(cmd)
288 279
289 280 cmd = """hg ci -m 'changed file %s' %s """ % (i, modified_file)
290 281 Command(cwd).execute(cmd)
291 282
292 283 Command(cwd).execute('hg push %s' % jn(TESTS_TMP_PATH, HG_REPO))
293 284
294 285 @test_wrapp
295 286 def test_push_new_file(commits=15, with_clone=True):
296 287
297 288 if with_clone:
298 289 test_clone_with_credentials(no_errors=True)
299 290
300 291 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
301 292 added_file = jn(path, '%ssetupążźć.py' % _RandomNameSequence().next())
302 293
303 294 Command(cwd).execute('touch %s' % added_file)
304 295
305 296 Command(cwd).execute('hg add %s' % added_file)
306 297
307 298 for i in xrange(commits):
308 299 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
309 300 Command(cwd).execute(cmd)
310 301
311 302 cmd = """hg ci -m 'commited new %s' -u '%s' %s """ % (i,
312 303 'Marcin Kuźminski <marcin@python-blog.com>',
313 304 added_file)
314 305 Command(cwd).execute(cmd)
315 306
316 307 push_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
317 308 {'user':USER,
318 309 'pass':PASS,
319 310 'host':HOST,
320 311 'cloned_repo':HG_REPO,
321 312 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
322 313
323 314 Command(cwd).execute('hg push --verbose --debug %s' % push_url)
324 315
325 316 @test_wrapp
326 317 def test_push_wrong_credentials():
327 318 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
328 319 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
329 320 {'user':USER + 'xxx',
330 321 'pass':PASS,
331 322 'host':HOST,
332 323 'cloned_repo':HG_REPO,
333 324 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
334 325
335 326 modified_file = jn(TESTS_TMP_PATH, HG_REPO, 'setup.py')
336 327 for i in xrange(5):
337 328 cmd = """echo 'added_line%s' >> %s""" % (i, modified_file)
338 329 Command(cwd).execute(cmd)
339 330
340 331 cmd = """hg ci -m 'commited %s' %s """ % (i, modified_file)
341 332 Command(cwd).execute(cmd)
342 333
343 334 Command(cwd).execute('hg push %s' % clone_url)
344 335
345 336 @test_wrapp
346 337 def test_push_wrong_path():
347 338 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
348 339 added_file = jn(path, 'somefile.py')
349 340
350 341 try:
351 342 shutil.rmtree(path, ignore_errors=True)
352 343 os.makedirs(path)
353 344 print '\tmade dirs %s' % jn(path)
354 345 except OSError:
355 346 raise
356 347
357 348 Command(cwd).execute("""echo '' > %s""" % added_file)
358 349 Command(cwd).execute("""hg init %s""" % path)
359 350 Command(cwd).execute("""hg add %s""" % added_file)
360 351
361 352 for i in xrange(2):
362 353 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
363 354 Command(cwd).execute(cmd)
364 355
365 356 cmd = """hg ci -m 'commited new %s' %s """ % (i, added_file)
366 357 Command(cwd).execute(cmd)
367 358
368 359 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
369 360 {'user':USER,
370 361 'pass':PASS,
371 362 'host':HOST,
372 363 'cloned_repo':HG_REPO + '_error',
373 364 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
374 365
375 366 stdout, stderr = Command(cwd).execute('hg push %s' % clone_url)
376 367 if not """abort: HTTP Error 403: Forbidden""" in stderr:
377 368 raise Exception('Failure')
378 369
379 370 @test_wrapp
380 371 def get_logs():
381 sa = get_session()
382 return len(sa.query(UserLog).all())
372 return UserLog.query().all()
383 373
384 374 @test_wrapp
385 375 def test_logs(initial):
386 sa = get_session()
387 logs = sa.query(UserLog).all()
388 operations = 7
389 if initial + operations != len(logs):
390 raise Exception("missing number of logs %s vs %s" % (initial, len(logs)))
376 logs = UserLog.query().all()
377 operations = 4
378 if len(initial) + operations != len(logs):
379 raise Exception("missing number of logs initial:%s vs current:%s" % \
380 (len(initial), len(logs)))
391 381
392 382
393 383 if __name__ == '__main__':
394 384 create_test_user(force=False)
395 385 create_test_repo()
396 386
397 387 initial_logs = get_logs()
388 print 'initial activity logs: %s' % len(initial_logs)
398 389
399 # test_push_modify_file()
390 #test_push_modify_file()
400 391 test_clone_with_credentials()
401 392 test_clone_wrong_credentials()
402 393
403
404 394 test_push_new_file(commits=2, with_clone=True)
405 395
406 396 test_clone_anonymous()
407 397 test_push_wrong_path()
408 398
409
410 399 test_push_wrong_credentials()
411 400
412 401 test_logs(initial_logs)
General Comments 0
You need to be logged in to leave comments. Login now