##// 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 # RhodeCode - Pylons environment configuration #
3 # RhodeCode - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 pdebug = false
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the address which should receive ##
12 ## Uncomment and replace with the address which should receive ##
13 ## any error reports after application crash ##
13 ## any error reports after application crash ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20
20
21 #smtp_server = mail.server.com
21 #smtp_server = mail.server.com
22 #smtp_username =
22 #smtp_username =
23 #smtp_password =
23 #smtp_password =
24 #smtp_port =
24 #smtp_port =
25 #smtp_use_tls = false
25 #smtp_use_tls = false
26 #smtp_use_ssl = true
26 #smtp_use_ssl = true
27 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 #smtp_auth =
27
29
28 [server:main]
30 [server:main]
29 ##nr of threads to spawn
31 ##nr of threads to spawn
30 threadpool_workers = 5
32 threadpool_workers = 5
31
33
32 ##max request before thread respawn
34 ##max request before thread respawn
33 threadpool_max_requests = 6
35 threadpool_max_requests = 6
34
36
35 ##option to use threads of process
37 ##option to use threads of process
36 use_threadpool = true
38 use_threadpool = true
37
39
38 use = egg:Paste#http
40 use = egg:Paste#http
39 host = 0.0.0.0
41 host = 0.0.0.0
40 port = 5000
42 port = 5000
41
43
42 [app:main]
44 [app:main]
43 use = egg:rhodecode
45 use = egg:rhodecode
44 full_stack = true
46 full_stack = true
45 static_files = true
47 static_files = true
46 lang=en
48 lang=en
47 cache_dir = %(here)s/data
49 cache_dir = %(here)s/data
48 index_dir = %(here)s/data/index
50 index_dir = %(here)s/data/index
49 app_instance_uuid = develop
51 app_instance_uuid = develop
50 cut_off_limit = 256000
52 cut_off_limit = 256000
51 force_https = false
53 force_https = false
52 commit_parse_limit = 25
54 commit_parse_limit = 25
53 use_gravatar = true
55 use_gravatar = true
54
56
55 ####################################
57 ####################################
56 ### CELERY CONFIG ####
58 ### CELERY CONFIG ####
57 ####################################
59 ####################################
58 use_celery = false
60 use_celery = false
59 broker.host = localhost
61 broker.host = localhost
60 broker.vhost = rabbitmqhost
62 broker.vhost = rabbitmqhost
61 broker.port = 5672
63 broker.port = 5672
62 broker.user = rabbitmq
64 broker.user = rabbitmq
63 broker.password = qweqwe
65 broker.password = qweqwe
64
66
65 celery.imports = rhodecode.lib.celerylib.tasks
67 celery.imports = rhodecode.lib.celerylib.tasks
66
68
67 celery.result.backend = amqp
69 celery.result.backend = amqp
68 celery.result.dburi = amqp://
70 celery.result.dburi = amqp://
69 celery.result.serialier = json
71 celery.result.serialier = json
70
72
71 #celery.send.task.error.emails = true
73 #celery.send.task.error.emails = true
72 #celery.amqp.task.result.expires = 18000
74 #celery.amqp.task.result.expires = 18000
73
75
74 celeryd.concurrency = 2
76 celeryd.concurrency = 2
75 #celeryd.log.file = celeryd.log
77 #celeryd.log.file = celeryd.log
76 celeryd.log.level = debug
78 celeryd.log.level = debug
77 celeryd.max.tasks.per.child = 1
79 celeryd.max.tasks.per.child = 1
78
80
79 #tasks will never be sent to the queue, but executed locally instead.
81 #tasks will never be sent to the queue, but executed locally instead.
80 celery.always.eager = false
82 celery.always.eager = false
81
83
82 ####################################
84 ####################################
83 ### BEAKER CACHE ####
85 ### BEAKER CACHE ####
84 ####################################
86 ####################################
85 beaker.cache.data_dir=%(here)s/data/cache/data
87 beaker.cache.data_dir=%(here)s/data/cache/data
86 beaker.cache.lock_dir=%(here)s/data/cache/lock
88 beaker.cache.lock_dir=%(here)s/data/cache/lock
87
89
88 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
90 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
89
91
90 beaker.cache.super_short_term.type=memory
92 beaker.cache.super_short_term.type=memory
91 beaker.cache.super_short_term.expire=10
93 beaker.cache.super_short_term.expire=10
92
94
93 beaker.cache.short_term.type=memory
95 beaker.cache.short_term.type=memory
94 beaker.cache.short_term.expire=60
96 beaker.cache.short_term.expire=60
95
97
96 beaker.cache.long_term.type=memory
98 beaker.cache.long_term.type=memory
97 beaker.cache.long_term.expire=36000
99 beaker.cache.long_term.expire=36000
98
100
99 beaker.cache.sql_cache_short.type=memory
101 beaker.cache.sql_cache_short.type=memory
100 beaker.cache.sql_cache_short.expire=10
102 beaker.cache.sql_cache_short.expire=10
101
103
102 beaker.cache.sql_cache_med.type=memory
104 beaker.cache.sql_cache_med.type=memory
103 beaker.cache.sql_cache_med.expire=360
105 beaker.cache.sql_cache_med.expire=360
104
106
105 beaker.cache.sql_cache_long.type=file
107 beaker.cache.sql_cache_long.type=file
106 beaker.cache.sql_cache_long.expire=3600
108 beaker.cache.sql_cache_long.expire=3600
107
109
108 ####################################
110 ####################################
109 ### BEAKER SESSION ####
111 ### BEAKER SESSION ####
110 ####################################
112 ####################################
111 ## Type of storage used for the session, current types are
113 ## Type of storage used for the session, current types are
112 ## dbm, file, memcached, database, and memory.
114 ## dbm, file, memcached, database, and memory.
113 ## The storage uses the Container API
115 ## The storage uses the Container API
114 ##that is also used by the cache system.
116 ##that is also used by the cache system.
115 beaker.session.type = file
117 beaker.session.type = file
116
118
117 beaker.session.key = rhodecode
119 beaker.session.key = rhodecode
118 beaker.session.secret = g654dcno0-9873jhgfreyu
120 beaker.session.secret = g654dcno0-9873jhgfreyu
119 beaker.session.timeout = 36000
121 beaker.session.timeout = 36000
120
122
121 ##auto save the session to not to use .save()
123 ##auto save the session to not to use .save()
122 beaker.session.auto = False
124 beaker.session.auto = False
123
125
124 ##true exire at browser close
126 ##true exire at browser close
125 #beaker.session.cookie_expires = 3600
127 #beaker.session.cookie_expires = 3600
126
128
127
129
128 ################################################################################
130 ################################################################################
129 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
131 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
130 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
132 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
131 ## execute malicious code after an exception is raised. ##
133 ## execute malicious code after an exception is raised. ##
132 ################################################################################
134 ################################################################################
133 #set debug = false
135 #set debug = false
134
136
135 ##################################
137 ##################################
136 ### LOGVIEW CONFIG ###
138 ### LOGVIEW CONFIG ###
137 ##################################
139 ##################################
138 logview.sqlalchemy = #faa
140 logview.sqlalchemy = #faa
139 logview.pylons.templating = #bfb
141 logview.pylons.templating = #bfb
140 logview.pylons.util = #eee
142 logview.pylons.util = #eee
141
143
142 #########################################################
144 #########################################################
143 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
145 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
144 #########################################################
146 #########################################################
145 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
147 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
146 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
148 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
147 sqlalchemy.db1.echo = false
149 sqlalchemy.db1.echo = false
148 sqlalchemy.db1.pool_recycle = 3600
150 sqlalchemy.db1.pool_recycle = 3600
149 sqlalchemy.convert_unicode = true
151 sqlalchemy.convert_unicode = true
150
152
151 ################################
153 ################################
152 ### LOGGING CONFIGURATION ####
154 ### LOGGING CONFIGURATION ####
153 ################################
155 ################################
154 [loggers]
156 [loggers]
155 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
157 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
156
158
157 [handlers]
159 [handlers]
158 keys = console, console_sql
160 keys = console, console_sql
159
161
160 [formatters]
162 [formatters]
161 keys = generic, color_formatter, color_formatter_sql
163 keys = generic, color_formatter, color_formatter_sql
162
164
163 #############
165 #############
164 ## LOGGERS ##
166 ## LOGGERS ##
165 #############
167 #############
166 [logger_root]
168 [logger_root]
167 level = NOTSET
169 level = NOTSET
168 handlers = console
170 handlers = console
169
171
170 [logger_routes]
172 [logger_routes]
171 level = DEBUG
173 level = DEBUG
172 handlers =
174 handlers =
173 qualname = routes.middleware
175 qualname = routes.middleware
174 # "level = DEBUG" logs the route matched and routing variables.
176 # "level = DEBUG" logs the route matched and routing variables.
175 propagate = 1
177 propagate = 1
176
178
177 [logger_beaker]
179 [logger_beaker]
178 level = DEBUG
180 level = DEBUG
179 handlers =
181 handlers =
180 qualname = beaker.container
182 qualname = beaker.container
181 propagate = 1
183 propagate = 1
182
184
183 [logger_templates]
185 [logger_templates]
184 level = INFO
186 level = INFO
185 handlers =
187 handlers =
186 qualname = pylons.templating
188 qualname = pylons.templating
187 propagate = 1
189 propagate = 1
188
190
189 [logger_rhodecode]
191 [logger_rhodecode]
190 level = DEBUG
192 level = DEBUG
191 handlers =
193 handlers =
192 qualname = rhodecode
194 qualname = rhodecode
193 propagate = 1
195 propagate = 1
194
196
195 [logger_sqlalchemy]
197 [logger_sqlalchemy]
196 level = INFO
198 level = INFO
197 handlers = console_sql
199 handlers = console_sql
198 qualname = sqlalchemy.engine
200 qualname = sqlalchemy.engine
199 propagate = 0
201 propagate = 0
200
202
201 ##############
203 ##############
202 ## HANDLERS ##
204 ## HANDLERS ##
203 ##############
205 ##############
204
206
205 [handler_console]
207 [handler_console]
206 class = StreamHandler
208 class = StreamHandler
207 args = (sys.stderr,)
209 args = (sys.stderr,)
208 level = DEBUG
210 level = DEBUG
209 formatter = color_formatter
211 formatter = color_formatter
210
212
211 [handler_console_sql]
213 [handler_console_sql]
212 class = StreamHandler
214 class = StreamHandler
213 args = (sys.stderr,)
215 args = (sys.stderr,)
214 level = DEBUG
216 level = DEBUG
215 formatter = color_formatter_sql
217 formatter = color_formatter_sql
216
218
217 ################
219 ################
218 ## FORMATTERS ##
220 ## FORMATTERS ##
219 ################
221 ################
220
222
221 [formatter_generic]
223 [formatter_generic]
222 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
224 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
223 datefmt = %Y-%m-%d %H:%M:%S
225 datefmt = %Y-%m-%d %H:%M:%S
224
226
225 [formatter_color_formatter]
227 [formatter_color_formatter]
226 class=rhodecode.lib.colored_formatter.ColorFormatter
228 class=rhodecode.lib.colored_formatter.ColorFormatter
227 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
229 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
228 datefmt = %Y-%m-%d %H:%M:%S
230 datefmt = %Y-%m-%d %H:%M:%S
229
231
230 [formatter_color_formatter_sql]
232 [formatter_color_formatter_sql]
231 class=rhodecode.lib.colored_formatter.ColorFormatterSql
233 class=rhodecode.lib.colored_formatter.ColorFormatterSql
232 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
234 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
233 datefmt = %Y-%m-%d %H:%M:%S
235 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,111 +1,362 b''
1 .. _api:
1 .. _api:
2
2
3
3
4 API
4 API
5 ===
5 ===
6
6
7
7
8 Starting from RhodeCode version 1.2 a simple API was implemented.
8 Starting from RhodeCode version 1.2 a simple API was implemented.
9 There's a single schema for calling all api methods. API is implemented
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 <your_server>/_admin/api
11 <your_server>/_admin/api
12
12
13
13
14 All clients need to send JSON data in such format::
14 All clients need to send JSON data in such format::
15
15
16 {
16 {
17 "api_key":"<api_key>",
17 "api_key":"<api_key>",
18 "method":"<method_name>",
18 "method":"<method_name>",
19 "args":{"<arg_key>":"<arg_val>"}
19 "args":{"<arg_key>":"<arg_val>"}
20 }
20 }
21
21
22 Example call for autopulling remotes repos using curl::
22 Example call for autopulling remotes repos using curl::
23 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
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 - *api_key* for access and permission validation.
26 - *api_key* for access and permission validation.
27 - *method* is name of method to call
27 - *method* is name of method to call
28 - *args* is an key:value list of arguments to pass to method
28 - *args* is an key:value list of arguments to pass to method
29
29
30 .. note::
30 .. note::
31
31
32 api_key can be found in your user account page
32 api_key can be found in your user account page
33
33
34
34
35 RhodeCode API will return always a JSON formatted answer::
35 RhodeCode API will return always a JSON formatted answer::
36
36
37 {
37 {
38 "result": "<result>",
38 "result": "<result>",
39 "error": null
39 "error": null
40 }
40 }
41
41
42 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
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 and result will be null.
44 and result will be null.
45
45
46 API METHODS
46 API METHODS
47 +++++++++++
47 +++++++++++
48
48
49
49
50 pull
50 pull
51 ----
51 ----
52
52
53 Pulls given repo from remote location. Can be used to automatically keep
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
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
73 belonging to user with admin rights
55 belonging to user with admin rights
74
56
75 INPUT::
57 INPUT::
76
58
77 api_key:"<api_key>"
59 api_key : "<api_key>"
78 method: "create_user"
60 method : "pull"
79 args: {"username": "<username>",
61 args : {
80 "password": "<password>",
62 "repo" : "<repo_name>"
81 "active": "<bool>",
63 }
82 "admin": "<bool>",
64
83 "name": "<firstname>",
65 OUTPUT::
84 "lastname": "<lastname>",
66
85 "email": "<useremail>"}
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 OUTPUT::
121 OUTPUT::
88
122
89 result:{"id": <newuserid>,
123 result: {
90 "msg":"created new user <username>"}
124 "msg" : "created new user <username>"
91 error:null
125 }
92
126 error: null
93
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 create_users_group
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 belonging to user with admin rights
204 belonging to user with admin rights
99
205
100 INPUT::
206 INPUT::
101
207
102 api_key:"<api_key>"
208 api_key : "<api_key>"
103 method: "create_user"
209 method : "create_users_group"
104 args: {"name": "<groupname>",
210 args: {
105 "active":"<bool>"}
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 OUTPUT::
258 OUTPUT::
108
259
109 result:{"id": <newusersgroupid>,
260 result: [
110 "msg":"created new users group <groupname>"}
261 {
111 error:null
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 # RhodeCode - Pylons environment configuration #
3 # RhodeCode - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 pdebug = false
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the address which should receive ##
12 ## Uncomment and replace with the address which should receive ##
13 ## any error reports after application crash ##
13 ## any error reports after application crash ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20
20
21 #smtp_server = mail.server.com
21 #smtp_server = mail.server.com
22 #smtp_username =
22 #smtp_username =
23 #smtp_password =
23 #smtp_password =
24 #smtp_port =
24 #smtp_port =
25 #smtp_use_tls = false
25 #smtp_use_tls = false
26 #smtp_use_ssl = true
26 #smtp_use_ssl = true
27 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 #smtp_auth =
27
29
28 [server:main]
30 [server:main]
29 ##nr of threads to spawn
31 ##nr of threads to spawn
30 threadpool_workers = 5
32 threadpool_workers = 5
31
33
32 ##max request before thread respawn
34 ##max request before thread respawn
33 threadpool_max_requests = 10
35 threadpool_max_requests = 10
34
36
35 ##option to use threads of process
37 ##option to use threads of process
36 use_threadpool = true
38 use_threadpool = true
37
39
38 use = egg:Paste#http
40 use = egg:Paste#http
39 host = 127.0.0.1
41 host = 127.0.0.1
40 port = 8001
42 port = 8001
41
43
42 [app:main]
44 [app:main]
43 use = egg:rhodecode
45 use = egg:rhodecode
44 full_stack = true
46 full_stack = true
45 static_files = true
47 static_files = true
46 lang=en
48 lang=en
47 cache_dir = %(here)s/data
49 cache_dir = %(here)s/data
48 index_dir = %(here)s/data/index
50 index_dir = %(here)s/data/index
49 app_instance_uuid = prod1234
51 app_instance_uuid = prod1234
50 cut_off_limit = 256000
52 cut_off_limit = 256000
51 force_https = false
53 force_https = false
52 commit_parse_limit = 50
54 commit_parse_limit = 50
53 use_gravatar = true
55 use_gravatar = true
54
56
55 ####################################
57 ####################################
56 ### CELERY CONFIG ####
58 ### CELERY CONFIG ####
57 ####################################
59 ####################################
58 use_celery = false
60 use_celery = false
59 broker.host = localhost
61 broker.host = localhost
60 broker.vhost = rabbitmqhost
62 broker.vhost = rabbitmqhost
61 broker.port = 5672
63 broker.port = 5672
62 broker.user = rabbitmq
64 broker.user = rabbitmq
63 broker.password = qweqwe
65 broker.password = qweqwe
64
66
65 celery.imports = rhodecode.lib.celerylib.tasks
67 celery.imports = rhodecode.lib.celerylib.tasks
66
68
67 celery.result.backend = amqp
69 celery.result.backend = amqp
68 celery.result.dburi = amqp://
70 celery.result.dburi = amqp://
69 celery.result.serialier = json
71 celery.result.serialier = json
70
72
71 #celery.send.task.error.emails = true
73 #celery.send.task.error.emails = true
72 #celery.amqp.task.result.expires = 18000
74 #celery.amqp.task.result.expires = 18000
73
75
74 celeryd.concurrency = 2
76 celeryd.concurrency = 2
75 #celeryd.log.file = celeryd.log
77 #celeryd.log.file = celeryd.log
76 celeryd.log.level = debug
78 celeryd.log.level = debug
77 celeryd.max.tasks.per.child = 1
79 celeryd.max.tasks.per.child = 1
78
80
79 #tasks will never be sent to the queue, but executed locally instead.
81 #tasks will never be sent to the queue, but executed locally instead.
80 celery.always.eager = false
82 celery.always.eager = false
81
83
82 ####################################
84 ####################################
83 ### BEAKER CACHE ####
85 ### BEAKER CACHE ####
84 ####################################
86 ####################################
85 beaker.cache.data_dir=%(here)s/data/cache/data
87 beaker.cache.data_dir=%(here)s/data/cache/data
86 beaker.cache.lock_dir=%(here)s/data/cache/lock
88 beaker.cache.lock_dir=%(here)s/data/cache/lock
87
89
88 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
90 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
89
91
90 beaker.cache.super_short_term.type=memory
92 beaker.cache.super_short_term.type=memory
91 beaker.cache.super_short_term.expire=10
93 beaker.cache.super_short_term.expire=10
92
94
93 beaker.cache.short_term.type=memory
95 beaker.cache.short_term.type=memory
94 beaker.cache.short_term.expire=60
96 beaker.cache.short_term.expire=60
95
97
96 beaker.cache.long_term.type=memory
98 beaker.cache.long_term.type=memory
97 beaker.cache.long_term.expire=36000
99 beaker.cache.long_term.expire=36000
98
100
99 beaker.cache.sql_cache_short.type=memory
101 beaker.cache.sql_cache_short.type=memory
100 beaker.cache.sql_cache_short.expire=10
102 beaker.cache.sql_cache_short.expire=10
101
103
102 beaker.cache.sql_cache_med.type=memory
104 beaker.cache.sql_cache_med.type=memory
103 beaker.cache.sql_cache_med.expire=360
105 beaker.cache.sql_cache_med.expire=360
104
106
105 beaker.cache.sql_cache_long.type=file
107 beaker.cache.sql_cache_long.type=file
106 beaker.cache.sql_cache_long.expire=3600
108 beaker.cache.sql_cache_long.expire=3600
107
109
108 ####################################
110 ####################################
109 ### BEAKER SESSION ####
111 ### BEAKER SESSION ####
110 ####################################
112 ####################################
111 ## Type of storage used for the session, current types are
113 ## Type of storage used for the session, current types are
112 ## dbm, file, memcached, database, and memory.
114 ## dbm, file, memcached, database, and memory.
113 ## The storage uses the Container API
115 ## The storage uses the Container API
114 ##that is also used by the cache system.
116 ##that is also used by the cache system.
115 beaker.session.type = file
117 beaker.session.type = file
116
118
117 beaker.session.key = rhodecode
119 beaker.session.key = rhodecode
118 beaker.session.secret = g654dcno0-9873jhgfreyu
120 beaker.session.secret = g654dcno0-9873jhgfreyu
119 beaker.session.timeout = 36000
121 beaker.session.timeout = 36000
120
122
121 ##auto save the session to not to use .save()
123 ##auto save the session to not to use .save()
122 beaker.session.auto = False
124 beaker.session.auto = False
123
125
124 ##true exire at browser close
126 ##true exire at browser close
125 #beaker.session.cookie_expires = 3600
127 #beaker.session.cookie_expires = 3600
126
128
127
129
128 ################################################################################
130 ################################################################################
129 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
131 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
130 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
132 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
131 ## execute malicious code after an exception is raised. ##
133 ## execute malicious code after an exception is raised. ##
132 ################################################################################
134 ################################################################################
133 set debug = false
135 set debug = false
134
136
135 ##################################
137 ##################################
136 ### LOGVIEW CONFIG ###
138 ### LOGVIEW CONFIG ###
137 ##################################
139 ##################################
138 logview.sqlalchemy = #faa
140 logview.sqlalchemy = #faa
139 logview.pylons.templating = #bfb
141 logview.pylons.templating = #bfb
140 logview.pylons.util = #eee
142 logview.pylons.util = #eee
141
143
142 #########################################################
144 #########################################################
143 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
145 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
144 #########################################################
146 #########################################################
145 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
147 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
146 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
148 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
147 sqlalchemy.db1.echo = false
149 sqlalchemy.db1.echo = false
148 sqlalchemy.db1.pool_recycle = 3600
150 sqlalchemy.db1.pool_recycle = 3600
149 sqlalchemy.convert_unicode = true
151 sqlalchemy.convert_unicode = true
150
152
151 ################################
153 ################################
152 ### LOGGING CONFIGURATION ####
154 ### LOGGING CONFIGURATION ####
153 ################################
155 ################################
154 [loggers]
156 [loggers]
155 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
157 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
156
158
157 [handlers]
159 [handlers]
158 keys = console, console_sql
160 keys = console, console_sql
159
161
160 [formatters]
162 [formatters]
161 keys = generic, color_formatter, color_formatter_sql
163 keys = generic, color_formatter, color_formatter_sql
162
164
163 #############
165 #############
164 ## LOGGERS ##
166 ## LOGGERS ##
165 #############
167 #############
166 [logger_root]
168 [logger_root]
167 level = NOTSET
169 level = NOTSET
168 handlers = console
170 handlers = console
169
171
170 [logger_routes]
172 [logger_routes]
171 level = DEBUG
173 level = DEBUG
172 handlers =
174 handlers =
173 qualname = routes.middleware
175 qualname = routes.middleware
174 # "level = DEBUG" logs the route matched and routing variables.
176 # "level = DEBUG" logs the route matched and routing variables.
175 propagate = 1
177 propagate = 1
176
178
177 [logger_beaker]
179 [logger_beaker]
178 level = DEBUG
180 level = DEBUG
179 handlers =
181 handlers =
180 qualname = beaker.container
182 qualname = beaker.container
181 propagate = 1
183 propagate = 1
182
184
183 [logger_templates]
185 [logger_templates]
184 level = INFO
186 level = INFO
185 handlers =
187 handlers =
186 qualname = pylons.templating
188 qualname = pylons.templating
187 propagate = 1
189 propagate = 1
188
190
189 [logger_rhodecode]
191 [logger_rhodecode]
190 level = DEBUG
192 level = DEBUG
191 handlers =
193 handlers =
192 qualname = rhodecode
194 qualname = rhodecode
193 propagate = 1
195 propagate = 1
194
196
195 [logger_sqlalchemy]
197 [logger_sqlalchemy]
196 level = INFO
198 level = INFO
197 handlers = console_sql
199 handlers = console_sql
198 qualname = sqlalchemy.engine
200 qualname = sqlalchemy.engine
199 propagate = 0
201 propagate = 0
200
202
201 ##############
203 ##############
202 ## HANDLERS ##
204 ## HANDLERS ##
203 ##############
205 ##############
204
206
205 [handler_console]
207 [handler_console]
206 class = StreamHandler
208 class = StreamHandler
207 args = (sys.stderr,)
209 args = (sys.stderr,)
208 level = INFO
210 level = INFO
209 formatter = generic
211 formatter = generic
210
212
211 [handler_console_sql]
213 [handler_console_sql]
212 class = StreamHandler
214 class = StreamHandler
213 args = (sys.stderr,)
215 args = (sys.stderr,)
214 level = WARN
216 level = WARN
215 formatter = generic
217 formatter = generic
216
218
217 ################
219 ################
218 ## FORMATTERS ##
220 ## FORMATTERS ##
219 ################
221 ################
220
222
221 [formatter_generic]
223 [formatter_generic]
222 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
224 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
223 datefmt = %Y-%m-%d %H:%M:%S
225 datefmt = %Y-%m-%d %H:%M:%S
224
226
225 [formatter_color_formatter]
227 [formatter_color_formatter]
226 class=rhodecode.lib.colored_formatter.ColorFormatter
228 class=rhodecode.lib.colored_formatter.ColorFormatter
227 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
229 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
228 datefmt = %Y-%m-%d %H:%M:%S
230 datefmt = %Y-%m-%d %H:%M:%S
229
231
230 [formatter_color_formatter_sql]
232 [formatter_color_formatter_sql]
231 class=rhodecode.lib.colored_formatter.ColorFormatterSql
233 class=rhodecode.lib.colored_formatter.ColorFormatterSql
232 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
234 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
233 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
235 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,242 +1,244 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode - Pylons environment configuration #
3 # RhodeCode - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 pdebug = false
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the address which should receive ##
12 ## Uncomment and replace with the address which should receive ##
13 ## any error reports after application crash ##
13 ## any error reports after application crash ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20
20
21 #smtp_server = mail.server.com
21 #smtp_server = mail.server.com
22 #smtp_username =
22 #smtp_username =
23 #smtp_password =
23 #smtp_password =
24 #smtp_port =
24 #smtp_port =
25 #smtp_use_tls = false
25 #smtp_use_tls = false
26 #smtp_use_ssl = true
26 #smtp_use_ssl = true
27 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 #smtp_auth =
27
29
28 [server:main]
30 [server:main]
29 ##nr of threads to spawn
31 ##nr of threads to spawn
30 threadpool_workers = 5
32 threadpool_workers = 5
31
33
32 ##max request before thread respawn
34 ##max request before thread respawn
33 threadpool_max_requests = 10
35 threadpool_max_requests = 10
34
36
35 ##option to use threads of process
37 ##option to use threads of process
36 use_threadpool = true
38 use_threadpool = true
37
39
38 use = egg:Paste#http
40 use = egg:Paste#http
39 host = 127.0.0.1
41 host = 127.0.0.1
40 port = 5000
42 port = 5000
41
43
42 [app:main]
44 [app:main]
43 use = egg:rhodecode
45 use = egg:rhodecode
44 full_stack = true
46 full_stack = true
45 static_files = true
47 static_files = true
46 lang=en
48 lang=en
47 cache_dir = %(here)s/data
49 cache_dir = %(here)s/data
48 index_dir = %(here)s/data/index
50 index_dir = %(here)s/data/index
49 app_instance_uuid = ${app_instance_uuid}
51 app_instance_uuid = ${app_instance_uuid}
50 cut_off_limit = 256000
52 cut_off_limit = 256000
51 force_https = false
53 force_https = false
52 commit_parse_limit = 50
54 commit_parse_limit = 50
53 use_gravatar = true
55 use_gravatar = true
54
56
55 ####################################
57 ####################################
56 ### CELERY CONFIG ####
58 ### CELERY CONFIG ####
57 ####################################
59 ####################################
58 use_celery = false
60 use_celery = false
59 broker.host = localhost
61 broker.host = localhost
60 broker.vhost = rabbitmqhost
62 broker.vhost = rabbitmqhost
61 broker.port = 5672
63 broker.port = 5672
62 broker.user = rabbitmq
64 broker.user = rabbitmq
63 broker.password = qweqwe
65 broker.password = qweqwe
64
66
65 celery.imports = rhodecode.lib.celerylib.tasks
67 celery.imports = rhodecode.lib.celerylib.tasks
66
68
67 celery.result.backend = amqp
69 celery.result.backend = amqp
68 celery.result.dburi = amqp://
70 celery.result.dburi = amqp://
69 celery.result.serialier = json
71 celery.result.serialier = json
70
72
71 #celery.send.task.error.emails = true
73 #celery.send.task.error.emails = true
72 #celery.amqp.task.result.expires = 18000
74 #celery.amqp.task.result.expires = 18000
73
75
74 celeryd.concurrency = 2
76 celeryd.concurrency = 2
75 #celeryd.log.file = celeryd.log
77 #celeryd.log.file = celeryd.log
76 celeryd.log.level = debug
78 celeryd.log.level = debug
77 celeryd.max.tasks.per.child = 1
79 celeryd.max.tasks.per.child = 1
78
80
79 #tasks will never be sent to the queue, but executed locally instead.
81 #tasks will never be sent to the queue, but executed locally instead.
80 celery.always.eager = false
82 celery.always.eager = false
81
83
82 ####################################
84 ####################################
83 ### BEAKER CACHE ####
85 ### BEAKER CACHE ####
84 ####################################
86 ####################################
85 beaker.cache.data_dir=%(here)s/data/cache/data
87 beaker.cache.data_dir=%(here)s/data/cache/data
86 beaker.cache.lock_dir=%(here)s/data/cache/lock
88 beaker.cache.lock_dir=%(here)s/data/cache/lock
87
89
88 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
90 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
89
91
90 beaker.cache.super_short_term.type=memory
92 beaker.cache.super_short_term.type=memory
91 beaker.cache.super_short_term.expire=10
93 beaker.cache.super_short_term.expire=10
92
94
93 beaker.cache.short_term.type=memory
95 beaker.cache.short_term.type=memory
94 beaker.cache.short_term.expire=60
96 beaker.cache.short_term.expire=60
95
97
96 beaker.cache.long_term.type=memory
98 beaker.cache.long_term.type=memory
97 beaker.cache.long_term.expire=36000
99 beaker.cache.long_term.expire=36000
98
100
99 beaker.cache.sql_cache_short.type=memory
101 beaker.cache.sql_cache_short.type=memory
100 beaker.cache.sql_cache_short.expire=10
102 beaker.cache.sql_cache_short.expire=10
101
103
102 beaker.cache.sql_cache_med.type=memory
104 beaker.cache.sql_cache_med.type=memory
103 beaker.cache.sql_cache_med.expire=360
105 beaker.cache.sql_cache_med.expire=360
104
106
105 beaker.cache.sql_cache_long.type=file
107 beaker.cache.sql_cache_long.type=file
106 beaker.cache.sql_cache_long.expire=3600
108 beaker.cache.sql_cache_long.expire=3600
107
109
108 ####################################
110 ####################################
109 ### BEAKER SESSION ####
111 ### BEAKER SESSION ####
110 ####################################
112 ####################################
111 ## Type of storage used for the session, current types are
113 ## Type of storage used for the session, current types are
112 ## dbm, file, memcached, database, and memory.
114 ## dbm, file, memcached, database, and memory.
113 ## The storage uses the Container API
115 ## The storage uses the Container API
114 ##that is also used by the cache system.
116 ##that is also used by the cache system.
115 beaker.session.type = file
117 beaker.session.type = file
116
118
117 beaker.session.key = rhodecode
119 beaker.session.key = rhodecode
118 beaker.session.secret = ${app_instance_secret}
120 beaker.session.secret = ${app_instance_secret}
119 beaker.session.timeout = 36000
121 beaker.session.timeout = 36000
120
122
121 ##auto save the session to not to use .save()
123 ##auto save the session to not to use .save()
122 beaker.session.auto = False
124 beaker.session.auto = False
123
125
124 ##true exire at browser close
126 ##true exire at browser close
125 #beaker.session.cookie_expires = 3600
127 #beaker.session.cookie_expires = 3600
126
128
127
129
128 ################################################################################
130 ################################################################################
129 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
131 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
130 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
132 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
131 ## execute malicious code after an exception is raised. ##
133 ## execute malicious code after an exception is raised. ##
132 ################################################################################
134 ################################################################################
133 set debug = false
135 set debug = false
134
136
135 ##################################
137 ##################################
136 ### LOGVIEW CONFIG ###
138 ### LOGVIEW CONFIG ###
137 ##################################
139 ##################################
138 logview.sqlalchemy = #faa
140 logview.sqlalchemy = #faa
139 logview.pylons.templating = #bfb
141 logview.pylons.templating = #bfb
140 logview.pylons.util = #eee
142 logview.pylons.util = #eee
141
143
142 #########################################################
144 #########################################################
143 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
145 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
144 #########################################################
146 #########################################################
145
147
146 # SQLITE [default]
148 # SQLITE [default]
147 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
149 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
148
150
149 # POSTGRES
151 # POSTGRESQL
150 # sqlalchemy.db1.url = postgresql://user:pass@localhost/rhodecode
152 # sqlalchemy.db1.url = postgresql://user:pass@localhost/rhodecode
151
153
152 # MySQL
154 # MySQL
153 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
155 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
154
156
155
157
156 sqlalchemy.db1.echo = false
158 sqlalchemy.db1.echo = false
157 sqlalchemy.db1.pool_recycle = 3600
159 sqlalchemy.db1.pool_recycle = 3600
158 sqlalchemy.convert_unicode = true
160 sqlalchemy.convert_unicode = true
159
161
160 ################################
162 ################################
161 ### LOGGING CONFIGURATION ####
163 ### LOGGING CONFIGURATION ####
162 ################################
164 ################################
163 [loggers]
165 [loggers]
164 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
166 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
165
167
166 [handlers]
168 [handlers]
167 keys = console, console_sql
169 keys = console, console_sql
168
170
169 [formatters]
171 [formatters]
170 keys = generic, color_formatter, color_formatter_sql
172 keys = generic, color_formatter, color_formatter_sql
171
173
172 #############
174 #############
173 ## LOGGERS ##
175 ## LOGGERS ##
174 #############
176 #############
175 [logger_root]
177 [logger_root]
176 level = NOTSET
178 level = NOTSET
177 handlers = console
179 handlers = console
178
180
179 [logger_routes]
181 [logger_routes]
180 level = DEBUG
182 level = DEBUG
181 handlers =
183 handlers =
182 qualname = routes.middleware
184 qualname = routes.middleware
183 # "level = DEBUG" logs the route matched and routing variables.
185 # "level = DEBUG" logs the route matched and routing variables.
184 propagate = 1
186 propagate = 1
185
187
186 [logger_beaker]
188 [logger_beaker]
187 level = DEBUG
189 level = DEBUG
188 handlers =
190 handlers =
189 qualname = beaker.container
191 qualname = beaker.container
190 propagate = 1
192 propagate = 1
191
193
192 [logger_templates]
194 [logger_templates]
193 level = INFO
195 level = INFO
194 handlers =
196 handlers =
195 qualname = pylons.templating
197 qualname = pylons.templating
196 propagate = 1
198 propagate = 1
197
199
198 [logger_rhodecode]
200 [logger_rhodecode]
199 level = DEBUG
201 level = DEBUG
200 handlers =
202 handlers =
201 qualname = rhodecode
203 qualname = rhodecode
202 propagate = 1
204 propagate = 1
203
205
204 [logger_sqlalchemy]
206 [logger_sqlalchemy]
205 level = INFO
207 level = INFO
206 handlers = console_sql
208 handlers = console_sql
207 qualname = sqlalchemy.engine
209 qualname = sqlalchemy.engine
208 propagate = 0
210 propagate = 0
209
211
210 ##############
212 ##############
211 ## HANDLERS ##
213 ## HANDLERS ##
212 ##############
214 ##############
213
215
214 [handler_console]
216 [handler_console]
215 class = StreamHandler
217 class = StreamHandler
216 args = (sys.stderr,)
218 args = (sys.stderr,)
217 level = INFO
219 level = INFO
218 formatter = color_formatter
220 formatter = color_formatter
219
221
220 [handler_console_sql]
222 [handler_console_sql]
221 class = StreamHandler
223 class = StreamHandler
222 args = (sys.stderr,)
224 args = (sys.stderr,)
223 level = WARN
225 level = WARN
224 formatter = color_formatter_sql
226 formatter = color_formatter_sql
225
227
226 ################
228 ################
227 ## FORMATTERS ##
229 ## FORMATTERS ##
228 ################
230 ################
229
231
230 [formatter_generic]
232 [formatter_generic]
231 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
233 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
232 datefmt = %Y-%m-%d %H:%M:%S
234 datefmt = %Y-%m-%d %H:%M:%S
233
235
234 [formatter_color_formatter]
236 [formatter_color_formatter]
235 class=rhodecode.lib.colored_formatter.ColorFormatter
237 class=rhodecode.lib.colored_formatter.ColorFormatter
236 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
238 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
237 datefmt = %Y-%m-%d %H:%M:%S
239 datefmt = %Y-%m-%d %H:%M:%S
238
240
239 [formatter_color_formatter_sql]
241 [formatter_color_formatter_sql]
240 class=rhodecode.lib.colored_formatter.ColorFormatterSql
242 class=rhodecode.lib.colored_formatter.ColorFormatterSql
241 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
243 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
242 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
244 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,421 +1,397 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos
3 rhodecode.controllers.admin.repos
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Admin controller for RhodeCode
6 Admin controller for RhodeCode
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29 from operator import itemgetter
30 from formencode import htmlfill
29 from formencode import htmlfill
31
30
32 from paste.httpexceptions import HTTPInternalServerError
31 from paste.httpexceptions import HTTPInternalServerError
33 from pylons import request, response, session, tmpl_context as c, url
32 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
36
35
37 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 HasPermissionAnyDecorator
38 HasPermissionAnyDecorator
40 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
40 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 from rhodecode.lib.helpers import get_token
41 from rhodecode.lib.helpers import get_token
43 from rhodecode.model.db import User, Repository, UserFollowing, Group
42 from rhodecode.model.db import User, Repository, UserFollowing, Group
44 from rhodecode.model.forms import RepoForm
43 from rhodecode.model.forms import RepoForm
45 from rhodecode.model.scm import ScmModel
44 from rhodecode.model.scm import ScmModel
46 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.repo import RepoModel
47 from sqlalchemy.exc import IntegrityError
46 from sqlalchemy.exc import IntegrityError
48
47
49 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
50
49
51
50
52 class ReposController(BaseController):
51 class ReposController(BaseController):
53 """
52 """
54 REST Controller styled on the Atom Publishing Protocol"""
53 REST Controller styled on the Atom Publishing Protocol"""
55 # To properly map this controller, ensure your config/routing.py
54 # To properly map this controller, ensure your config/routing.py
56 # file has a resource setup:
55 # file has a resource setup:
57 # map.resource('repo', 'repos')
56 # map.resource('repo', 'repos')
58
57
59 @LoginRequired()
58 @LoginRequired()
60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
59 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 def __before__(self):
60 def __before__(self):
62 c.admin_user = session.get('admin_user')
61 c.admin_user = session.get('admin_user')
63 c.admin_username = session.get('admin_username')
62 c.admin_username = session.get('admin_username')
64 super(ReposController, self).__before__()
63 super(ReposController, self).__before__()
65
64
66 def __load_defaults(self):
65 def __load_defaults(self):
67 c.repo_groups = Group.groups_choices()
66 c.repo_groups = Group.groups_choices()
68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
67 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
69
68
70 repo_model = RepoModel()
69 repo_model = RepoModel()
71 c.users_array = repo_model.get_users_js()
70 c.users_array = repo_model.get_users_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
71 c.users_groups_array = repo_model.get_users_groups_js()
73
72
74 def __load_data(self, repo_name=None):
73 def __load_data(self, repo_name=None):
75 """
74 """
76 Load defaults settings for edit, and update
75 Load defaults settings for edit, and update
77
76
78 :param repo_name:
77 :param repo_name:
79 """
78 """
80 self.__load_defaults()
79 self.__load_defaults()
81
80
82 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
81 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
83 repo = db_repo.scm_instance
82 repo = db_repo.scm_instance
84
83
85 if c.repo_info is None:
84 if c.repo_info is None:
86 h.flash(_('%s repository is not mapped to db perhaps'
85 h.flash(_('%s repository is not mapped to db perhaps'
87 ' it was created or renamed from the filesystem'
86 ' it was created or renamed from the filesystem'
88 ' please run the application again'
87 ' please run the application again'
89 ' in order to rescan repositories') % repo_name,
88 ' in order to rescan repositories') % repo_name,
90 category='error')
89 category='error')
91
90
92 return redirect(url('repos'))
91 return redirect(url('repos'))
93
92
94 c.default_user_id = User.get_by_username('default').user_id
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 .filter(UserFollowing.user_id == c.default_user_id)\
95 .filter(UserFollowing.user_id == c.default_user_id)\
97 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
96 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
98
97
99 if c.repo_info.stats:
98 if c.repo_info.stats:
100 last_rev = c.repo_info.stats.stat_on_revision
99 last_rev = c.repo_info.stats.stat_on_revision
101 else:
100 else:
102 last_rev = 0
101 last_rev = 0
103 c.stats_revision = last_rev
102 c.stats_revision = last_rev
104
103
105 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
104 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
106
105
107 if last_rev == 0 or c.repo_last_rev == 0:
106 if last_rev == 0 or c.repo_last_rev == 0:
108 c.stats_percentage = 0
107 c.stats_percentage = 0
109 else:
108 else:
110 c.stats_percentage = '%.2f' % ((float((last_rev)) /
109 c.stats_percentage = '%.2f' % ((float((last_rev)) /
111 c.repo_last_rev) * 100)
110 c.repo_last_rev) * 100)
112
111
113 defaults = c.repo_info.get_dict()
112 defaults = RepoModel()._get_defaults(repo_name)
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
137 return defaults
113 return defaults
138
114
139 @HasPermissionAllDecorator('hg.admin')
115 @HasPermissionAllDecorator('hg.admin')
140 def index(self, format='html'):
116 def index(self, format='html'):
141 """GET /repos: All items in the collection"""
117 """GET /repos: All items in the collection"""
142 # url('repos')
118 # url('repos')
143
119
144 c.repos_list = ScmModel().get_repos(Repository.query()
120 c.repos_list = ScmModel().get_repos(Repository.query()
145 .order_by(Repository.repo_name)
121 .order_by(Repository.repo_name)
146 .all(), sort_key='name_sort')
122 .all(), sort_key='name_sort')
147 return render('admin/repos/repos.html')
123 return render('admin/repos/repos.html')
148
124
149 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
125 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
150 def create(self):
126 def create(self):
151 """
127 """
152 POST /repos: Create a new item"""
128 POST /repos: Create a new item"""
153 # url('repos')
129 # url('repos')
154 repo_model = RepoModel()
130 repo_model = RepoModel()
155 self.__load_defaults()
131 self.__load_defaults()
156 form_result = {}
132 form_result = {}
157 try:
133 try:
158 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
134 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
159 .to_python(dict(request.POST))
135 .to_python(dict(request.POST))
160 repo_model.create(form_result, self.rhodecode_user)
136 repo_model.create(form_result, self.rhodecode_user)
161 if form_result['clone_uri']:
137 if form_result['clone_uri']:
162 h.flash(_('created repository %s from %s') \
138 h.flash(_('created repository %s from %s') \
163 % (form_result['repo_name'], form_result['clone_uri']),
139 % (form_result['repo_name'], form_result['clone_uri']),
164 category='success')
140 category='success')
165 else:
141 else:
166 h.flash(_('created repository %s') % form_result['repo_name'],
142 h.flash(_('created repository %s') % form_result['repo_name'],
167 category='success')
143 category='success')
168
144
169 if request.POST.get('user_created'):
145 if request.POST.get('user_created'):
170 #created by regular non admin user
146 #created by regular non admin user
171 action_logger(self.rhodecode_user, 'user_created_repo',
147 action_logger(self.rhodecode_user, 'user_created_repo',
172 form_result['repo_name_full'], '', self.sa)
148 form_result['repo_name_full'], '', self.sa)
173 else:
149 else:
174 action_logger(self.rhodecode_user, 'admin_created_repo',
150 action_logger(self.rhodecode_user, 'admin_created_repo',
175 form_result['repo_name_full'], '', self.sa)
151 form_result['repo_name_full'], '', self.sa)
176
152
177 except formencode.Invalid, errors:
153 except formencode.Invalid, errors:
178
154
179 c.new_repo = errors.value['repo_name']
155 c.new_repo = errors.value['repo_name']
180
156
181 if request.POST.get('user_created'):
157 if request.POST.get('user_created'):
182 r = render('admin/repos/repo_add_create_repository.html')
158 r = render('admin/repos/repo_add_create_repository.html')
183 else:
159 else:
184 r = render('admin/repos/repo_add.html')
160 r = render('admin/repos/repo_add.html')
185
161
186 return htmlfill.render(
162 return htmlfill.render(
187 r,
163 r,
188 defaults=errors.value,
164 defaults=errors.value,
189 errors=errors.error_dict or {},
165 errors=errors.error_dict or {},
190 prefix_error=False,
166 prefix_error=False,
191 encoding="UTF-8")
167 encoding="UTF-8")
192
168
193 except Exception:
169 except Exception:
194 log.error(traceback.format_exc())
170 log.error(traceback.format_exc())
195 msg = _('error occurred during creation of repository %s') \
171 msg = _('error occurred during creation of repository %s') \
196 % form_result.get('repo_name')
172 % form_result.get('repo_name')
197 h.flash(msg, category='error')
173 h.flash(msg, category='error')
198 if request.POST.get('user_created'):
174 if request.POST.get('user_created'):
199 return redirect(url('home'))
175 return redirect(url('home'))
200 return redirect(url('repos'))
176 return redirect(url('repos'))
201
177
202 @HasPermissionAllDecorator('hg.admin')
178 @HasPermissionAllDecorator('hg.admin')
203 def new(self, format='html'):
179 def new(self, format='html'):
204 """GET /repos/new: Form to create a new item"""
180 """GET /repos/new: Form to create a new item"""
205 new_repo = request.GET.get('repo', '')
181 new_repo = request.GET.get('repo', '')
206 c.new_repo = repo_name_slug(new_repo)
182 c.new_repo = repo_name_slug(new_repo)
207 self.__load_defaults()
183 self.__load_defaults()
208 return render('admin/repos/repo_add.html')
184 return render('admin/repos/repo_add.html')
209
185
210 @HasPermissionAllDecorator('hg.admin')
186 @HasPermissionAllDecorator('hg.admin')
211 def update(self, repo_name):
187 def update(self, repo_name):
212 """
188 """
213 PUT /repos/repo_name: Update an existing item"""
189 PUT /repos/repo_name: Update an existing item"""
214 # Forms posted to this method should contain a hidden field:
190 # Forms posted to this method should contain a hidden field:
215 # <input type="hidden" name="_method" value="PUT" />
191 # <input type="hidden" name="_method" value="PUT" />
216 # Or using helpers:
192 # Or using helpers:
217 # h.form(url('repo', repo_name=ID),
193 # h.form(url('repo', repo_name=ID),
218 # method='put')
194 # method='put')
219 # url('repo', repo_name=ID)
195 # url('repo', repo_name=ID)
220 self.__load_defaults()
196 self.__load_defaults()
221 repo_model = RepoModel()
197 repo_model = RepoModel()
222 changed_name = repo_name
198 changed_name = repo_name
223 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
199 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
224 repo_groups=c.repo_groups_choices)()
200 repo_groups=c.repo_groups_choices)()
225 try:
201 try:
226 form_result = _form.to_python(dict(request.POST))
202 form_result = _form.to_python(dict(request.POST))
227 repo = repo_model.update(repo_name, form_result)
203 repo = repo_model.update(repo_name, form_result)
228 invalidate_cache('get_repo_cached_%s' % repo_name)
204 invalidate_cache('get_repo_cached_%s' % repo_name)
229 h.flash(_('Repository %s updated successfully' % repo_name),
205 h.flash(_('Repository %s updated successfully' % repo_name),
230 category='success')
206 category='success')
231 changed_name = repo.repo_name
207 changed_name = repo.repo_name
232 action_logger(self.rhodecode_user, 'admin_updated_repo',
208 action_logger(self.rhodecode_user, 'admin_updated_repo',
233 changed_name, '', self.sa)
209 changed_name, '', self.sa)
234
210
235 except formencode.Invalid, errors:
211 except formencode.Invalid, errors:
236 defaults = self.__load_data(repo_name)
212 defaults = self.__load_data(repo_name)
237 defaults.update(errors.value)
213 defaults.update(errors.value)
238 return htmlfill.render(
214 return htmlfill.render(
239 render('admin/repos/repo_edit.html'),
215 render('admin/repos/repo_edit.html'),
240 defaults=defaults,
216 defaults=defaults,
241 errors=errors.error_dict or {},
217 errors=errors.error_dict or {},
242 prefix_error=False,
218 prefix_error=False,
243 encoding="UTF-8")
219 encoding="UTF-8")
244
220
245 except Exception:
221 except Exception:
246 log.error(traceback.format_exc())
222 log.error(traceback.format_exc())
247 h.flash(_('error occurred during update of repository %s') \
223 h.flash(_('error occurred during update of repository %s') \
248 % repo_name, category='error')
224 % repo_name, category='error')
249 return redirect(url('edit_repo', repo_name=changed_name))
225 return redirect(url('edit_repo', repo_name=changed_name))
250
226
251 @HasPermissionAllDecorator('hg.admin')
227 @HasPermissionAllDecorator('hg.admin')
252 def delete(self, repo_name):
228 def delete(self, repo_name):
253 """
229 """
254 DELETE /repos/repo_name: Delete an existing item"""
230 DELETE /repos/repo_name: Delete an existing item"""
255 # Forms posted to this method should contain a hidden field:
231 # Forms posted to this method should contain a hidden field:
256 # <input type="hidden" name="_method" value="DELETE" />
232 # <input type="hidden" name="_method" value="DELETE" />
257 # Or using helpers:
233 # Or using helpers:
258 # h.form(url('repo', repo_name=ID),
234 # h.form(url('repo', repo_name=ID),
259 # method='delete')
235 # method='delete')
260 # url('repo', repo_name=ID)
236 # url('repo', repo_name=ID)
261
237
262 repo_model = RepoModel()
238 repo_model = RepoModel()
263 repo = repo_model.get_by_repo_name(repo_name)
239 repo = repo_model.get_by_repo_name(repo_name)
264 if not repo:
240 if not repo:
265 h.flash(_('%s repository is not mapped to db perhaps'
241 h.flash(_('%s repository is not mapped to db perhaps'
266 ' it was moved or renamed from the filesystem'
242 ' it was moved or renamed from the filesystem'
267 ' please run the application again'
243 ' please run the application again'
268 ' in order to rescan repositories') % repo_name,
244 ' in order to rescan repositories') % repo_name,
269 category='error')
245 category='error')
270
246
271 return redirect(url('repos'))
247 return redirect(url('repos'))
272 try:
248 try:
273 action_logger(self.rhodecode_user, 'admin_deleted_repo',
249 action_logger(self.rhodecode_user, 'admin_deleted_repo',
274 repo_name, '', self.sa)
250 repo_name, '', self.sa)
275 repo_model.delete(repo)
251 repo_model.delete(repo)
276 invalidate_cache('get_repo_cached_%s' % repo_name)
252 invalidate_cache('get_repo_cached_%s' % repo_name)
277 h.flash(_('deleted repository %s') % repo_name, category='success')
253 h.flash(_('deleted repository %s') % repo_name, category='success')
278
254
279 except IntegrityError, e:
255 except IntegrityError, e:
280 if e.message.find('repositories_fork_id_fkey'):
256 if e.message.find('repositories_fork_id_fkey'):
281 log.error(traceback.format_exc())
257 log.error(traceback.format_exc())
282 h.flash(_('Cannot delete %s it still contains attached '
258 h.flash(_('Cannot delete %s it still contains attached '
283 'forks') % repo_name,
259 'forks') % repo_name,
284 category='warning')
260 category='warning')
285 else:
261 else:
286 log.error(traceback.format_exc())
262 log.error(traceback.format_exc())
287 h.flash(_('An error occurred during '
263 h.flash(_('An error occurred during '
288 'deletion of %s') % repo_name,
264 'deletion of %s') % repo_name,
289 category='error')
265 category='error')
290
266
291 except Exception, e:
267 except Exception, e:
292 log.error(traceback.format_exc())
268 log.error(traceback.format_exc())
293 h.flash(_('An error occurred during deletion of %s') % repo_name,
269 h.flash(_('An error occurred during deletion of %s') % repo_name,
294 category='error')
270 category='error')
295
271
296 return redirect(url('repos'))
272 return redirect(url('repos'))
297
273
298 @HasPermissionAllDecorator('hg.admin')
274 @HasPermissionAllDecorator('hg.admin')
299 def delete_perm_user(self, repo_name):
275 def delete_perm_user(self, repo_name):
300 """
276 """
301 DELETE an existing repository permission user
277 DELETE an existing repository permission user
302
278
303 :param repo_name:
279 :param repo_name:
304 """
280 """
305
281
306 try:
282 try:
307 repo_model = RepoModel()
283 repo_model = RepoModel()
308 repo_model.delete_perm_user(request.POST, repo_name)
284 repo_model.delete_perm_user(request.POST, repo_name)
309 except Exception, e:
285 except Exception, e:
310 h.flash(_('An error occurred during deletion of repository user'),
286 h.flash(_('An error occurred during deletion of repository user'),
311 category='error')
287 category='error')
312 raise HTTPInternalServerError()
288 raise HTTPInternalServerError()
313
289
314 @HasPermissionAllDecorator('hg.admin')
290 @HasPermissionAllDecorator('hg.admin')
315 def delete_perm_users_group(self, repo_name):
291 def delete_perm_users_group(self, repo_name):
316 """
292 """
317 DELETE an existing repository permission users group
293 DELETE an existing repository permission users group
318
294
319 :param repo_name:
295 :param repo_name:
320 """
296 """
321 try:
297 try:
322 repo_model = RepoModel()
298 repo_model = RepoModel()
323 repo_model.delete_perm_users_group(request.POST, repo_name)
299 repo_model.delete_perm_users_group(request.POST, repo_name)
324 except Exception, e:
300 except Exception, e:
325 h.flash(_('An error occurred during deletion of repository'
301 h.flash(_('An error occurred during deletion of repository'
326 ' users groups'),
302 ' users groups'),
327 category='error')
303 category='error')
328 raise HTTPInternalServerError()
304 raise HTTPInternalServerError()
329
305
330 @HasPermissionAllDecorator('hg.admin')
306 @HasPermissionAllDecorator('hg.admin')
331 def repo_stats(self, repo_name):
307 def repo_stats(self, repo_name):
332 """
308 """
333 DELETE an existing repository statistics
309 DELETE an existing repository statistics
334
310
335 :param repo_name:
311 :param repo_name:
336 """
312 """
337
313
338 try:
314 try:
339 repo_model = RepoModel()
315 repo_model = RepoModel()
340 repo_model.delete_stats(repo_name)
316 repo_model.delete_stats(repo_name)
341 except Exception, e:
317 except Exception, e:
342 h.flash(_('An error occurred during deletion of repository stats'),
318 h.flash(_('An error occurred during deletion of repository stats'),
343 category='error')
319 category='error')
344 return redirect(url('edit_repo', repo_name=repo_name))
320 return redirect(url('edit_repo', repo_name=repo_name))
345
321
346 @HasPermissionAllDecorator('hg.admin')
322 @HasPermissionAllDecorator('hg.admin')
347 def repo_cache(self, repo_name):
323 def repo_cache(self, repo_name):
348 """
324 """
349 INVALIDATE existing repository cache
325 INVALIDATE existing repository cache
350
326
351 :param repo_name:
327 :param repo_name:
352 """
328 """
353
329
354 try:
330 try:
355 ScmModel().mark_for_invalidation(repo_name)
331 ScmModel().mark_for_invalidation(repo_name)
356 except Exception, e:
332 except Exception, e:
357 h.flash(_('An error occurred during cache invalidation'),
333 h.flash(_('An error occurred during cache invalidation'),
358 category='error')
334 category='error')
359 return redirect(url('edit_repo', repo_name=repo_name))
335 return redirect(url('edit_repo', repo_name=repo_name))
360
336
361 @HasPermissionAllDecorator('hg.admin')
337 @HasPermissionAllDecorator('hg.admin')
362 def repo_public_journal(self, repo_name):
338 def repo_public_journal(self, repo_name):
363 """
339 """
364 Set's this repository to be visible in public journal,
340 Set's this repository to be visible in public journal,
365 in other words assing default user to follow this repo
341 in other words assing default user to follow this repo
366
342
367 :param repo_name:
343 :param repo_name:
368 """
344 """
369
345
370 cur_token = request.POST.get('auth_token')
346 cur_token = request.POST.get('auth_token')
371 token = get_token()
347 token = get_token()
372 if cur_token == token:
348 if cur_token == token:
373 try:
349 try:
374 repo_id = Repository.get_by_repo_name(repo_name).repo_id
350 repo_id = Repository.get_by_repo_name(repo_name).repo_id
375 user_id = User.get_by_username('default').user_id
351 user_id = User.get_by_username('default').user_id
376 self.scm_model.toggle_following_repo(repo_id, user_id)
352 self.scm_model.toggle_following_repo(repo_id, user_id)
377 h.flash(_('Updated repository visibility in public journal'),
353 h.flash(_('Updated repository visibility in public journal'),
378 category='success')
354 category='success')
379 except:
355 except:
380 h.flash(_('An error occurred during setting this'
356 h.flash(_('An error occurred during setting this'
381 ' repository in public journal'),
357 ' repository in public journal'),
382 category='error')
358 category='error')
383
359
384 else:
360 else:
385 h.flash(_('Token mismatch'), category='error')
361 h.flash(_('Token mismatch'), category='error')
386 return redirect(url('edit_repo', repo_name=repo_name))
362 return redirect(url('edit_repo', repo_name=repo_name))
387
363
388 @HasPermissionAllDecorator('hg.admin')
364 @HasPermissionAllDecorator('hg.admin')
389 def repo_pull(self, repo_name):
365 def repo_pull(self, repo_name):
390 """
366 """
391 Runs task to update given repository with remote changes,
367 Runs task to update given repository with remote changes,
392 ie. make pull on remote location
368 ie. make pull on remote location
393
369
394 :param repo_name:
370 :param repo_name:
395 """
371 """
396 try:
372 try:
397 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
373 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
398 h.flash(_('Pulled from remote location'), category='success')
374 h.flash(_('Pulled from remote location'), category='success')
399 except Exception, e:
375 except Exception, e:
400 h.flash(_('An error occurred during pull from remote location'),
376 h.flash(_('An error occurred during pull from remote location'),
401 category='error')
377 category='error')
402
378
403 return redirect(url('edit_repo', repo_name=repo_name))
379 return redirect(url('edit_repo', repo_name=repo_name))
404
380
405 @HasPermissionAllDecorator('hg.admin')
381 @HasPermissionAllDecorator('hg.admin')
406 def show(self, repo_name, format='html'):
382 def show(self, repo_name, format='html'):
407 """GET /repos/repo_name: Show a specific item"""
383 """GET /repos/repo_name: Show a specific item"""
408 # url('repo', repo_name=ID)
384 # url('repo', repo_name=ID)
409
385
410 @HasPermissionAllDecorator('hg.admin')
386 @HasPermissionAllDecorator('hg.admin')
411 def edit(self, repo_name, format='html'):
387 def edit(self, repo_name, format='html'):
412 """GET /repos/repo_name/edit: Form to edit an existing item"""
388 """GET /repos/repo_name/edit: Form to edit an existing item"""
413 # url('edit_repo', repo_name=ID)
389 # url('edit_repo', repo_name=ID)
414 defaults = self.__load_data(repo_name)
390 defaults = self.__load_data(repo_name)
415
391
416 return htmlfill.render(
392 return htmlfill.render(
417 render('admin/repos/repo_edit.html'),
393 render('admin/repos/repo_edit.html'),
418 defaults=defaults,
394 defaults=defaults,
419 encoding="UTF-8",
395 encoding="UTF-8",
420 force_defaults=False
396 force_defaults=False
421 )
397 )
@@ -1,98 +1,374 b''
1 import traceback
1 import traceback
2 import logging
2 import logging
3
3
4 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
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 from rhodecode.model.scm import ScmModel
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 log = logging.getLogger(__name__)
18 log = logging.getLogger(__name__)
11
19
12
20
13 class ApiController(JSONRPCController):
21 class ApiController(JSONRPCController):
14 """
22 """
15 API Controller
23 API Controller
16
24
17
25
18 Each method needs to have USER as argument this is then based on given
26 Each method needs to have USER as argument this is then based on given
19 API_KEY propagated as instance of user object
27 API_KEY propagated as instance of user object
20
28
21 Preferably this should be first argument also
29 Preferably this should be first argument also
22
30
23
31
24 Each function should also **raise** JSONRPCError for any
32 Each function should also **raise** JSONRPCError for any
25 errors that happens
33 errors that happens
26
34
27 """
35 """
28
36
29 @HasPermissionAllDecorator('hg.admin')
37 @HasPermissionAllDecorator('hg.admin')
30 def pull(self, apiuser, repo):
38 def pull(self, apiuser, repo):
31 """
39 """
32 Dispatch pull action on given repo
40 Dispatch pull action on given repo
33
41
34
42
35 :param user:
43 :param user:
36 :param repo:
44 :param repo:
37 """
45 """
38
46
39 if Repository.is_valid(repo) is False:
47 if Repository.is_valid(repo) is False:
40 raise JSONRPCError('Unknown repo "%s"' % repo)
48 raise JSONRPCError('Unknown repo "%s"' % repo)
41
49
42 try:
50 try:
43 ScmModel().pull_changes(repo, self.rhodecode_user.username)
51 ScmModel().pull_changes(repo, self.rhodecode_user.username)
44 return 'Pulled from %s' % repo
52 return 'Pulled from %s' % repo
45 except Exception:
53 except Exception:
46 raise JSONRPCError('Unable to pull changes from "%s"' % repo)
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 @HasPermissionAllDecorator('hg.admin')
78 @HasPermissionAllDecorator('hg.admin')
50 def create_user(self, apiuser, username, password, active, admin, name,
79 def get_users(self, apiuser):
51 lastname, email):
80 """"
81 Get all users
82
83 :param apiuser
52 """
84 """
53 Creates new user
85
54
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 :param apiuser:
104 :param apiuser:
56 :param username:
105 :param username:
57 :param password:
106 :param password:
58 :param active:
59 :param admin:
60 :param name:
107 :param name:
61 :param lastname:
108 :param lastname:
62 :param email:
109 :param email:
110 :param active:
111 :param admin:
112 :param ldap_dn:
63 """
113 """
64
114
65 form_data = dict(username=username,
115 if self.get_user(apiuser, username):
66 password=password,
116 raise JSONRPCError("user %s already exist" % username)
67 active=active,
117
68 admin=admin,
69 name=name,
70 lastname=lastname,
71 email=email)
72 try:
118 try:
73 u = User.create(form_data)
119 form_data = dict(username=username,
74 return {'id':u.user_id,
120 password=password,
75 'msg':'created new user %s' % name}
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 except Exception:
129 except Exception:
77 log.error(traceback.format_exc())
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 @HasPermissionAllDecorator('hg.admin')
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 Creates an new usergroup
194 Creates an new usergroup
85
195
86 :param name:
196 :param name:
87 :param active:
197 :param active:
88 """
198 """
89 form_data = {'users_group_name':name,
199
90 'users_group_active':active}
200 if self.get_users_group(apiuser, name):
201 raise JSONRPCError("users group %s already exist" % name)
202
91 try:
203 try:
204 form_data = dict(users_group_name=name,
205 users_group_active=active)
92 ug = UsersGroup.create(form_data)
206 ug = UsersGroup.create(form_data)
93 return {'id':ug.users_group_id,
207 return dict(id=ug.users_group_id,
94 'msg':'created new users group %s' % name}
208 msg='created new users group %s' % name)
95 except Exception:
209 except Exception:
96 log.error(traceback.format_exc())
210 log.error(traceback.format_exc())
97 raise JSONRPCError('failed to create group %s' % name)
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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.settings
3 rhodecode.controllers.settings
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Settings controller for rhodecode
6 Settings controller for rhodecode
7
7
8 :created_on: Jun 30, 2010
8 :created_on: Jun 30, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29
29
30 from formencode import htmlfill
30 from formencode import htmlfill
31
31
32 from pylons import tmpl_context as c, request, url
32 from pylons import tmpl_context as c, request, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37
37
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator, \
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator, \
39 HasRepoPermissionAnyDecorator, NotAnonymous
39 HasRepoPermissionAnyDecorator, NotAnonymous
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.utils import invalidate_cache, action_logger
41 from rhodecode.lib.utils import invalidate_cache, action_logger
42
42
43 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
43 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
44 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.db import User
45 from rhodecode.model.db import Group
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class SettingsController(BaseRepoController):
50 class SettingsController(BaseRepoController):
51
51
52 @LoginRequired()
52 @LoginRequired()
53 def __before__(self):
53 def __before__(self):
54 super(SettingsController, self).__before__()
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 @HasRepoPermissionAllDecorator('repository.admin')
64 @HasRepoPermissionAllDecorator('repository.admin')
57 def index(self, repo_name):
65 def index(self, repo_name):
58 repo_model = RepoModel()
66 repo_model = RepoModel()
59 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
67 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
60 if not repo:
68 if not repo:
61 h.flash(_('%s repository is not mapped to db perhaps'
69 h.flash(_('%s repository is not mapped to db perhaps'
62 ' it was created or renamed from the file system'
70 ' it was created or renamed from the file system'
63 ' please run the application again'
71 ' please run the application again'
64 ' in order to rescan repositories') % repo_name,
72 ' in order to rescan repositories') % repo_name,
65 category='error')
73 category='error')
66
74
67 return redirect(url('home'))
75 return redirect(url('home'))
68
76
69 c.users_array = repo_model.get_users_js()
77 self.__load_defaults()
70 c.users_groups_array = repo_model.get_users_groups_js()
71
72 defaults = c.repo_info.get_dict()
73
78
74 #fill owner
79 defaults = RepoModel()._get_defaults(repo_name)
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})
91
80
92 return htmlfill.render(
81 return htmlfill.render(
93 render('settings/repo_settings.html'),
82 render('settings/repo_settings.html'),
94 defaults=defaults,
83 defaults=defaults,
95 encoding="UTF-8",
84 encoding="UTF-8",
96 force_defaults=False
85 force_defaults=False
97 )
86 )
98
87
99 @HasRepoPermissionAllDecorator('repository.admin')
88 @HasRepoPermissionAllDecorator('repository.admin')
100 def update(self, repo_name):
89 def update(self, repo_name):
101 repo_model = RepoModel()
90 repo_model = RepoModel()
102 changed_name = repo_name
91 changed_name = repo_name
92
93 self.__load_defaults()
94
103 _form = RepoSettingsForm(edit=True,
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 try:
98 try:
106 form_result = _form.to_python(dict(request.POST))
99 form_result = _form.to_python(dict(request.POST))
100
107 repo_model.update(repo_name, form_result)
101 repo_model.update(repo_name, form_result)
108 invalidate_cache('get_repo_cached_%s' % repo_name)
102 invalidate_cache('get_repo_cached_%s' % repo_name)
109 h.flash(_('Repository %s updated successfully' % repo_name),
103 h.flash(_('Repository %s updated successfully' % repo_name),
110 category='success')
104 category='success')
111 changed_name = form_result['repo_name']
105 changed_name = form_result['repo_name_full']
112 action_logger(self.rhodecode_user, 'user_updated_repo',
106 action_logger(self.rhodecode_user, 'user_updated_repo',
113 changed_name, '', self.sa)
107 changed_name, '', self.sa)
114 except formencode.Invalid, errors:
108 except formencode.Invalid, errors:
115 c.repo_info = repo_model.get_by_repo_name(repo_name)
109 c.repo_info = repo_model.get_by_repo_name(repo_name)
116 c.users_array = repo_model.get_users_js()
110 c.users_array = repo_model.get_users_js()
117 errors.value.update({'user': c.repo_info.user.username})
111 errors.value.update({'user': c.repo_info.user.username})
118 return htmlfill.render(
112 return htmlfill.render(
119 render('settings/repo_settings.html'),
113 render('settings/repo_settings.html'),
120 defaults=errors.value,
114 defaults=errors.value,
121 errors=errors.error_dict or {},
115 errors=errors.error_dict or {},
122 prefix_error=False,
116 prefix_error=False,
123 encoding="UTF-8")
117 encoding="UTF-8")
124 except Exception:
118 except Exception:
125 log.error(traceback.format_exc())
119 log.error(traceback.format_exc())
126 h.flash(_('error occurred during update of repository %s') \
120 h.flash(_('error occurred during update of repository %s') \
127 % repo_name, category='error')
121 % repo_name, category='error')
128
122
129 return redirect(url('repo_settings_home', repo_name=changed_name))
123 return redirect(url('repo_settings_home', repo_name=changed_name))
130
124
131 @HasRepoPermissionAllDecorator('repository.admin')
125 @HasRepoPermissionAllDecorator('repository.admin')
132 def delete(self, repo_name):
126 def delete(self, repo_name):
133 """DELETE /repos/repo_name: Delete an existing item"""
127 """DELETE /repos/repo_name: Delete an existing item"""
134 # Forms posted to this method should contain a hidden field:
128 # Forms posted to this method should contain a hidden field:
135 # <input type="hidden" name="_method" value="DELETE" />
129 # <input type="hidden" name="_method" value="DELETE" />
136 # Or using helpers:
130 # Or using helpers:
137 # h.form(url('repo_settings_delete', repo_name=ID),
131 # h.form(url('repo_settings_delete', repo_name=ID),
138 # method='delete')
132 # method='delete')
139 # url('repo_settings_delete', repo_name=ID)
133 # url('repo_settings_delete', repo_name=ID)
140
134
141 repo_model = RepoModel()
135 repo_model = RepoModel()
142 repo = repo_model.get_by_repo_name(repo_name)
136 repo = repo_model.get_by_repo_name(repo_name)
143 if not repo:
137 if not repo:
144 h.flash(_('%s repository is not mapped to db perhaps'
138 h.flash(_('%s repository is not mapped to db perhaps'
145 ' it was moved or renamed from the filesystem'
139 ' it was moved or renamed from the filesystem'
146 ' please run the application again'
140 ' please run the application again'
147 ' in order to rescan repositories') % repo_name,
141 ' in order to rescan repositories') % repo_name,
148 category='error')
142 category='error')
149
143
150 return redirect(url('home'))
144 return redirect(url('home'))
151 try:
145 try:
152 action_logger(self.rhodecode_user, 'user_deleted_repo',
146 action_logger(self.rhodecode_user, 'user_deleted_repo',
153 repo_name, '', self.sa)
147 repo_name, '', self.sa)
154 repo_model.delete(repo)
148 repo_model.delete(repo)
155 invalidate_cache('get_repo_cached_%s' % repo_name)
149 invalidate_cache('get_repo_cached_%s' % repo_name)
156 h.flash(_('deleted repository %s') % repo_name, category='success')
150 h.flash(_('deleted repository %s') % repo_name, category='success')
157 except Exception:
151 except Exception:
158 log.error(traceback.format_exc())
152 log.error(traceback.format_exc())
159 h.flash(_('An error occurred during deletion of %s') % repo_name,
153 h.flash(_('An error occurred during deletion of %s') % repo_name,
160 category='error')
154 category='error')
161
155
162 return redirect(url('home'))
156 return redirect(url('home'))
163
157
164 @NotAnonymous()
158 @NotAnonymous()
165 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
159 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
166 'repository.admin')
160 'repository.admin')
167 def fork(self, repo_name):
161 def fork(self, repo_name):
168 repo_model = RepoModel()
162 repo_model = RepoModel()
169 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
163 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
170 if not repo:
164 if not repo:
171 h.flash(_('%s repository is not mapped to db perhaps'
165 h.flash(_('%s repository is not mapped to db perhaps'
172 ' it was created or renamed from the file system'
166 ' it was created or renamed from the file system'
173 ' please run the application again'
167 ' please run the application again'
174 ' in order to rescan repositories') % repo_name,
168 ' in order to rescan repositories') % repo_name,
175 category='error')
169 category='error')
176
170
177 return redirect(url('home'))
171 return redirect(url('home'))
178
172
179 return render('settings/repo_fork.html')
173 return render('settings/repo_fork.html')
180
174
181 @NotAnonymous()
175 @NotAnonymous()
182 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
176 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
183 'repository.admin')
177 'repository.admin')
184 def fork_create(self, repo_name):
178 def fork_create(self, repo_name):
185 repo_model = RepoModel()
179 repo_model = RepoModel()
186 c.repo_info = repo_model.get_by_repo_name(repo_name)
180 c.repo_info = repo_model.get_by_repo_name(repo_name)
187 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type})()
181 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type})()
188 form_result = {}
182 form_result = {}
189 try:
183 try:
190 form_result = _form.to_python(dict(request.POST))
184 form_result = _form.to_python(dict(request.POST))
191 form_result.update({'repo_name': repo_name})
185 form_result.update({'repo_name': repo_name})
192 repo_model.create_fork(form_result, self.rhodecode_user)
186 repo_model.create_fork(form_result, self.rhodecode_user)
193 h.flash(_('forked %s repository as %s') \
187 h.flash(_('forked %s repository as %s') \
194 % (repo_name, form_result['fork_name']),
188 % (repo_name, form_result['fork_name']),
195 category='success')
189 category='success')
196 action_logger(self.rhodecode_user,
190 action_logger(self.rhodecode_user,
197 'user_forked_repo:%s' % form_result['fork_name'],
191 'user_forked_repo:%s' % form_result['fork_name'],
198 repo_name, '', self.sa)
192 repo_name, '', self.sa)
199 except formencode.Invalid, errors:
193 except formencode.Invalid, errors:
200 c.new_repo = errors.value['fork_name']
194 c.new_repo = errors.value['fork_name']
201 r = render('settings/repo_fork.html')
195 r = render('settings/repo_fork.html')
202
196
203 return htmlfill.render(
197 return htmlfill.render(
204 r,
198 r,
205 defaults=errors.value,
199 defaults=errors.value,
206 errors=errors.error_dict or {},
200 errors=errors.error_dict or {},
207 prefix_error=False,
201 prefix_error=False,
208 encoding="UTF-8")
202 encoding="UTF-8")
209 except Exception:
203 except Exception:
210 log.error(traceback.format_exc())
204 log.error(traceback.format_exc())
211 h.flash(_('An error occurred during repository forking %s') %
205 h.flash(_('An error occurred during repository forking %s') %
212 repo_name, category='error')
206 repo_name, category='error')
213
207
214 return redirect(url('home'))
208 return redirect(url('home'))
@@ -1,408 +1,407 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.__init__
3 rhodecode.lib.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27
27
28 def __get_lem():
28 def __get_lem():
29 from pygments import lexers
29 from pygments import lexers
30 from string import lower
30 from string import lower
31 from collections import defaultdict
31 from collections import defaultdict
32
32
33 d = defaultdict(lambda: [])
33 d = defaultdict(lambda: [])
34
34
35 def __clean(s):
35 def __clean(s):
36 s = s.lstrip('*')
36 s = s.lstrip('*')
37 s = s.lstrip('.')
37 s = s.lstrip('.')
38
38
39 if s.find('[') != -1:
39 if s.find('[') != -1:
40 exts = []
40 exts = []
41 start, stop = s.find('['), s.find(']')
41 start, stop = s.find('['), s.find(']')
42
42
43 for suffix in s[start + 1:stop]:
43 for suffix in s[start + 1:stop]:
44 exts.append(s[:s.find('[')] + suffix)
44 exts.append(s[:s.find('[')] + suffix)
45 return map(lower, exts)
45 return map(lower, exts)
46 else:
46 else:
47 return map(lower, [s])
47 return map(lower, [s])
48
48
49 for lx, t in sorted(lexers.LEXERS.items()):
49 for lx, t in sorted(lexers.LEXERS.items()):
50 m = map(__clean, t[-2])
50 m = map(__clean, t[-2])
51 if m:
51 if m:
52 m = reduce(lambda x, y: x + y, m)
52 m = reduce(lambda x, y: x + y, m)
53 for ext in m:
53 for ext in m:
54 desc = lx.replace('Lexer', '')
54 desc = lx.replace('Lexer', '')
55 d[ext].append(desc)
55 d[ext].append(desc)
56
56
57 return dict(d)
57 return dict(d)
58
58
59 # language map is also used by whoosh indexer, which for those specified
59 # language map is also used by whoosh indexer, which for those specified
60 # extensions will index it's content
60 # extensions will index it's content
61 LANGUAGES_EXTENSIONS_MAP = __get_lem()
61 LANGUAGES_EXTENSIONS_MAP = __get_lem()
62
62
63 # Additional mappings that are not present in the pygments lexers
63 # Additional mappings that are not present in the pygments lexers
64 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
64 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
65 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
65 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
66
66
67 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
67 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
68
68
69
69
70 def str2bool(_str):
70 def str2bool(_str):
71 """
71 """
72 returs True/False value from given string, it tries to translate the
72 returs True/False value from given string, it tries to translate the
73 string into boolean
73 string into boolean
74
74
75 :param _str: string value to translate into boolean
75 :param _str: string value to translate into boolean
76 :rtype: boolean
76 :rtype: boolean
77 :returns: boolean from given string
77 :returns: boolean from given string
78 """
78 """
79 if _str is None:
79 if _str is None:
80 return False
80 return False
81 if _str in (True, False):
81 if _str in (True, False):
82 return _str
82 return _str
83 _str = str(_str).strip().lower()
83 _str = str(_str).strip().lower()
84 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
84 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
85
85
86
86
87 def convert_line_endings(line, mode):
87 def convert_line_endings(line, mode):
88 """
88 """
89 Converts a given line "line end" accordingly to given mode
89 Converts a given line "line end" accordingly to given mode
90
90
91 Available modes are::
91 Available modes are::
92 0 - Unix
92 0 - Unix
93 1 - Mac
93 1 - Mac
94 2 - DOS
94 2 - DOS
95
95
96 :param line: given line to convert
96 :param line: given line to convert
97 :param mode: mode to convert to
97 :param mode: mode to convert to
98 :rtype: str
98 :rtype: str
99 :return: converted line according to mode
99 :return: converted line according to mode
100 """
100 """
101 from string import replace
101 from string import replace
102
102
103 if mode == 0:
103 if mode == 0:
104 line = replace(line, '\r\n', '\n')
104 line = replace(line, '\r\n', '\n')
105 line = replace(line, '\r', '\n')
105 line = replace(line, '\r', '\n')
106 elif mode == 1:
106 elif mode == 1:
107 line = replace(line, '\r\n', '\r')
107 line = replace(line, '\r\n', '\r')
108 line = replace(line, '\n', '\r')
108 line = replace(line, '\n', '\r')
109 elif mode == 2:
109 elif mode == 2:
110 import re
110 import re
111 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
111 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
112 return line
112 return line
113
113
114
114
115 def detect_mode(line, default):
115 def detect_mode(line, default):
116 """
116 """
117 Detects line break for given line, if line break couldn't be found
117 Detects line break for given line, if line break couldn't be found
118 given default value is returned
118 given default value is returned
119
119
120 :param line: str line
120 :param line: str line
121 :param default: default
121 :param default: default
122 :rtype: int
122 :rtype: int
123 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
123 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
124 """
124 """
125 if line.endswith('\r\n'):
125 if line.endswith('\r\n'):
126 return 2
126 return 2
127 elif line.endswith('\n'):
127 elif line.endswith('\n'):
128 return 0
128 return 0
129 elif line.endswith('\r'):
129 elif line.endswith('\r'):
130 return 1
130 return 1
131 else:
131 else:
132 return default
132 return default
133
133
134
134
135 def generate_api_key(username, salt=None):
135 def generate_api_key(username, salt=None):
136 """
136 """
137 Generates unique API key for given username, if salt is not given
137 Generates unique API key for given username, if salt is not given
138 it'll be generated from some random string
138 it'll be generated from some random string
139
139
140 :param username: username as string
140 :param username: username as string
141 :param salt: salt to hash generate KEY
141 :param salt: salt to hash generate KEY
142 :rtype: str
142 :rtype: str
143 :returns: sha1 hash from username+salt
143 :returns: sha1 hash from username+salt
144 """
144 """
145 from tempfile import _RandomNameSequence
145 from tempfile import _RandomNameSequence
146 import hashlib
146 import hashlib
147
147
148 if salt is None:
148 if salt is None:
149 salt = _RandomNameSequence().next()
149 salt = _RandomNameSequence().next()
150
150
151 return hashlib.sha1(username + salt).hexdigest()
151 return hashlib.sha1(username + salt).hexdigest()
152
152
153
153
154 def safe_unicode(str_, from_encoding='utf8'):
154 def safe_unicode(str_, from_encoding='utf8'):
155 """
155 """
156 safe unicode function. Does few trick to turn str_ into unicode
156 safe unicode function. Does few trick to turn str_ into unicode
157
157
158 In case of UnicodeDecode error we try to return it with encoding detected
158 In case of UnicodeDecode error we try to return it with encoding detected
159 by chardet library if it fails fallback to unicode with errors replaced
159 by chardet library if it fails fallback to unicode with errors replaced
160
160
161 :param str_: string to decode
161 :param str_: string to decode
162 :rtype: unicode
162 :rtype: unicode
163 :returns: unicode object
163 :returns: unicode object
164 """
164 """
165 if isinstance(str_, unicode):
165 if isinstance(str_, unicode):
166 return str_
166 return str_
167
167
168 try:
168 try:
169 return unicode(str_)
169 return unicode(str_)
170 except UnicodeDecodeError:
170 except UnicodeDecodeError:
171 pass
171 pass
172
172
173 try:
173 try:
174 return unicode(str_, from_encoding)
174 return unicode(str_, from_encoding)
175 except UnicodeDecodeError:
175 except UnicodeDecodeError:
176 pass
176 pass
177
177
178 try:
178 try:
179 import chardet
179 import chardet
180 encoding = chardet.detect(str_)['encoding']
180 encoding = chardet.detect(str_)['encoding']
181 if encoding is None:
181 if encoding is None:
182 raise Exception()
182 raise Exception()
183 return str_.decode(encoding)
183 return str_.decode(encoding)
184 except (ImportError, UnicodeDecodeError, Exception):
184 except (ImportError, UnicodeDecodeError, Exception):
185 return unicode(str_, from_encoding, 'replace')
185 return unicode(str_, from_encoding, 'replace')
186
186
187 def safe_str(unicode_, to_encoding='utf8'):
187 def safe_str(unicode_, to_encoding='utf8'):
188 """
188 """
189 safe str function. Does few trick to turn unicode_ into string
189 safe str function. Does few trick to turn unicode_ into string
190
190
191 In case of UnicodeEncodeError we try to return it with encoding detected
191 In case of UnicodeEncodeError we try to return it with encoding detected
192 by chardet library if it fails fallback to string with errors replaced
192 by chardet library if it fails fallback to string with errors replaced
193
193
194 :param unicode_: unicode to encode
194 :param unicode_: unicode to encode
195 :rtype: str
195 :rtype: str
196 :returns: str object
196 :returns: str object
197 """
197 """
198
198
199 if isinstance(unicode_, str):
199 if isinstance(unicode_, str):
200 return unicode_
200 return unicode_
201
201
202 try:
202 try:
203 return unicode_.encode(to_encoding)
203 return unicode_.encode(to_encoding)
204 except UnicodeEncodeError:
204 except UnicodeEncodeError:
205 pass
205 pass
206
206
207 try:
207 try:
208 import chardet
208 import chardet
209 encoding = chardet.detect(unicode_)['encoding']
209 encoding = chardet.detect(unicode_)['encoding']
210 print encoding
210 print encoding
211 if encoding is None:
211 if encoding is None:
212 raise UnicodeEncodeError()
212 raise UnicodeEncodeError()
213
213
214 return unicode_.encode(encoding)
214 return unicode_.encode(encoding)
215 except (ImportError, UnicodeEncodeError):
215 except (ImportError, UnicodeEncodeError):
216 return unicode_.encode(to_encoding, 'replace')
216 return unicode_.encode(to_encoding, 'replace')
217
217
218 return safe_str
218 return safe_str
219
219
220
220
221
221
222 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
222 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
223 """
223 """
224 Custom engine_from_config functions that makes sure we use NullPool for
224 Custom engine_from_config functions that makes sure we use NullPool for
225 file based sqlite databases. This prevents errors on sqlite. This only
225 file based sqlite databases. This prevents errors on sqlite. This only
226 applies to sqlalchemy versions < 0.7.0
226 applies to sqlalchemy versions < 0.7.0
227
227
228 """
228 """
229 import sqlalchemy
229 import sqlalchemy
230 from sqlalchemy import engine_from_config as efc
230 from sqlalchemy import engine_from_config as efc
231 import logging
231 import logging
232
232
233 if int(sqlalchemy.__version__.split('.')[1]) < 7:
233 if int(sqlalchemy.__version__.split('.')[1]) < 7:
234
234
235 # This solution should work for sqlalchemy < 0.7.0, and should use
235 # This solution should work for sqlalchemy < 0.7.0, and should use
236 # proxy=TimerProxy() for execution time profiling
236 # proxy=TimerProxy() for execution time profiling
237
237
238 from sqlalchemy.pool import NullPool
238 from sqlalchemy.pool import NullPool
239 url = configuration[prefix + 'url']
239 url = configuration[prefix + 'url']
240
240
241 if url.startswith('sqlite'):
241 if url.startswith('sqlite'):
242 kwargs.update({'poolclass': NullPool})
242 kwargs.update({'poolclass': NullPool})
243 return efc(configuration, prefix, **kwargs)
243 return efc(configuration, prefix, **kwargs)
244 else:
244 else:
245 import time
245 import time
246 from sqlalchemy import event
246 from sqlalchemy import event
247 from sqlalchemy.engine import Engine
247 from sqlalchemy.engine import Engine
248
248
249 log = logging.getLogger('sqlalchemy.engine')
249 log = logging.getLogger('sqlalchemy.engine')
250 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
250 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
251 engine = efc(configuration, prefix, **kwargs)
251 engine = efc(configuration, prefix, **kwargs)
252
252
253 def color_sql(sql):
253 def color_sql(sql):
254 COLOR_SEQ = "\033[1;%dm"
254 COLOR_SEQ = "\033[1;%dm"
255 COLOR_SQL = YELLOW
255 COLOR_SQL = YELLOW
256 normal = '\x1b[0m'
256 normal = '\x1b[0m'
257 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
257 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
258
258
259 if configuration['debug']:
259 if configuration['debug']:
260 #attach events only for debug configuration
260 #attach events only for debug configuration
261
261
262 def before_cursor_execute(conn, cursor, statement,
262 def before_cursor_execute(conn, cursor, statement,
263 parameters, context, executemany):
263 parameters, context, executemany):
264 context._query_start_time = time.time()
264 context._query_start_time = time.time()
265 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
265 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
266
266
267
267
268 def after_cursor_execute(conn, cursor, statement,
268 def after_cursor_execute(conn, cursor, statement,
269 parameters, context, executemany):
269 parameters, context, executemany):
270 total = time.time() - context._query_start_time
270 total = time.time() - context._query_start_time
271 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
271 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
272
272
273 event.listen(engine, "before_cursor_execute",
273 event.listen(engine, "before_cursor_execute",
274 before_cursor_execute)
274 before_cursor_execute)
275 event.listen(engine, "after_cursor_execute",
275 event.listen(engine, "after_cursor_execute",
276 after_cursor_execute)
276 after_cursor_execute)
277
277
278 return engine
278 return engine
279
279
280
280
281 def age(curdate):
281 def age(curdate):
282 """
282 """
283 turns a datetime into an age string.
283 turns a datetime into an age string.
284
284
285 :param curdate: datetime object
285 :param curdate: datetime object
286 :rtype: unicode
286 :rtype: unicode
287 :returns: unicode words describing age
287 :returns: unicode words describing age
288 """
288 """
289
289
290 from datetime import datetime
290 from datetime import datetime
291 from webhelpers.date import time_ago_in_words
291 from webhelpers.date import time_ago_in_words
292
292
293 _ = lambda s:s
293 _ = lambda s:s
294
294
295 if not curdate:
295 if not curdate:
296 return ''
296 return ''
297
297
298 agescales = [(_(u"year"), 3600 * 24 * 365),
298 agescales = [(_(u"year"), 3600 * 24 * 365),
299 (_(u"month"), 3600 * 24 * 30),
299 (_(u"month"), 3600 * 24 * 30),
300 (_(u"day"), 3600 * 24),
300 (_(u"day"), 3600 * 24),
301 (_(u"hour"), 3600),
301 (_(u"hour"), 3600),
302 (_(u"minute"), 60),
302 (_(u"minute"), 60),
303 (_(u"second"), 1), ]
303 (_(u"second"), 1), ]
304
304
305 age = datetime.now() - curdate
305 age = datetime.now() - curdate
306 age_seconds = (age.days * agescales[2][1]) + age.seconds
306 age_seconds = (age.days * agescales[2][1]) + age.seconds
307 pos = 1
307 pos = 1
308 for scale in agescales:
308 for scale in agescales:
309 if scale[1] <= age_seconds:
309 if scale[1] <= age_seconds:
310 if pos == 6:pos = 5
310 if pos == 6:pos = 5
311 return '%s %s' % (time_ago_in_words(curdate,
311 return '%s %s' % (time_ago_in_words(curdate,
312 agescales[pos][0]), _('ago'))
312 agescales[pos][0]), _('ago'))
313 pos += 1
313 pos += 1
314
314
315 return _(u'just now')
315 return _(u'just now')
316
316
317
317
318 def uri_filter(uri):
318 def uri_filter(uri):
319 """
319 """
320 Removes user:password from given url string
320 Removes user:password from given url string
321
321
322 :param uri:
322 :param uri:
323 :rtype: unicode
323 :rtype: unicode
324 :returns: filtered list of strings
324 :returns: filtered list of strings
325 """
325 """
326 if not uri:
326 if not uri:
327 return ''
327 return ''
328
328
329 proto = ''
329 proto = ''
330
330
331 for pat in ('https://', 'http://'):
331 for pat in ('https://', 'http://'):
332 if uri.startswith(pat):
332 if uri.startswith(pat):
333 uri = uri[len(pat):]
333 uri = uri[len(pat):]
334 proto = pat
334 proto = pat
335 break
335 break
336
336
337 # remove passwords and username
337 # remove passwords and username
338 uri = uri[uri.find('@') + 1:]
338 uri = uri[uri.find('@') + 1:]
339
339
340 # get the port
340 # get the port
341 cred_pos = uri.find(':')
341 cred_pos = uri.find(':')
342 if cred_pos == -1:
342 if cred_pos == -1:
343 host, port = uri, None
343 host, port = uri, None
344 else:
344 else:
345 host, port = uri[:cred_pos], uri[cred_pos + 1:]
345 host, port = uri[:cred_pos], uri[cred_pos + 1:]
346
346
347 return filter(None, [proto, host, port])
347 return filter(None, [proto, host, port])
348
348
349
349
350 def credentials_filter(uri):
350 def credentials_filter(uri):
351 """
351 """
352 Returns a url with removed credentials
352 Returns a url with removed credentials
353
353
354 :param uri:
354 :param uri:
355 """
355 """
356
356
357 uri = uri_filter(uri)
357 uri = uri_filter(uri)
358 #check if we have port
358 #check if we have port
359 if len(uri) > 2 and uri[2]:
359 if len(uri) > 2 and uri[2]:
360 uri[2] = ':' + uri[2]
360 uri[2] = ':' + uri[2]
361
361
362 return ''.join(uri)
362 return ''.join(uri)
363
363
364 def get_changeset_safe(repo, rev):
364 def get_changeset_safe(repo, rev):
365 """
365 """
366 Safe version of get_changeset if this changeset doesn't exists for a
366 Safe version of get_changeset if this changeset doesn't exists for a
367 repo it returns a Dummy one instead
367 repo it returns a Dummy one instead
368
368
369 :param repo:
369 :param repo:
370 :param rev:
370 :param rev:
371 """
371 """
372 from vcs.backends.base import BaseRepository
372 from vcs.backends.base import BaseRepository
373 from vcs.exceptions import RepositoryError
373 from vcs.exceptions import RepositoryError
374 if not isinstance(repo, BaseRepository):
374 if not isinstance(repo, BaseRepository):
375 raise Exception('You must pass an Repository '
375 raise Exception('You must pass an Repository '
376 'object as first argument got %s', type(repo))
376 'object as first argument got %s', type(repo))
377
377
378 try:
378 try:
379 cs = repo.get_changeset(rev)
379 cs = repo.get_changeset(rev)
380 except RepositoryError:
380 except RepositoryError:
381 from rhodecode.lib.utils import EmptyChangeset
381 from rhodecode.lib.utils import EmptyChangeset
382 cs = EmptyChangeset(requested_revision=rev)
382 cs = EmptyChangeset(requested_revision=rev)
383 return cs
383 return cs
384
384
385
385
386 def get_current_revision(quiet=False):
386 def get_current_revision(quiet=False):
387 """
387 """
388 Returns tuple of (number, id) from repository containing this package
388 Returns tuple of (number, id) from repository containing this package
389 or None if repository could not be found.
389 or None if repository could not be found.
390
390
391 :param quiet: prints error for fetching revision if True
391 :param quiet: prints error for fetching revision if True
392 """
392 """
393
393
394 try:
394 try:
395 from vcs import get_repo
395 from vcs import get_repo
396 from vcs.utils.helpers import get_scm
396 from vcs.utils.helpers import get_scm
397 from vcs.exceptions import RepositoryError, VCSError
398 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
397 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
399 scm = get_scm(repopath)[0]
398 scm = get_scm(repopath)[0]
400 repo = get_repo(path=repopath, alias=scm)
399 repo = get_repo(path=repopath, alias=scm)
401 tip = repo.get_changeset()
400 tip = repo.get_changeset()
402 return (tip.revision, tip.short_id)
401 return (tip.revision, tip.short_id)
403 except (ImportError, RepositoryError, VCSError), err:
402 except Exception, err:
404 if not quiet:
403 if not quiet:
405 print ("Cannot retrieve rhodecode's revision. Original error "
404 print ("Cannot retrieve rhodecode's revision. Original error "
406 "was: %s" % err)
405 "was: %s" % err)
407 return None
406 return None
408
407
@@ -1,147 +1,151 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changelog
3 rhodecode.controllers.changelog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode authentication library for LDAP
6 RhodeCode authentication library for LDAP
7
7
8 :created_on: Created on Nov 17, 2010
8 :created_on: Created on Nov 17, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27
27
28 from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \
28 from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \
29 LdapPasswordError
29 LdapPasswordError
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 try:
34 try:
35 import ldap
35 import ldap
36 except ImportError:
36 except ImportError:
37 # means that python-ldap is not installed
37 # means that python-ldap is not installed
38 pass
38 pass
39
39
40
40
41 class AuthLdap(object):
41 class AuthLdap(object):
42
42
43 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
43 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
44 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
44 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
45 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
45 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
46 search_scope='SUBTREE',
46 search_scope='SUBTREE',
47 attr_login='uid'):
47 attr_login='uid'):
48 self.ldap_version = ldap_version
48 self.ldap_version = ldap_version
49 ldap_server_type = 'ldap'
49 ldap_server_type = 'ldap'
50
50
51 self.TLS_KIND = tls_kind
51 self.TLS_KIND = tls_kind
52
52
53 if self.TLS_KIND == 'LDAPS':
53 if self.TLS_KIND == 'LDAPS':
54 port = port or 689
54 port = port or 689
55 ldap_server_type = ldap_server_type + 's'
55 ldap_server_type = ldap_server_type + 's'
56
56
57 self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
57 OPT_X_TLS_DEMAND = 2
58 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
59 OPT_X_TLS_DEMAND)
58 self.LDAP_SERVER_ADDRESS = server
60 self.LDAP_SERVER_ADDRESS = server
59 self.LDAP_SERVER_PORT = port
61 self.LDAP_SERVER_PORT = port
60
62
61 #USE FOR READ ONLY BIND TO LDAP SERVER
63 #USE FOR READ ONLY BIND TO LDAP SERVER
62 self.LDAP_BIND_DN = bind_dn
64 self.LDAP_BIND_DN = bind_dn
63 self.LDAP_BIND_PASS = bind_pass
65 self.LDAP_BIND_PASS = bind_pass
64
66
65 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
67 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
66 self.LDAP_SERVER_ADDRESS,
68 self.LDAP_SERVER_ADDRESS,
67 self.LDAP_SERVER_PORT)
69 self.LDAP_SERVER_PORT)
68
70
69 self.BASE_DN = base_dn
71 self.BASE_DN = base_dn
70 self.LDAP_FILTER = ldap_filter
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 self.attr_login = attr_login
74 self.attr_login = attr_login
73
75
74 def authenticate_ldap(self, username, password):
76 def authenticate_ldap(self, username, password):
75 """Authenticate a user via LDAP and return his/her LDAP properties.
77 """Authenticate a user via LDAP and return his/her LDAP properties.
76
78
77 Raises AuthenticationError if the credentials are rejected, or
79 Raises AuthenticationError if the credentials are rejected, or
78 EnvironmentError if the LDAP server can't be reached.
80 EnvironmentError if the LDAP server can't be reached.
79
81
80 :param username: username
82 :param username: username
81 :param password: password
83 :param password: password
82 """
84 """
83
85
84 from rhodecode.lib.helpers import chop_at
86 from rhodecode.lib.helpers import chop_at
85
87
86 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
88 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
87
89
88 if "," in username:
90 if "," in username:
89 raise LdapUsernameError("invalid character in username: ,")
91 raise LdapUsernameError("invalid character in username: ,")
90 try:
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 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
96 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
93 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
97 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
94 ldap.set_option(ldap.OPT_TIMEOUT, 20)
98 ldap.set_option(ldap.OPT_TIMEOUT, 20)
95 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
99 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
96 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
100 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
97 if self.TLS_KIND != 'PLAIN':
101 if self.TLS_KIND != 'PLAIN':
98 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
102 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
99 server = ldap.initialize(self.LDAP_SERVER)
103 server = ldap.initialize(self.LDAP_SERVER)
100 if self.ldap_version == 2:
104 if self.ldap_version == 2:
101 server.protocol = ldap.VERSION2
105 server.protocol = ldap.VERSION2
102 else:
106 else:
103 server.protocol = ldap.VERSION3
107 server.protocol = ldap.VERSION3
104
108
105 if self.TLS_KIND == 'START_TLS':
109 if self.TLS_KIND == 'START_TLS':
106 server.start_tls_s()
110 server.start_tls_s()
107
111
108 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
112 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
109 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
113 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
110
114
111 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
115 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
112 username)
116 username)
113 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
117 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
114 filt, self.LDAP_SERVER)
118 filt, self.LDAP_SERVER)
115 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
119 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
116 filt)
120 filt)
117
121
118 if not lobjects:
122 if not lobjects:
119 raise ldap.NO_SUCH_OBJECT()
123 raise ldap.NO_SUCH_OBJECT()
120
124
121 for (dn, _attrs) in lobjects:
125 for (dn, _attrs) in lobjects:
122 if dn is None:
126 if dn is None:
123 continue
127 continue
124
128
125 try:
129 try:
126 server.simple_bind_s(dn, password)
130 server.simple_bind_s(dn, password)
127 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
131 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
128 '(objectClass=*)')[0][1]
132 '(objectClass=*)')[0][1]
129 break
133 break
130
134
131 except ldap.INVALID_CREDENTIALS, e:
135 except ldap.INVALID_CREDENTIALS, e:
132 log.debug("LDAP rejected password for user '%s' (%s): %s",
136 log.debug("LDAP rejected password for user '%s' (%s): %s",
133 uid, username, dn)
137 uid, username, dn)
134
138
135 else:
139 else:
136 log.debug("No matching LDAP objects for authentication "
140 log.debug("No matching LDAP objects for authentication "
137 "of '%s' (%s)", uid, username)
141 "of '%s' (%s)", uid, username)
138 raise LdapPasswordError()
142 raise LdapPasswordError()
139
143
140 except ldap.NO_SUCH_OBJECT, e:
144 except ldap.NO_SUCH_OBJECT, e:
141 log.debug("LDAP says no such user '%s' (%s)", uid, username)
145 log.debug("LDAP says no such user '%s' (%s)", uid, username)
142 raise LdapUsernameError()
146 raise LdapUsernameError()
143 except ldap.SERVER_DOWN, e:
147 except ldap.SERVER_DOWN, e:
144 raise LdapConnectionError("LDAP can't access "
148 raise LdapConnectionError("LDAP can't access "
145 "authentication server")
149 "authentication server")
146
150
147 return (dn, attrs)
151 return (dn, attrs)
@@ -1,411 +1,412 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.celerylib.tasks
3 rhodecode.lib.celerylib.tasks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode task modules, containing all task that suppose to be run
6 RhodeCode task modules, containing all task that suppose to be run
7 by celery daemon
7 by celery daemon
8
8
9 :created_on: Oct 6, 2010
9 :created_on: Oct 6, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 from celery.decorators import task
26 from celery.decorators import task
27
27
28 import os
28 import os
29 import traceback
29 import traceback
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from time import mktime
33 from time import mktime
34 from operator import itemgetter
34 from operator import itemgetter
35 from string import lower
35 from string import lower
36
36
37 from pylons import config, url
37 from pylons import config, url
38 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
39
39
40 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
40 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
42 __get_lockkey, LockHeld, DaemonLock
42 __get_lockkey, LockHeld, DaemonLock
43 from rhodecode.lib.helpers import person
43 from rhodecode.lib.helpers import person
44 from rhodecode.lib.smtp_mailer import SmtpMailer
44 from rhodecode.lib.smtp_mailer import SmtpMailer
45 from rhodecode.lib.utils import add_cache
45 from rhodecode.lib.utils import add_cache
46 from rhodecode.lib.compat import json, OrderedDict
46 from rhodecode.lib.compat import json, OrderedDict
47
47
48 from rhodecode.model import init_model
48 from rhodecode.model import init_model
49 from rhodecode.model import meta
49 from rhodecode.model import meta
50 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
50 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
51
51
52 from vcs.backends import get_repo
52 from vcs.backends import get_repo
53
53
54 from sqlalchemy import engine_from_config
54 from sqlalchemy import engine_from_config
55
55
56 add_cache(config)
56 add_cache(config)
57
57
58
58
59
59
60 __all__ = ['whoosh_index', 'get_commits_stats',
60 __all__ = ['whoosh_index', 'get_commits_stats',
61 'reset_user_password', 'send_email']
61 'reset_user_password', 'send_email']
62
62
63 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
63 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
64
64
65
65
66 def get_session():
66 def get_session():
67 if CELERY_ON:
67 if CELERY_ON:
68 engine = engine_from_config(config, 'sqlalchemy.db1.')
68 engine = engine_from_config(config, 'sqlalchemy.db1.')
69 init_model(engine)
69 init_model(engine)
70 sa = meta.Session()
70 sa = meta.Session()
71 return sa
71 return sa
72
72
73
73
74 def get_repos_path():
74 def get_repos_path():
75 sa = get_session()
75 sa = get_session()
76 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
76 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
77 return q.ui_value
77 return q.ui_value
78
78
79
79
80 @task(ignore_result=True)
80 @task(ignore_result=True)
81 @locked_task
81 @locked_task
82 def whoosh_index(repo_location, full_index):
82 def whoosh_index(repo_location, full_index):
83 #log = whoosh_index.get_logger()
83 #log = whoosh_index.get_logger()
84 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
84 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
85 index_location = config['index_dir']
85 index_location = config['index_dir']
86 WhooshIndexingDaemon(index_location=index_location,
86 WhooshIndexingDaemon(index_location=index_location,
87 repo_location=repo_location, sa=get_session())\
87 repo_location=repo_location, sa=get_session())\
88 .run(full_index=full_index)
88 .run(full_index=full_index)
89
89
90
90
91 @task(ignore_result=True)
91 @task(ignore_result=True)
92 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
92 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
93 try:
93 try:
94 log = get_commits_stats.get_logger()
94 log = get_commits_stats.get_logger()
95 except:
95 except:
96 log = logging.getLogger(__name__)
96 log = logging.getLogger(__name__)
97
97
98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
99 ts_max_y)
99 ts_max_y)
100 lockkey_path = config['here']
100 lockkey_path = config['here']
101
101
102 log.info('running task with lockkey %s', lockkey)
102 log.info('running task with lockkey %s', lockkey)
103 try:
103 try:
104 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
104 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
105
105
106 #for js data compatibilty cleans the key for person from '
106 #for js data compatibilty cleans the key for person from '
107 akc = lambda k: person(k).replace('"', "")
107 akc = lambda k: person(k).replace('"', "")
108
108
109 co_day_auth_aggr = {}
109 co_day_auth_aggr = {}
110 commits_by_day_aggregate = {}
110 commits_by_day_aggregate = {}
111 repos_path = get_repos_path()
111 repos_path = get_repos_path()
112 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
112 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
113 repo_size = len(repo.revisions)
113 repo_size = len(repo.revisions)
114 #return if repo have no revisions
114 #return if repo have no revisions
115 if repo_size < 1:
115 if repo_size < 1:
116 lock.release()
116 lock.release()
117 return True
117 return True
118
118
119 skip_date_limit = True
119 skip_date_limit = True
120 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
120 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
121 last_rev = 0
121 last_rev = 0
122 last_cs = None
122 last_cs = None
123 timegetter = itemgetter('time')
123 timegetter = itemgetter('time')
124
124
125 sa = get_session()
125 sa = get_session()
126
126
127 dbrepo = sa.query(Repository)\
127 dbrepo = sa.query(Repository)\
128 .filter(Repository.repo_name == repo_name).scalar()
128 .filter(Repository.repo_name == repo_name).scalar()
129 cur_stats = sa.query(Statistics)\
129 cur_stats = sa.query(Statistics)\
130 .filter(Statistics.repository == dbrepo).scalar()
130 .filter(Statistics.repository == dbrepo).scalar()
131
131
132 if cur_stats is not None:
132 if cur_stats is not None:
133 last_rev = cur_stats.stat_on_revision
133 last_rev = cur_stats.stat_on_revision
134
134
135 if last_rev == repo.get_changeset().revision and repo_size > 1:
135 if last_rev == repo.get_changeset().revision and repo_size > 1:
136 #pass silently without any work if we're not on first revision or
136 #pass silently without any work if we're not on first revision or
137 #current state of parsing revision(from db marker) is the
137 #current state of parsing revision(from db marker) is the
138 #last revision
138 #last revision
139 lock.release()
139 lock.release()
140 return True
140 return True
141
141
142 if cur_stats:
142 if cur_stats:
143 commits_by_day_aggregate = OrderedDict(json.loads(
143 commits_by_day_aggregate = OrderedDict(json.loads(
144 cur_stats.commit_activity_combined))
144 cur_stats.commit_activity_combined))
145 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
145 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
146
146
147 log.debug('starting parsing %s', parse_limit)
147 log.debug('starting parsing %s', parse_limit)
148 lmktime = mktime
148 lmktime = mktime
149
149
150 last_rev = last_rev + 1 if last_rev > 0 else last_rev
150 last_rev = last_rev + 1 if last_rev > 0 else last_rev
151
151
152 for cs in repo[last_rev:last_rev + parse_limit]:
152 for cs in repo[last_rev:last_rev + parse_limit]:
153 last_cs = cs # remember last parsed changeset
153 last_cs = cs # remember last parsed changeset
154 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
154 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
155 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
155 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
156
156
157 if akc(cs.author) in co_day_auth_aggr:
157 if akc(cs.author) in co_day_auth_aggr:
158 try:
158 try:
159 l = [timegetter(x) for x in
159 l = [timegetter(x) for x in
160 co_day_auth_aggr[akc(cs.author)]['data']]
160 co_day_auth_aggr[akc(cs.author)]['data']]
161 time_pos = l.index(k)
161 time_pos = l.index(k)
162 except ValueError:
162 except ValueError:
163 time_pos = False
163 time_pos = False
164
164
165 if time_pos >= 0 and time_pos is not False:
165 if time_pos >= 0 and time_pos is not False:
166
166
167 datadict = \
167 datadict = \
168 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
168 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
169
169
170 datadict["commits"] += 1
170 datadict["commits"] += 1
171 datadict["added"] += len(cs.added)
171 datadict["added"] += len(cs.added)
172 datadict["changed"] += len(cs.changed)
172 datadict["changed"] += len(cs.changed)
173 datadict["removed"] += len(cs.removed)
173 datadict["removed"] += len(cs.removed)
174
174
175 else:
175 else:
176 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
176 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
177
177
178 datadict = {"time": k,
178 datadict = {"time": k,
179 "commits": 1,
179 "commits": 1,
180 "added": len(cs.added),
180 "added": len(cs.added),
181 "changed": len(cs.changed),
181 "changed": len(cs.changed),
182 "removed": len(cs.removed),
182 "removed": len(cs.removed),
183 }
183 }
184 co_day_auth_aggr[akc(cs.author)]['data']\
184 co_day_auth_aggr[akc(cs.author)]['data']\
185 .append(datadict)
185 .append(datadict)
186
186
187 else:
187 else:
188 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
188 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
189 co_day_auth_aggr[akc(cs.author)] = {
189 co_day_auth_aggr[akc(cs.author)] = {
190 "label": akc(cs.author),
190 "label": akc(cs.author),
191 "data": [{"time":k,
191 "data": [{"time":k,
192 "commits":1,
192 "commits":1,
193 "added":len(cs.added),
193 "added":len(cs.added),
194 "changed":len(cs.changed),
194 "changed":len(cs.changed),
195 "removed":len(cs.removed),
195 "removed":len(cs.removed),
196 }],
196 }],
197 "schema": ["commits"],
197 "schema": ["commits"],
198 }
198 }
199
199
200 #gather all data by day
200 #gather all data by day
201 if k in commits_by_day_aggregate:
201 if k in commits_by_day_aggregate:
202 commits_by_day_aggregate[k] += 1
202 commits_by_day_aggregate[k] += 1
203 else:
203 else:
204 commits_by_day_aggregate[k] = 1
204 commits_by_day_aggregate[k] = 1
205
205
206 overview_data = sorted(commits_by_day_aggregate.items(),
206 overview_data = sorted(commits_by_day_aggregate.items(),
207 key=itemgetter(0))
207 key=itemgetter(0))
208
208
209 if not co_day_auth_aggr:
209 if not co_day_auth_aggr:
210 co_day_auth_aggr[akc(repo.contact)] = {
210 co_day_auth_aggr[akc(repo.contact)] = {
211 "label": akc(repo.contact),
211 "label": akc(repo.contact),
212 "data": [0, 1],
212 "data": [0, 1],
213 "schema": ["commits"],
213 "schema": ["commits"],
214 }
214 }
215
215
216 stats = cur_stats if cur_stats else Statistics()
216 stats = cur_stats if cur_stats else Statistics()
217 stats.commit_activity = json.dumps(co_day_auth_aggr)
217 stats.commit_activity = json.dumps(co_day_auth_aggr)
218 stats.commit_activity_combined = json.dumps(overview_data)
218 stats.commit_activity_combined = json.dumps(overview_data)
219
219
220 log.debug('last revison %s', last_rev)
220 log.debug('last revison %s', last_rev)
221 leftovers = len(repo.revisions[last_rev:])
221 leftovers = len(repo.revisions[last_rev:])
222 log.debug('revisions to parse %s', leftovers)
222 log.debug('revisions to parse %s', leftovers)
223
223
224 if last_rev == 0 or leftovers < parse_limit:
224 if last_rev == 0 or leftovers < parse_limit:
225 log.debug('getting code trending stats')
225 log.debug('getting code trending stats')
226 stats.languages = json.dumps(__get_codes_stats(repo_name))
226 stats.languages = json.dumps(__get_codes_stats(repo_name))
227
227
228 try:
228 try:
229 stats.repository = dbrepo
229 stats.repository = dbrepo
230 stats.stat_on_revision = last_cs.revision if last_cs else 0
230 stats.stat_on_revision = last_cs.revision if last_cs else 0
231 sa.add(stats)
231 sa.add(stats)
232 sa.commit()
232 sa.commit()
233 except:
233 except:
234 log.error(traceback.format_exc())
234 log.error(traceback.format_exc())
235 sa.rollback()
235 sa.rollback()
236 lock.release()
236 lock.release()
237 return False
237 return False
238
238
239 #final release
239 #final release
240 lock.release()
240 lock.release()
241
241
242 #execute another task if celery is enabled
242 #execute another task if celery is enabled
243 if len(repo.revisions) > 1 and CELERY_ON:
243 if len(repo.revisions) > 1 and CELERY_ON:
244 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
244 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
245 return True
245 return True
246 except LockHeld:
246 except LockHeld:
247 log.info('LockHeld')
247 log.info('LockHeld')
248 return 'Task with key %s already running' % lockkey
248 return 'Task with key %s already running' % lockkey
249
249
250 @task(ignore_result=True)
250 @task(ignore_result=True)
251 def send_password_link(user_email):
251 def send_password_link(user_email):
252 try:
252 try:
253 log = reset_user_password.get_logger()
253 log = reset_user_password.get_logger()
254 except:
254 except:
255 log = logging.getLogger(__name__)
255 log = logging.getLogger(__name__)
256
256
257 from rhodecode.lib import auth
257 from rhodecode.lib import auth
258 from rhodecode.model.db import User
258 from rhodecode.model.db import User
259
259
260 try:
260 try:
261 sa = get_session()
261 sa = get_session()
262 user = sa.query(User).filter(User.email == user_email).scalar()
262 user = sa.query(User).filter(User.email == user_email).scalar()
263
263
264 if user:
264 if user:
265 link = url('reset_password_confirmation', key=user.api_key,
265 link = url('reset_password_confirmation', key=user.api_key,
266 qualified=True)
266 qualified=True)
267 tmpl = """
267 tmpl = """
268 Hello %s
268 Hello %s
269
269
270 We received a request to create a new password for your account.
270 We received a request to create a new password for your account.
271
271
272 You can generate it by clicking following URL:
272 You can generate it by clicking following URL:
273
273
274 %s
274 %s
275
275
276 If you didn't request new password please ignore this email.
276 If you didn't request new password please ignore this email.
277 """
277 """
278 run_task(send_email, user_email,
278 run_task(send_email, user_email,
279 "RhodeCode password reset link",
279 "RhodeCode password reset link",
280 tmpl % (user.short_contact, link))
280 tmpl % (user.short_contact, link))
281 log.info('send new password mail to %s', user_email)
281 log.info('send new password mail to %s', user_email)
282
282
283 except:
283 except:
284 log.error('Failed to update user password')
284 log.error('Failed to update user password')
285 log.error(traceback.format_exc())
285 log.error(traceback.format_exc())
286 return False
286 return False
287
287
288 return True
288 return True
289
289
290 @task(ignore_result=True)
290 @task(ignore_result=True)
291 def reset_user_password(user_email):
291 def reset_user_password(user_email):
292 try:
292 try:
293 log = reset_user_password.get_logger()
293 log = reset_user_password.get_logger()
294 except:
294 except:
295 log = logging.getLogger(__name__)
295 log = logging.getLogger(__name__)
296
296
297 from rhodecode.lib import auth
297 from rhodecode.lib import auth
298 from rhodecode.model.db import User
298 from rhodecode.model.db import User
299
299
300 try:
300 try:
301 try:
301 try:
302 sa = get_session()
302 sa = get_session()
303 user = sa.query(User).filter(User.email == user_email).scalar()
303 user = sa.query(User).filter(User.email == user_email).scalar()
304 new_passwd = auth.PasswordGenerator().gen_password(8,
304 new_passwd = auth.PasswordGenerator().gen_password(8,
305 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
305 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
306 if user:
306 if user:
307 user.password = auth.get_crypt_password(new_passwd)
307 user.password = auth.get_crypt_password(new_passwd)
308 user.api_key = auth.generate_api_key(user.username)
308 user.api_key = auth.generate_api_key(user.username)
309 sa.add(user)
309 sa.add(user)
310 sa.commit()
310 sa.commit()
311 log.info('change password for %s', user_email)
311 log.info('change password for %s', user_email)
312 if new_passwd is None:
312 if new_passwd is None:
313 raise Exception('unable to generate new password')
313 raise Exception('unable to generate new password')
314
314
315 except:
315 except:
316 log.error(traceback.format_exc())
316 log.error(traceback.format_exc())
317 sa.rollback()
317 sa.rollback()
318
318
319 run_task(send_email, user_email,
319 run_task(send_email, user_email,
320 "Your new RhodeCode password",
320 "Your new RhodeCode password",
321 'Your new RhodeCode password:%s' % (new_passwd))
321 'Your new RhodeCode password:%s' % (new_passwd))
322 log.info('send new password mail to %s', user_email)
322 log.info('send new password mail to %s', user_email)
323
323
324 except:
324 except:
325 log.error('Failed to update user password')
325 log.error('Failed to update user password')
326 log.error(traceback.format_exc())
326 log.error(traceback.format_exc())
327
327
328 return True
328 return True
329
329
330
330
331 @task(ignore_result=True)
331 @task(ignore_result=True)
332 def send_email(recipients, subject, body):
332 def send_email(recipients, subject, body):
333 """
333 """
334 Sends an email with defined parameters from the .ini files.
334 Sends an email with defined parameters from the .ini files.
335
335
336 :param recipients: list of recipients, it this is empty the defined email
336 :param recipients: list of recipients, it this is empty the defined email
337 address from field 'email_to' is used instead
337 address from field 'email_to' is used instead
338 :param subject: subject of the mail
338 :param subject: subject of the mail
339 :param body: body of the mail
339 :param body: body of the mail
340 """
340 """
341 try:
341 try:
342 log = send_email.get_logger()
342 log = send_email.get_logger()
343 except:
343 except:
344 log = logging.getLogger(__name__)
344 log = logging.getLogger(__name__)
345
345
346 email_config = config
346 email_config = config
347
347
348 if not recipients:
348 if not recipients:
349 recipients = [email_config.get('email_to')]
349 recipients = [email_config.get('email_to')]
350
350
351 mail_from = email_config.get('app_email_from')
351 mail_from = email_config.get('app_email_from')
352 user = email_config.get('smtp_username')
352 user = email_config.get('smtp_username')
353 passwd = email_config.get('smtp_password')
353 passwd = email_config.get('smtp_password')
354 mail_server = email_config.get('smtp_server')
354 mail_server = email_config.get('smtp_server')
355 mail_port = email_config.get('smtp_port')
355 mail_port = email_config.get('smtp_port')
356 tls = str2bool(email_config.get('smtp_use_tls'))
356 tls = str2bool(email_config.get('smtp_use_tls'))
357 ssl = str2bool(email_config.get('smtp_use_ssl'))
357 ssl = str2bool(email_config.get('smtp_use_ssl'))
358 debug = str2bool(config.get('debug'))
358 debug = str2bool(config.get('debug'))
359 smtp_auth = email_config.get('smtp_auth')
359
360
360 try:
361 try:
361 m = SmtpMailer(mail_from, user, passwd, mail_server,
362 m = SmtpMailer(mail_from, user, passwd, mail_server,smtp_auth,
362 mail_port, ssl, tls, debug=debug)
363 mail_port, ssl, tls, debug=debug)
363 m.send(recipients, subject, body)
364 m.send(recipients, subject, body)
364 except:
365 except:
365 log.error('Mail sending failed')
366 log.error('Mail sending failed')
366 log.error(traceback.format_exc())
367 log.error(traceback.format_exc())
367 return False
368 return False
368 return True
369 return True
369
370
370
371
371 @task(ignore_result=True)
372 @task(ignore_result=True)
372 def create_repo_fork(form_data, cur_user):
373 def create_repo_fork(form_data, cur_user):
373 from rhodecode.model.repo import RepoModel
374 from rhodecode.model.repo import RepoModel
374 from vcs import get_backend
375 from vcs import get_backend
375
376
376 try:
377 try:
377 log = create_repo_fork.get_logger()
378 log = create_repo_fork.get_logger()
378 except:
379 except:
379 log = logging.getLogger(__name__)
380 log = logging.getLogger(__name__)
380
381
381 repo_model = RepoModel(get_session())
382 repo_model = RepoModel(get_session())
382 repo_model.create(form_data, cur_user, just_db=True, fork=True)
383 repo_model.create(form_data, cur_user, just_db=True, fork=True)
383 repo_name = form_data['repo_name']
384 repo_name = form_data['repo_name']
384 repos_path = get_repos_path()
385 repos_path = get_repos_path()
385 repo_path = os.path.join(repos_path, repo_name)
386 repo_path = os.path.join(repos_path, repo_name)
386 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
387 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
387 alias = form_data['repo_type']
388 alias = form_data['repo_type']
388
389
389 log.info('creating repo fork %s as %s', repo_name, repo_path)
390 log.info('creating repo fork %s as %s', repo_name, repo_path)
390 backend = get_backend(alias)
391 backend = get_backend(alias)
391 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
392 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
392
393
393
394
394 def __get_codes_stats(repo_name):
395 def __get_codes_stats(repo_name):
395 repos_path = get_repos_path()
396 repos_path = get_repos_path()
396 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
397 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
397 tip = repo.get_changeset()
398 tip = repo.get_changeset()
398 code_stats = {}
399 code_stats = {}
399
400
400 def aggregate(cs):
401 def aggregate(cs):
401 for f in cs[2]:
402 for f in cs[2]:
402 ext = lower(f.extension)
403 ext = lower(f.extension)
403 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
404 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
404 if ext in code_stats:
405 if ext in code_stats:
405 code_stats[ext] += 1
406 code_stats[ext] += 1
406 else:
407 else:
407 code_stats[ext] = 1
408 code_stats[ext] = 1
408
409
409 map(aggregate, tip.walk('/'))
410 map(aggregate, tip.walk('/'))
410
411
411 return code_stats or {}
412 return code_stats or {}
@@ -1,289 +1,291 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplegit
3 rhodecode.lib.middleware.simplegit
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 It's implemented with basic auth function
7 It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from dulwich import server as dulserver
31 from dulwich import server as dulserver
32
32
33
33
34 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
34 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35
35
36 def handle(self):
36 def handle(self):
37 write = lambda x: self.proto.write_sideband(1, x)
37 write = lambda x: self.proto.write_sideband(1, x)
38
38
39 graph_walker = dulserver.ProtocolGraphWalker(self,
39 graph_walker = dulserver.ProtocolGraphWalker(self,
40 self.repo.object_store,
40 self.repo.object_store,
41 self.repo.get_peeled)
41 self.repo.get_peeled)
42 objects_iter = self.repo.fetch_objects(
42 objects_iter = self.repo.fetch_objects(
43 graph_walker.determine_wants, graph_walker, self.progress,
43 graph_walker.determine_wants, graph_walker, self.progress,
44 get_tagged=self.get_tagged)
44 get_tagged=self.get_tagged)
45
45
46 # Do they want any objects?
46 # Do they want any objects?
47 if objects_iter is None or len(objects_iter) == 0:
47 if objects_iter is None or len(objects_iter) == 0:
48 return
48 return
49
49
50 self.progress("counting objects: %d, done.\n" % len(objects_iter))
50 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
51 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
52 objects_iter, len(objects_iter))
52 objects_iter, len(objects_iter))
53 messages = []
53 messages = []
54 messages.append('thank you for using rhodecode')
54 messages.append('thank you for using rhodecode')
55
55
56 for msg in messages:
56 for msg in messages:
57 self.progress(msg + "\n")
57 self.progress(msg + "\n")
58 # we are done
58 # we are done
59 self.proto.write("0000")
59 self.proto.write("0000")
60
60
61 dulserver.DEFAULT_HANDLERS = {
61 dulserver.DEFAULT_HANDLERS = {
62 'git-upload-pack': SimpleGitUploadPackHandler,
62 'git-upload-pack': SimpleGitUploadPackHandler,
63 'git-receive-pack': dulserver.ReceivePackHandler,
63 'git-receive-pack': dulserver.ReceivePackHandler,
64 }
64 }
65
65
66 from dulwich.repo import Repo
66 from dulwich.repo import Repo
67 from dulwich.web import HTTPGitApplication
67 from dulwich.web import HTTPGitApplication
68
68
69 from paste.auth.basic import AuthBasicAuthenticator
69 from paste.auth.basic import AuthBasicAuthenticator
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71
71
72 from rhodecode.lib import safe_str
72 from rhodecode.lib import safe_str
73 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
73 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
74 from rhodecode.lib.utils import invalidate_cache, is_valid_repo
74 from rhodecode.lib.utils import invalidate_cache, is_valid_repo
75 from rhodecode.model.db import User
75 from rhodecode.model.db import User
76
76
77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
78
78
79 log = logging.getLogger(__name__)
79 log = logging.getLogger(__name__)
80
80
81
81
82 def is_git(environ):
82 def is_git(environ):
83 """Returns True if request's target is git server.
83 """Returns True if request's target is git server.
84 ``HTTP_USER_AGENT`` would then have git client version given.
84 ``HTTP_USER_AGENT`` would then have git client version given.
85
85
86 :param environ:
86 :param environ:
87 """
87 """
88 http_user_agent = environ.get('HTTP_USER_AGENT')
88 http_user_agent = environ.get('HTTP_USER_AGENT')
89 if http_user_agent and http_user_agent.startswith('git'):
89 if http_user_agent and http_user_agent.startswith('git'):
90 return True
90 return True
91 return False
91 return False
92
92
93
93
94 class SimpleGit(object):
94 class SimpleGit(object):
95
95
96 def __init__(self, application, config):
96 def __init__(self, application, config):
97 self.application = application
97 self.application = application
98 self.config = config
98 self.config = config
99 # base path of repo locations
99 # base path of repo locations
100 self.basepath = self.config['base_path']
100 self.basepath = self.config['base_path']
101 #authenticate this mercurial request using authfunc
101 #authenticate this mercurial request using authfunc
102 self.authenticate = AuthBasicAuthenticator('', authfunc)
102 self.authenticate = AuthBasicAuthenticator('', authfunc)
103
103
104 def __call__(self, environ, start_response):
104 def __call__(self, environ, start_response):
105 if not is_git(environ):
105 if not is_git(environ):
106 return self.application(environ, start_response)
106 return self.application(environ, start_response)
107
107
108 proxy_key = 'HTTP_X_REAL_IP'
108 proxy_key = 'HTTP_X_REAL_IP'
109 def_key = 'REMOTE_ADDR'
109 def_key = 'REMOTE_ADDR'
110 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
110 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
111 username = None
111 username = None
112 # skip passing error to error controller
112 # skip passing error to error controller
113 environ['pylons.status_code_redirect'] = True
113 environ['pylons.status_code_redirect'] = True
114
114
115 #======================================================================
115 #======================================================================
116 # EXTRACT REPOSITORY NAME FROM ENV
116 # EXTRACT REPOSITORY NAME FROM ENV
117 #======================================================================
117 #======================================================================
118 try:
118 try:
119 repo_name = self.__get_repository(environ)
119 repo_name = self.__get_repository(environ)
120 log.debug('Extracted repo name is %s' % repo_name)
120 log.debug('Extracted repo name is %s' % repo_name)
121 except:
121 except:
122 return HTTPInternalServerError()(environ, start_response)
122 return HTTPInternalServerError()(environ, start_response)
123
123
124 #======================================================================
124 #======================================================================
125 # GET ACTION PULL or PUSH
125 # GET ACTION PULL or PUSH
126 #======================================================================
126 #======================================================================
127 action = self.__get_action(environ)
127 action = self.__get_action(environ)
128
128
129 #======================================================================
129 #======================================================================
130 # CHECK ANONYMOUS PERMISSION
130 # CHECK ANONYMOUS PERMISSION
131 #======================================================================
131 #======================================================================
132 if action in ['pull', 'push']:
132 if action in ['pull', 'push']:
133 anonymous_user = self.__get_user('default')
133 anonymous_user = self.__get_user('default')
134 username = anonymous_user.username
134 username = anonymous_user.username
135 anonymous_perm = self.__check_permission(action,
135 anonymous_perm = self.__check_permission(action,
136 anonymous_user,
136 anonymous_user,
137 repo_name)
137 repo_name)
138
138
139 if anonymous_perm is not True or anonymous_user.active is False:
139 if anonymous_perm is not True or anonymous_user.active is False:
140 if anonymous_perm is not True:
140 if anonymous_perm is not True:
141 log.debug('Not enough credentials to access this '
141 log.debug('Not enough credentials to access this '
142 'repository as anonymous user')
142 'repository as anonymous user')
143 if anonymous_user.active is False:
143 if anonymous_user.active is False:
144 log.debug('Anonymous access is disabled, running '
144 log.debug('Anonymous access is disabled, running '
145 'authentication')
145 'authentication')
146 #==============================================================
146 #==============================================================
147 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
147 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
148 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
148 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
149 #==============================================================
149 #==============================================================
150
150
151 if not REMOTE_USER(environ):
151 if not REMOTE_USER(environ):
152 self.authenticate.realm = \
152 self.authenticate.realm = \
153 safe_str(self.config['rhodecode_realm'])
153 safe_str(self.config['rhodecode_realm'])
154 result = self.authenticate(environ)
154 result = self.authenticate(environ)
155 if isinstance(result, str):
155 if isinstance(result, str):
156 AUTH_TYPE.update(environ, 'basic')
156 AUTH_TYPE.update(environ, 'basic')
157 REMOTE_USER.update(environ, result)
157 REMOTE_USER.update(environ, result)
158 else:
158 else:
159 return result.wsgi_application(environ, start_response)
159 return result.wsgi_application(environ, start_response)
160
160
161 #==============================================================
161 #==============================================================
162 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
162 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
163 # BASIC AUTH
163 # BASIC AUTH
164 #==============================================================
164 #==============================================================
165
165
166 if action in ['pull', 'push']:
166 if action in ['pull', 'push']:
167 username = REMOTE_USER(environ)
167 username = REMOTE_USER(environ)
168 try:
168 try:
169 user = self.__get_user(username)
169 user = self.__get_user(username)
170 if user is None:
171 return HTTPForbidden()(environ, start_response)
170 username = user.username
172 username = user.username
171 except:
173 except:
172 log.error(traceback.format_exc())
174 log.error(traceback.format_exc())
173 return HTTPInternalServerError()(environ,
175 return HTTPInternalServerError()(environ,
174 start_response)
176 start_response)
175
177
176 #check permissions for this repository
178 #check permissions for this repository
177 perm = self.__check_permission(action, user,
179 perm = self.__check_permission(action, user,
178 repo_name)
180 repo_name)
179 if perm is not True:
181 if perm is not True:
180 return HTTPForbidden()(environ, start_response)
182 return HTTPForbidden()(environ, start_response)
181
183
182 extras = {'ip': ipaddr,
184 extras = {'ip': ipaddr,
183 'username': username,
185 'username': username,
184 'action': action,
186 'action': action,
185 'repository': repo_name}
187 'repository': repo_name}
186
188
187 #===================================================================
189 #===================================================================
188 # GIT REQUEST HANDLING
190 # GIT REQUEST HANDLING
189 #===================================================================
191 #===================================================================
190
192
191 repo_path = safe_str(os.path.join(self.basepath, repo_name))
193 repo_path = safe_str(os.path.join(self.basepath, repo_name))
192 log.debug('Repository path is %s' % repo_path)
194 log.debug('Repository path is %s' % repo_path)
193
195
194 # quick check if that dir exists...
196 # quick check if that dir exists...
195 if is_valid_repo(repo_name, self.basepath) is False:
197 if is_valid_repo(repo_name, self.basepath) is False:
196 return HTTPNotFound()(environ, start_response)
198 return HTTPNotFound()(environ, start_response)
197
199
198 try:
200 try:
199 #invalidate cache on push
201 #invalidate cache on push
200 if action == 'push':
202 if action == 'push':
201 self.__invalidate_cache(repo_name)
203 self.__invalidate_cache(repo_name)
202
204
203 app = self.__make_app(repo_name, repo_path)
205 app = self.__make_app(repo_name, repo_path)
204 return app(environ, start_response)
206 return app(environ, start_response)
205 except Exception:
207 except Exception:
206 log.error(traceback.format_exc())
208 log.error(traceback.format_exc())
207 return HTTPInternalServerError()(environ, start_response)
209 return HTTPInternalServerError()(environ, start_response)
208
210
209 def __make_app(self, repo_name, repo_path):
211 def __make_app(self, repo_name, repo_path):
210 """
212 """
211 Make an wsgi application using dulserver
213 Make an wsgi application using dulserver
212
214
213 :param repo_name: name of the repository
215 :param repo_name: name of the repository
214 :param repo_path: full path to the repository
216 :param repo_path: full path to the repository
215 """
217 """
216
218
217 _d = {'/' + repo_name: Repo(repo_path)}
219 _d = {'/' + repo_name: Repo(repo_path)}
218 backend = dulserver.DictBackend(_d)
220 backend = dulserver.DictBackend(_d)
219 gitserve = HTTPGitApplication(backend)
221 gitserve = HTTPGitApplication(backend)
220
222
221 return gitserve
223 return gitserve
222
224
223 def __check_permission(self, action, user, repo_name):
225 def __check_permission(self, action, user, repo_name):
224 """
226 """
225 Checks permissions using action (push/pull) user and repository
227 Checks permissions using action (push/pull) user and repository
226 name
228 name
227
229
228 :param action: push or pull action
230 :param action: push or pull action
229 :param user: user instance
231 :param user: user instance
230 :param repo_name: repository name
232 :param repo_name: repository name
231 """
233 """
232 if action == 'push':
234 if action == 'push':
233 if not HasPermissionAnyMiddleware('repository.write',
235 if not HasPermissionAnyMiddleware('repository.write',
234 'repository.admin')(user,
236 'repository.admin')(user,
235 repo_name):
237 repo_name):
236 return False
238 return False
237
239
238 else:
240 else:
239 #any other action need at least read permission
241 #any other action need at least read permission
240 if not HasPermissionAnyMiddleware('repository.read',
242 if not HasPermissionAnyMiddleware('repository.read',
241 'repository.write',
243 'repository.write',
242 'repository.admin')(user,
244 'repository.admin')(user,
243 repo_name):
245 repo_name):
244 return False
246 return False
245
247
246 return True
248 return True
247
249
248 def __get_repository(self, environ):
250 def __get_repository(self, environ):
249 """
251 """
250 Get's repository name out of PATH_INFO header
252 Get's repository name out of PATH_INFO header
251
253
252 :param environ: environ where PATH_INFO is stored
254 :param environ: environ where PATH_INFO is stored
253 """
255 """
254 try:
256 try:
255 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
257 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
256 if repo_name.endswith('/'):
258 if repo_name.endswith('/'):
257 repo_name = repo_name.rstrip('/')
259 repo_name = repo_name.rstrip('/')
258 except:
260 except:
259 log.error(traceback.format_exc())
261 log.error(traceback.format_exc())
260 raise
262 raise
261 repo_name = repo_name.split('/')[0]
263 repo_name = repo_name.split('/')[0]
262 return repo_name
264 return repo_name
263
265
264 def __get_user(self, username):
266 def __get_user(self, username):
265 return User.get_by_username(username)
267 return User.get_by_username(username)
266
268
267 def __get_action(self, environ):
269 def __get_action(self, environ):
268 """Maps git request commands into a pull or push command.
270 """Maps git request commands into a pull or push command.
269
271
270 :param environ:
272 :param environ:
271 """
273 """
272 service = environ['QUERY_STRING'].split('=')
274 service = environ['QUERY_STRING'].split('=')
273 if len(service) > 1:
275 if len(service) > 1:
274 service_cmd = service[1]
276 service_cmd = service[1]
275 mapping = {'git-receive-pack': 'push',
277 mapping = {'git-receive-pack': 'push',
276 'git-upload-pack': 'pull',
278 'git-upload-pack': 'pull',
277 }
279 }
278
280
279 return mapping.get(service_cmd,
281 return mapping.get(service_cmd,
280 service_cmd if service_cmd else 'other')
282 service_cmd if service_cmd else 'other')
281 else:
283 else:
282 return 'other'
284 return 'other'
283
285
284 def __invalidate_cache(self, repo_name):
286 def __invalidate_cache(self, repo_name):
285 """we know that some change was made to repositories and we should
287 """we know that some change was made to repositories and we should
286 invalidate the cache to see the changes right away but only for
288 invalidate the cache to see the changes right away but only for
287 push requests"""
289 push requests"""
288 invalidate_cache('get_repo_cached_%s' % repo_name)
290 invalidate_cache('get_repo_cached_%s' % repo_name)
289
291
@@ -1,288 +1,290 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplehg
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleHG middleware for handling mercurial protocol request
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
7 (push/clone etc.). It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from mercurial.error import RepoError
31 from mercurial.error import RepoError
32 from mercurial.hgweb import hgweb_mod
32 from mercurial.hgweb import hgweb_mod
33
33
34 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.auth.basic import AuthBasicAuthenticator
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36
36
37 from rhodecode.lib import safe_str
37 from rhodecode.lib import safe_str
38 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
38 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
39 from rhodecode.lib.utils import make_ui, invalidate_cache, \
39 from rhodecode.lib.utils import make_ui, invalidate_cache, \
40 is_valid_repo, ui_sections
40 is_valid_repo, ui_sections
41 from rhodecode.model.db import User
41 from rhodecode.model.db import User
42
42
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 def is_mercurial(environ):
48 def is_mercurial(environ):
49 """Returns True if request's target is mercurial server - header
49 """Returns True if request's target is mercurial server - header
50 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
50 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 """
51 """
52 http_accept = environ.get('HTTP_ACCEPT')
52 http_accept = environ.get('HTTP_ACCEPT')
53 if http_accept and http_accept.startswith('application/mercurial'):
53 if http_accept and http_accept.startswith('application/mercurial'):
54 return True
54 return True
55 return False
55 return False
56
56
57
57
58 class SimpleHg(object):
58 class SimpleHg(object):
59
59
60 def __init__(self, application, config):
60 def __init__(self, application, config):
61 self.application = application
61 self.application = application
62 self.config = config
62 self.config = config
63 # base path of repo locations
63 # base path of repo locations
64 self.basepath = self.config['base_path']
64 self.basepath = self.config['base_path']
65 #authenticate this mercurial request using authfunc
65 #authenticate this mercurial request using authfunc
66 self.authenticate = AuthBasicAuthenticator('', authfunc)
66 self.authenticate = AuthBasicAuthenticator('', authfunc)
67 self.ipaddr = '0.0.0.0'
67 self.ipaddr = '0.0.0.0'
68
68
69 def __call__(self, environ, start_response):
69 def __call__(self, environ, start_response):
70 if not is_mercurial(environ):
70 if not is_mercurial(environ):
71 return self.application(environ, start_response)
71 return self.application(environ, start_response)
72
72
73 proxy_key = 'HTTP_X_REAL_IP'
73 proxy_key = 'HTTP_X_REAL_IP'
74 def_key = 'REMOTE_ADDR'
74 def_key = 'REMOTE_ADDR'
75 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
75 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
76
76
77 # skip passing error to error controller
77 # skip passing error to error controller
78 environ['pylons.status_code_redirect'] = True
78 environ['pylons.status_code_redirect'] = True
79
79
80 #======================================================================
80 #======================================================================
81 # EXTRACT REPOSITORY NAME FROM ENV
81 # EXTRACT REPOSITORY NAME FROM ENV
82 #======================================================================
82 #======================================================================
83 try:
83 try:
84 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
84 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
85 log.debug('Extracted repo name is %s' % repo_name)
85 log.debug('Extracted repo name is %s' % repo_name)
86 except:
86 except:
87 return HTTPInternalServerError()(environ, start_response)
87 return HTTPInternalServerError()(environ, start_response)
88
88
89 #======================================================================
89 #======================================================================
90 # GET ACTION PULL or PUSH
90 # GET ACTION PULL or PUSH
91 #======================================================================
91 #======================================================================
92 action = self.__get_action(environ)
92 action = self.__get_action(environ)
93
93
94 #======================================================================
94 #======================================================================
95 # CHECK ANONYMOUS PERMISSION
95 # CHECK ANONYMOUS PERMISSION
96 #======================================================================
96 #======================================================================
97 if action in ['pull', 'push']:
97 if action in ['pull', 'push']:
98 anonymous_user = self.__get_user('default')
98 anonymous_user = self.__get_user('default')
99
99
100 username = anonymous_user.username
100 username = anonymous_user.username
101 anonymous_perm = self.__check_permission(action,
101 anonymous_perm = self.__check_permission(action,
102 anonymous_user,
102 anonymous_user,
103 repo_name)
103 repo_name)
104
104
105 if anonymous_perm is not True or anonymous_user.active is False:
105 if anonymous_perm is not True or anonymous_user.active is False:
106 if anonymous_perm is not True:
106 if anonymous_perm is not True:
107 log.debug('Not enough credentials to access this '
107 log.debug('Not enough credentials to access this '
108 'repository as anonymous user')
108 'repository as anonymous user')
109 if anonymous_user.active is False:
109 if anonymous_user.active is False:
110 log.debug('Anonymous access is disabled, running '
110 log.debug('Anonymous access is disabled, running '
111 'authentication')
111 'authentication')
112 #==============================================================
112 #==============================================================
113 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
113 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
114 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
114 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
115 #==============================================================
115 #==============================================================
116
116
117 if not REMOTE_USER(environ):
117 if not REMOTE_USER(environ):
118 self.authenticate.realm = \
118 self.authenticate.realm = \
119 safe_str(self.config['rhodecode_realm'])
119 safe_str(self.config['rhodecode_realm'])
120 result = self.authenticate(environ)
120 result = self.authenticate(environ)
121 if isinstance(result, str):
121 if isinstance(result, str):
122 AUTH_TYPE.update(environ, 'basic')
122 AUTH_TYPE.update(environ, 'basic')
123 REMOTE_USER.update(environ, result)
123 REMOTE_USER.update(environ, result)
124 else:
124 else:
125 return result.wsgi_application(environ, start_response)
125 return result.wsgi_application(environ, start_response)
126
126
127 #==============================================================
127 #==============================================================
128 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
128 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
129 # BASIC AUTH
129 # BASIC AUTH
130 #==============================================================
130 #==============================================================
131
131
132 if action in ['pull', 'push']:
132 if action in ['pull', 'push']:
133 username = REMOTE_USER(environ)
133 username = REMOTE_USER(environ)
134 try:
134 try:
135 user = self.__get_user(username)
135 user = self.__get_user(username)
136 if user is None:
137 return HTTPForbidden()(environ, start_response)
136 username = user.username
138 username = user.username
137 except:
139 except:
138 log.error(traceback.format_exc())
140 log.error(traceback.format_exc())
139 return HTTPInternalServerError()(environ,
141 return HTTPInternalServerError()(environ,
140 start_response)
142 start_response)
141
143
142 #check permissions for this repository
144 #check permissions for this repository
143 perm = self.__check_permission(action, user,
145 perm = self.__check_permission(action, user,
144 repo_name)
146 repo_name)
145 if perm is not True:
147 if perm is not True:
146 return HTTPForbidden()(environ, start_response)
148 return HTTPForbidden()(environ, start_response)
147
149
148 extras = {'ip': ipaddr,
150 extras = {'ip': ipaddr,
149 'username': username,
151 'username': username,
150 'action': action,
152 'action': action,
151 'repository': repo_name}
153 'repository': repo_name}
152
154
153 #======================================================================
155 #======================================================================
154 # MERCURIAL REQUEST HANDLING
156 # MERCURIAL REQUEST HANDLING
155 #======================================================================
157 #======================================================================
156
158
157 repo_path = safe_str(os.path.join(self.basepath, repo_name))
159 repo_path = safe_str(os.path.join(self.basepath, repo_name))
158 log.debug('Repository path is %s' % repo_path)
160 log.debug('Repository path is %s' % repo_path)
159
161
160 baseui = make_ui('db')
162 baseui = make_ui('db')
161 self.__inject_extras(repo_path, baseui, extras)
163 self.__inject_extras(repo_path, baseui, extras)
162
164
163
165
164 # quick check if that dir exists...
166 # quick check if that dir exists...
165 if is_valid_repo(repo_name, self.basepath) is False:
167 if is_valid_repo(repo_name, self.basepath) is False:
166 return HTTPNotFound()(environ, start_response)
168 return HTTPNotFound()(environ, start_response)
167
169
168 try:
170 try:
169 #invalidate cache on push
171 #invalidate cache on push
170 if action == 'push':
172 if action == 'push':
171 self.__invalidate_cache(repo_name)
173 self.__invalidate_cache(repo_name)
172
174
173 app = self.__make_app(repo_path, baseui, extras)
175 app = self.__make_app(repo_path, baseui, extras)
174 return app(environ, start_response)
176 return app(environ, start_response)
175 except RepoError, e:
177 except RepoError, e:
176 if str(e).find('not found') != -1:
178 if str(e).find('not found') != -1:
177 return HTTPNotFound()(environ, start_response)
179 return HTTPNotFound()(environ, start_response)
178 except Exception:
180 except Exception:
179 log.error(traceback.format_exc())
181 log.error(traceback.format_exc())
180 return HTTPInternalServerError()(environ, start_response)
182 return HTTPInternalServerError()(environ, start_response)
181
183
182 def __make_app(self, repo_name, baseui, extras):
184 def __make_app(self, repo_name, baseui, extras):
183 """
185 """
184 Make an wsgi application using hgweb, and inject generated baseui
186 Make an wsgi application using hgweb, and inject generated baseui
185 instance, additionally inject some extras into ui object
187 instance, additionally inject some extras into ui object
186 """
188 """
187 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
189 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
188
190
189
191
190 def __check_permission(self, action, user, repo_name):
192 def __check_permission(self, action, user, repo_name):
191 """
193 """
192 Checks permissions using action (push/pull) user and repository
194 Checks permissions using action (push/pull) user and repository
193 name
195 name
194
196
195 :param action: push or pull action
197 :param action: push or pull action
196 :param user: user instance
198 :param user: user instance
197 :param repo_name: repository name
199 :param repo_name: repository name
198 """
200 """
199 if action == 'push':
201 if action == 'push':
200 if not HasPermissionAnyMiddleware('repository.write',
202 if not HasPermissionAnyMiddleware('repository.write',
201 'repository.admin')(user,
203 'repository.admin')(user,
202 repo_name):
204 repo_name):
203 return False
205 return False
204
206
205 else:
207 else:
206 #any other action need at least read permission
208 #any other action need at least read permission
207 if not HasPermissionAnyMiddleware('repository.read',
209 if not HasPermissionAnyMiddleware('repository.read',
208 'repository.write',
210 'repository.write',
209 'repository.admin')(user,
211 'repository.admin')(user,
210 repo_name):
212 repo_name):
211 return False
213 return False
212
214
213 return True
215 return True
214
216
215 def __get_repository(self, environ):
217 def __get_repository(self, environ):
216 """
218 """
217 Get's repository name out of PATH_INFO header
219 Get's repository name out of PATH_INFO header
218
220
219 :param environ: environ where PATH_INFO is stored
221 :param environ: environ where PATH_INFO is stored
220 """
222 """
221 try:
223 try:
222 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
224 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
223 if repo_name.endswith('/'):
225 if repo_name.endswith('/'):
224 repo_name = repo_name.rstrip('/')
226 repo_name = repo_name.rstrip('/')
225 except:
227 except:
226 log.error(traceback.format_exc())
228 log.error(traceback.format_exc())
227 raise
229 raise
228
230
229 return repo_name
231 return repo_name
230
232
231 def __get_user(self, username):
233 def __get_user(self, username):
232 return User.get_by_username(username)
234 return User.get_by_username(username)
233
235
234 def __get_action(self, environ):
236 def __get_action(self, environ):
235 """
237 """
236 Maps mercurial request commands into a clone,pull or push command.
238 Maps mercurial request commands into a clone,pull or push command.
237 This should always return a valid command string
239 This should always return a valid command string
238
240
239 :param environ:
241 :param environ:
240 """
242 """
241 mapping = {'changegroup': 'pull',
243 mapping = {'changegroup': 'pull',
242 'changegroupsubset': 'pull',
244 'changegroupsubset': 'pull',
243 'stream_out': 'pull',
245 'stream_out': 'pull',
244 'listkeys': 'pull',
246 'listkeys': 'pull',
245 'unbundle': 'push',
247 'unbundle': 'push',
246 'pushkey': 'push', }
248 'pushkey': 'push', }
247 for qry in environ['QUERY_STRING'].split('&'):
249 for qry in environ['QUERY_STRING'].split('&'):
248 if qry.startswith('cmd'):
250 if qry.startswith('cmd'):
249 cmd = qry.split('=')[-1]
251 cmd = qry.split('=')[-1]
250 if cmd in mapping:
252 if cmd in mapping:
251 return mapping[cmd]
253 return mapping[cmd]
252 else:
254 else:
253 return 'pull'
255 return 'pull'
254
256
255 def __invalidate_cache(self, repo_name):
257 def __invalidate_cache(self, repo_name):
256 """we know that some change was made to repositories and we should
258 """we know that some change was made to repositories and we should
257 invalidate the cache to see the changes right away but only for
259 invalidate the cache to see the changes right away but only for
258 push requests"""
260 push requests"""
259 invalidate_cache('get_repo_cached_%s' % repo_name)
261 invalidate_cache('get_repo_cached_%s' % repo_name)
260
262
261 def __inject_extras(self, repo_path, baseui, extras={}):
263 def __inject_extras(self, repo_path, baseui, extras={}):
262 """
264 """
263 Injects some extra params into baseui instance
265 Injects some extra params into baseui instance
264
266
265 also overwrites global settings with those takes from local hgrc file
267 also overwrites global settings with those takes from local hgrc file
266
268
267 :param baseui: baseui instance
269 :param baseui: baseui instance
268 :param extras: dict with extra params to put into baseui
270 :param extras: dict with extra params to put into baseui
269 """
271 """
270
272
271 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
273 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
272
274
273 # make our hgweb quiet so it doesn't print output
275 # make our hgweb quiet so it doesn't print output
274 baseui.setconfig('ui', 'quiet', 'true')
276 baseui.setconfig('ui', 'quiet', 'true')
275
277
276 #inject some additional parameters that will be available in ui
278 #inject some additional parameters that will be available in ui
277 #for hooks
279 #for hooks
278 for k, v in extras.items():
280 for k, v in extras.items():
279 baseui.setconfig('rhodecode_extras', k, v)
281 baseui.setconfig('rhodecode_extras', k, v)
280
282
281 repoui = make_ui('file', hgrc, False)
283 repoui = make_ui('file', hgrc, False)
282
284
283 if repoui:
285 if repoui:
284 #overwrite our ui instance with the section from hgrc file
286 #overwrite our ui instance with the section from hgrc file
285 for section in ui_sections:
287 for section in ui_sections:
286 for k, v in repoui.configitems(section):
288 for k, v in repoui.configitems(section):
287 baseui.setconfig(section, k, v)
289 baseui.setconfig(section, k, v)
288
290
@@ -1,161 +1,165 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.smtp_mailer
3 rhodecode.lib.smtp_mailer
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Simple smtp mailer used in RhodeCode
6 Simple smtp mailer used in RhodeCode
7
7
8 :created_on: Sep 13, 2010
8 :created_on: Sep 13, 2010
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :license: GPLv3, see COPYING for more details.
10 :license: GPLv3, see COPYING for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
24
25 import logging
25 import logging
26 import smtplib
26 import smtplib
27 import mimetypes
27 import mimetypes
28 from socket import sslerror
28 from socket import sslerror
29
29
30 from email.mime.multipart import MIMEMultipart
30 from email.mime.multipart import MIMEMultipart
31 from email.mime.image import MIMEImage
31 from email.mime.image import MIMEImage
32 from email.mime.audio import MIMEAudio
32 from email.mime.audio import MIMEAudio
33 from email.mime.base import MIMEBase
33 from email.mime.base import MIMEBase
34 from email.mime.text import MIMEText
34 from email.mime.text import MIMEText
35 from email.utils import formatdate
35 from email.utils import formatdate
36 from email import encoders
36 from email import encoders
37
37
38
38
39 class SmtpMailer(object):
39 class SmtpMailer(object):
40 """SMTP mailer class
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 mail_port, ssl, tls)
43 mail_port, ssl, tls)
44 mailer.send(recipients, subject, body, attachment_files)
44 mailer.send(recipients, subject, body, attachment_files)
45
45
46 :param recipients might be a list of string or single string
46 :param recipients might be a list of string or single string
47 :param attachment_files is a dict of {filename:location}
47 :param attachment_files is a dict of {filename:location}
48 it tries to guess the mimetype and attach the file
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,
52 def __init__(self, mail_from, user, passwd, mail_server, smtp_auth=None,
53 mail_port=None, ssl=False, tls=False, debug=False):
53 mail_port=None, ssl=False, tls=False, debug=False):
54
54
55 self.mail_from = mail_from
55 self.mail_from = mail_from
56 self.mail_server = mail_server
56 self.mail_server = mail_server
57 self.mail_port = mail_port
57 self.mail_port = mail_port
58 self.user = user
58 self.user = user
59 self.passwd = passwd
59 self.passwd = passwd
60 self.ssl = ssl
60 self.ssl = ssl
61 self.tls = tls
61 self.tls = tls
62 self.debug = debug
62 self.debug = debug
63 self.auth = smtp_auth
63
64
64 def send(self, recipients=[], subject='', body='', attachment_files=None):
65 def send(self, recipients=[], subject='', body='', attachment_files=None):
65
66
66 if isinstance(recipients, basestring):
67 if isinstance(recipients, basestring):
67 recipients = [recipients]
68 recipients = [recipients]
68 if self.ssl:
69 if self.ssl:
69 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
70 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
70 else:
71 else:
71 smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
72 smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
72
73
73 if self.tls:
74 if self.tls:
74 smtp_serv.ehlo()
75 smtp_serv.ehlo()
75 smtp_serv.starttls()
76 smtp_serv.starttls()
76
77
77 if self.debug:
78 if self.debug:
78 smtp_serv.set_debuglevel(1)
79 smtp_serv.set_debuglevel(1)
79
80
80 smtp_serv.ehlo()
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
85 # if server requires authorization you must provide login and password
83 #but only if we have them
86 # but only if we have them
84 if self.user and self.passwd:
87 if self.user and self.passwd:
85 smtp_serv.login(self.user, self.passwd)
88 smtp_serv.login(self.user, self.passwd)
86
89
87 date_ = formatdate(localtime=True)
90 date_ = formatdate(localtime=True)
88 msg = MIMEMultipart()
91 msg = MIMEMultipart()
89 msg.set_type('multipart/alternative')
92 msg.set_type('multipart/alternative')
90 msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
93 msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
91
94
92 text_msg = MIMEText(body)
95 text_msg = MIMEText(body)
93 text_msg.set_type('text/plain')
96 text_msg.set_type('text/plain')
94 text_msg.set_param('charset', 'UTF-8')
97 text_msg.set_param('charset', 'UTF-8')
95
98
96 msg['From'] = self.mail_from
99 msg['From'] = self.mail_from
97 msg['To'] = ','.join(recipients)
100 msg['To'] = ','.join(recipients)
98 msg['Date'] = date_
101 msg['Date'] = date_
99 msg['Subject'] = subject
102 msg['Subject'] = subject
100
103
101 msg.attach(text_msg)
104 msg.attach(text_msg)
102
105
103 if attachment_files:
106 if attachment_files:
104 self.__atach_files(msg, attachment_files)
107 self.__atach_files(msg, attachment_files)
105
108
106 smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
109 smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
107 logging.info('MAIL SEND TO: %s' % recipients)
110 logging.info('MAIL SEND TO: %s' % recipients)
108
111
109 try:
112 try:
110 smtp_serv.quit()
113 smtp_serv.quit()
111 except sslerror:
114 except sslerror:
112 # sslerror is raised in tls connections on closing sometimes
115 # sslerror is raised in tls connections on closing sometimes
113 pass
116 pass
114
117
115 def __atach_files(self, msg, attachment_files):
118 def __atach_files(self, msg, attachment_files):
116 if isinstance(attachment_files, dict):
119 if isinstance(attachment_files, dict):
117 for f_name, msg_file in attachment_files.items():
120 for f_name, msg_file in attachment_files.items():
118 ctype, encoding = mimetypes.guess_type(f_name)
121 ctype, encoding = mimetypes.guess_type(f_name)
119 logging.info("guessing file %s type based on %s", ctype,
122 logging.info("guessing file %s type based on %s", ctype,
120 f_name)
123 f_name)
121 if ctype is None or encoding is not None:
124 if ctype is None or encoding is not None:
122 # No guess could be made, or the file is encoded
125 # No guess could be made, or the file is encoded
123 # (compressed), so use a generic bag-of-bits type.
126 # (compressed), so use a generic bag-of-bits type.
124 ctype = 'application/octet-stream'
127 ctype = 'application/octet-stream'
125 maintype, subtype = ctype.split('/', 1)
128 maintype, subtype = ctype.split('/', 1)
126 if maintype == 'text':
129 if maintype == 'text':
127 # Note: we should handle calculating the charset
130 # Note: we should handle calculating the charset
128 file_part = MIMEText(self.get_content(msg_file),
131 file_part = MIMEText(self.get_content(msg_file),
129 _subtype=subtype)
132 _subtype=subtype)
130 elif maintype == 'image':
133 elif maintype == 'image':
131 file_part = MIMEImage(self.get_content(msg_file),
134 file_part = MIMEImage(self.get_content(msg_file),
132 _subtype=subtype)
135 _subtype=subtype)
133 elif maintype == 'audio':
136 elif maintype == 'audio':
134 file_part = MIMEAudio(self.get_content(msg_file),
137 file_part = MIMEAudio(self.get_content(msg_file),
135 _subtype=subtype)
138 _subtype=subtype)
136 else:
139 else:
137 file_part = MIMEBase(maintype, subtype)
140 file_part = MIMEBase(maintype, subtype)
138 file_part.set_payload(self.get_content(msg_file))
141 file_part.set_payload(self.get_content(msg_file))
139 # Encode the payload using Base64
142 # Encode the payload using Base64
140 encoders.encode_base64(msg)
143 encoders.encode_base64(msg)
141 # Set the filename parameter
144 # Set the filename parameter
142 file_part.add_header('Content-Disposition', 'attachment',
145 file_part.add_header('Content-Disposition', 'attachment',
143 filename=f_name)
146 filename=f_name)
144 file_part.add_header('Content-Type', ctype, name=f_name)
147 file_part.add_header('Content-Type', ctype, name=f_name)
145 msg.attach(file_part)
148 msg.attach(file_part)
146 else:
149 else:
147 raise Exception('Attachment files should be'
150 raise Exception('Attachment files should be'
148 'a dict in format {"filename":"filepath"}')
151 'a dict in format {"filename":"filepath"}')
149
152
150 def get_content(self, msg_file):
153 def get_content(self, msg_file):
151 """Get content based on type, if content is a string do open first
154 """Get content based on type, if content is a string do open first
152 else just read because it's a probably open file object
155 else just read because it's a probably open file object
153
156
154 :param msg_file:
157 :param msg_file:
155 """
158 """
156 if isinstance(msg_file, str):
159 if isinstance(msg_file, str):
157 return open(msg_file, "rb").read()
160 return open(msg_file, "rb").read()
158 else:
161 else:
159 #just for safe seek to 0
162 # just for safe seek to 0
160 msg_file.seek(0)
163 msg_file.seek(0)
161 return msg_file.read()
164 return msg_file.read()
165
@@ -1,1051 +1,1069 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 from datetime import date
30 from datetime import date
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.exc import DatabaseError
34 from sqlalchemy.ext.hybrid import hybrid_property
33 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper, \
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 validates
37 from sqlalchemy.orm.interfaces import MapperExtension
38 from beaker.cache import cache_region, region_invalidate
35 from beaker.cache import cache_region, region_invalidate
39
36
40 from vcs import get_backend
37 from vcs import get_backend
41 from vcs.utils.helpers import get_scm
38 from vcs.utils.helpers import get_scm
42 from vcs.exceptions import VCSError
39 from vcs.exceptions import VCSError
43 from vcs.utils.lazy import LazyProperty
40 from vcs.utils.lazy import LazyProperty
44
41
45 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
46 generate_api_key, safe_unicode
43 generate_api_key, safe_unicode
47 from rhodecode.lib.exceptions import UsersGroupsAssignedException
44 from rhodecode.lib.exceptions import UsersGroupsAssignedException
48 from rhodecode.lib.compat import json
45 from rhodecode.lib.compat import json
49
46
50 from rhodecode.model.meta import Base, Session
47 from rhodecode.model.meta import Base, Session
51 from rhodecode.model.caching_query import FromCache
48 from rhodecode.model.caching_query import FromCache
52
49
53
50
54 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
55
52
56 #==============================================================================
53 #==============================================================================
57 # BASE CLASSES
54 # BASE CLASSES
58 #==============================================================================
55 #==============================================================================
59
56
60 class ModelSerializer(json.JSONEncoder):
57 class ModelSerializer(json.JSONEncoder):
61 """
58 """
62 Simple Serializer for JSON,
59 Simple Serializer for JSON,
63
60
64 usage::
61 usage::
65
62
66 to make object customized for serialization implement a __json__
63 to make object customized for serialization implement a __json__
67 method that will return a dict for serialization into json
64 method that will return a dict for serialization into json
68
65
69 example::
66 example::
70
67
71 class Task(object):
68 class Task(object):
72
69
73 def __init__(self, name, value):
70 def __init__(self, name, value):
74 self.name = name
71 self.name = name
75 self.value = value
72 self.value = value
76
73
77 def __json__(self):
74 def __json__(self):
78 return dict(name=self.name,
75 return dict(name=self.name,
79 value=self.value)
76 value=self.value)
80
77
81 """
78 """
82
79
83 def default(self, obj):
80 def default(self, obj):
84
81
85 if hasattr(obj, '__json__'):
82 if hasattr(obj, '__json__'):
86 return obj.__json__()
83 return obj.__json__()
87 else:
84 else:
88 return json.JSONEncoder.default(self, obj)
85 return json.JSONEncoder.default(self, obj)
89
86
90 class BaseModel(object):
87 class BaseModel(object):
91 """Base Model for all classess
88 """Base Model for all classess
92
89
93 """
90 """
94
91
95 @classmethod
92 @classmethod
96 def _get_keys(cls):
93 def _get_keys(cls):
97 """return column names for this model """
94 """return column names for this model """
98 return class_mapper(cls).c.keys()
95 return class_mapper(cls).c.keys()
99
96
100 def get_dict(self):
97 def get_dict(self):
101 """return dict with keys and values corresponding
98 """return dict with keys and values corresponding
102 to this model data """
99 to this model data """
103
100
104 d = {}
101 d = {}
105 for k in self._get_keys():
102 for k in self._get_keys():
106 d[k] = getattr(self, k)
103 d[k] = getattr(self, k)
107 return d
104 return d
108
105
109 def get_appstruct(self):
106 def get_appstruct(self):
110 """return list with keys and values tupples corresponding
107 """return list with keys and values tupples corresponding
111 to this model data """
108 to this model data """
112
109
113 l = []
110 l = []
114 for k in self._get_keys():
111 for k in self._get_keys():
115 l.append((k, getattr(self, k),))
112 l.append((k, getattr(self, k),))
116 return l
113 return l
117
114
118 def populate_obj(self, populate_dict):
115 def populate_obj(self, populate_dict):
119 """populate model with data from given populate_dict"""
116 """populate model with data from given populate_dict"""
120
117
121 for k in self._get_keys():
118 for k in self._get_keys():
122 if k in populate_dict:
119 if k in populate_dict:
123 setattr(self, k, populate_dict[k])
120 setattr(self, k, populate_dict[k])
124
121
125 @classmethod
122 @classmethod
126 def query(cls):
123 def query(cls):
127 return Session.query(cls)
124 return Session.query(cls)
128
125
129 @classmethod
126 @classmethod
130 def get(cls, id_):
127 def get(cls, id_):
131 if id_:
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 @classmethod
135 @classmethod
135 def delete(cls, id_):
136 def delete(cls, id_):
136 obj = Session.query(cls).get(id_)
137 obj = cls.query().get(id_)
137 Session.delete(obj)
138 Session.delete(obj)
138 Session.commit()
139 Session.commit()
139
140
140
141
141 class RhodeCodeSettings(Base, BaseModel):
142 class RhodeCodeSettings(Base, BaseModel):
142 __tablename__ = 'rhodecode_settings'
143 __tablename__ = 'rhodecode_settings'
143 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
144 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
144 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
145 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
145 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
146 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
146 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
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 def __init__(self, k='', v=''):
149 def __init__(self, k='', v=''):
149 self.app_settings_name = k
150 self.app_settings_name = k
150 self.app_settings_value = v
151 self.app_settings_value = v
151
152
152
153
153 @validates('_app_settings_value')
154 @validates('_app_settings_value')
154 def validate_settings_value(self, key, val):
155 def validate_settings_value(self, key, val):
155 assert type(val) == unicode
156 assert type(val) == unicode
156 return val
157 return val
157
158
158 @hybrid_property
159 @hybrid_property
159 def app_settings_value(self):
160 def app_settings_value(self):
160 v = self._app_settings_value
161 v = self._app_settings_value
161 if v == 'ldap_active':
162 if v == 'ldap_active':
162 v = str2bool(v)
163 v = str2bool(v)
163 return v
164 return v
164
165
165 @app_settings_value.setter
166 @app_settings_value.setter
166 def app_settings_value(self,val):
167 def app_settings_value(self, val):
167 """
168 """
168 Setter that will always make sure we use unicode in app_settings_value
169 Setter that will always make sure we use unicode in app_settings_value
169
170
170 :param val:
171 :param val:
171 """
172 """
172 self._app_settings_value = safe_unicode(val)
173 self._app_settings_value = safe_unicode(val)
173
174
174 def __repr__(self):
175 def __repr__(self):
175 return "<%s('%s:%s')>" % (self.__class__.__name__,
176 return "<%s('%s:%s')>" % (self.__class__.__name__,
176 self.app_settings_name, self.app_settings_value)
177 self.app_settings_name, self.app_settings_value)
177
178
178
179
179 @classmethod
180 @classmethod
180 def get_by_name(cls, ldap_key):
181 def get_by_name(cls, ldap_key):
181 return Session.query(cls)\
182 return cls.query()\
182 .filter(cls.app_settings_name == ldap_key).scalar()
183 .filter(cls.app_settings_name == ldap_key).scalar()
183
184
184 @classmethod
185 @classmethod
185 def get_app_settings(cls, cache=False):
186 def get_app_settings(cls, cache=False):
186
187
187 ret = Session.query(cls)
188 ret = cls.query()
188
189
189 if cache:
190 if cache:
190 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
191 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
191
192
192 if not ret:
193 if not ret:
193 raise Exception('Could not get application settings !')
194 raise Exception('Could not get application settings !')
194 settings = {}
195 settings = {}
195 for each in ret:
196 for each in ret:
196 settings['rhodecode_' + each.app_settings_name] = \
197 settings['rhodecode_' + each.app_settings_name] = \
197 each.app_settings_value
198 each.app_settings_value
198
199
199 return settings
200 return settings
200
201
201 @classmethod
202 @classmethod
202 def get_ldap_settings(cls, cache=False):
203 def get_ldap_settings(cls, cache=False):
203 ret = Session.query(cls)\
204 ret = cls.query()\
204 .filter(cls.app_settings_name.startswith('ldap_')).all()
205 .filter(cls.app_settings_name.startswith('ldap_')).all()
205 fd = {}
206 fd = {}
206 for row in ret:
207 for row in ret:
207 fd.update({row.app_settings_name:row.app_settings_value})
208 fd.update({row.app_settings_name:row.app_settings_value})
208
209
209 return fd
210 return fd
210
211
211
212
212 class RhodeCodeUi(Base, BaseModel):
213 class RhodeCodeUi(Base, BaseModel):
213 __tablename__ = 'rhodecode_ui'
214 __tablename__ = 'rhodecode_ui'
214 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
215 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
215
216
216 HOOK_UPDATE = 'changegroup.update'
217 HOOK_UPDATE = 'changegroup.update'
217 HOOK_REPO_SIZE = 'changegroup.repo_size'
218 HOOK_REPO_SIZE = 'changegroup.repo_size'
218 HOOK_PUSH = 'pretxnchangegroup.push_logger'
219 HOOK_PUSH = 'pretxnchangegroup.push_logger'
219 HOOK_PULL = 'preoutgoing.pull_logger'
220 HOOK_PULL = 'preoutgoing.pull_logger'
220
221
221 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
222 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
222 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
223 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
223 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
224 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
224 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
225 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
225 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
226 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
226
227
227
228
228 @classmethod
229 @classmethod
229 def get_by_key(cls, key):
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 @classmethod
234 @classmethod
234 def get_builtin_hooks(cls):
235 def get_builtin_hooks(cls):
235 q = cls.query()
236 q = cls.query()
236 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
237 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
237 cls.HOOK_REPO_SIZE,
238 cls.HOOK_REPO_SIZE,
238 cls.HOOK_PUSH, cls.HOOK_PULL]))
239 cls.HOOK_PUSH, cls.HOOK_PULL]))
239 return q.all()
240 return q.all()
240
241
241 @classmethod
242 @classmethod
242 def get_custom_hooks(cls):
243 def get_custom_hooks(cls):
243 q = cls.query()
244 q = cls.query()
244 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
245 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
245 cls.HOOK_REPO_SIZE,
246 cls.HOOK_REPO_SIZE,
246 cls.HOOK_PUSH, cls.HOOK_PULL]))
247 cls.HOOK_PUSH, cls.HOOK_PULL]))
247 q = q.filter(cls.ui_section == 'hooks')
248 q = q.filter(cls.ui_section == 'hooks')
248 return q.all()
249 return q.all()
249
250
250 @classmethod
251 @classmethod
251 def create_or_update_hook(cls, key, val):
252 def create_or_update_hook(cls, key, val):
252 new_ui = cls.get_by_key(key).scalar() or cls()
253 new_ui = cls.get_by_key(key).scalar() or cls()
253 new_ui.ui_section = 'hooks'
254 new_ui.ui_section = 'hooks'
254 new_ui.ui_active = True
255 new_ui.ui_active = True
255 new_ui.ui_key = key
256 new_ui.ui_key = key
256 new_ui.ui_value = val
257 new_ui.ui_value = val
257
258
258 Session.add(new_ui)
259 Session.add(new_ui)
259 Session.commit()
260 Session.commit()
260
261
261
262
262 class User(Base, BaseModel):
263 class User(Base, BaseModel):
263 __tablename__ = 'users'
264 __tablename__ = 'users'
264 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
265 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
265 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
266 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
266 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
267 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
267 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
269 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
269 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
270 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
270 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
274 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
274 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
275 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
275 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
276 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
276
277
277 user_log = relationship('UserLog', cascade='all')
278 user_log = relationship('UserLog', cascade='all')
278 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
279 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
279
280
280 repositories = relationship('Repository')
281 repositories = relationship('Repository')
281 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
282 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
282 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
283 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
283
284
284 group_member = relationship('UsersGroupMember', cascade='all')
285 group_member = relationship('UsersGroupMember', cascade='all')
285
286
286 @property
287 @property
287 def full_contact(self):
288 def full_contact(self):
288 return '%s %s <%s>' % (self.name, self.lastname, self.email)
289 return '%s %s <%s>' % (self.name, self.lastname, self.email)
289
290
290 @property
291 @property
291 def short_contact(self):
292 def short_contact(self):
292 return '%s %s' % (self.name, self.lastname)
293 return '%s %s' % (self.name, self.lastname)
293
294
294 @property
295 @property
295 def is_admin(self):
296 def is_admin(self):
296 return self.admin
297 return self.admin
297
298
298 def __repr__(self):
299 def __repr__(self):
299 try:
300 try:
300 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
301 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
301 self.user_id, self.username)
302 self.user_id, self.username)
302 except:
303 except:
303 return self.__class__.__name__
304 return self.__class__.__name__
304
305
305 @classmethod
306 @classmethod
306 def get_by_username(cls, username, case_insensitive=False):
307 def get_by_username(cls, username, case_insensitive=False):
307 if case_insensitive:
308 if case_insensitive:
308 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
309 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
309 else:
310 else:
310 return Session.query(cls).filter(cls.username == username).scalar()
311 return Session.query(cls).filter(cls.username == username).scalar()
311
312
312 @classmethod
313 @classmethod
313 def get_by_api_key(cls, api_key):
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 def update_lastlogin(self):
317 def update_lastlogin(self):
317 """Update user lastlogin"""
318 """Update user lastlogin"""
318
319
319 self.last_login = datetime.datetime.now()
320 self.last_login = datetime.datetime.now()
320 Session.add(self)
321 Session.add(self)
321 Session.commit()
322 Session.commit()
322 log.debug('updated user %s lastlogin', self.username)
323 log.debug('updated user %s lastlogin', self.username)
323
324
324 @classmethod
325 @classmethod
325 def create(cls, form_data):
326 def create(cls, form_data):
326 from rhodecode.lib.auth import get_crypt_password
327 from rhodecode.lib.auth import get_crypt_password
327
328
328 try:
329 try:
329 new_user = cls()
330 new_user = cls()
330 for k, v in form_data.items():
331 for k, v in form_data.items():
331 if k == 'password':
332 if k == 'password':
332 v = get_crypt_password(v)
333 v = get_crypt_password(v)
333 setattr(new_user, k, v)
334 setattr(new_user, k, v)
334
335
335 new_user.api_key = generate_api_key(form_data['username'])
336 new_user.api_key = generate_api_key(form_data['username'])
336 Session.add(new_user)
337 Session.add(new_user)
337 Session.commit()
338 Session.commit()
338 return new_user
339 return new_user
339 except:
340 except:
340 log.error(traceback.format_exc())
341 log.error(traceback.format_exc())
341 Session.rollback()
342 Session.rollback()
342 raise
343 raise
343
344
344 class UserLog(Base, BaseModel):
345 class UserLog(Base, BaseModel):
345 __tablename__ = 'user_logs'
346 __tablename__ = 'user_logs'
346 __table_args__ = {'extend_existing':True}
347 __table_args__ = {'extend_existing':True}
347 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
348 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
348 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
349 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
349 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
350 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
350 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
351 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
351 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
353 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
353 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
354 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
354
355
355 @property
356 @property
356 def action_as_day(self):
357 def action_as_day(self):
357 return date(*self.action_date.timetuple()[:3])
358 return date(*self.action_date.timetuple()[:3])
358
359
359 user = relationship('User')
360 user = relationship('User')
360 repository = relationship('Repository')
361 repository = relationship('Repository')
361
362
362
363
363 class UsersGroup(Base, BaseModel):
364 class UsersGroup(Base, BaseModel):
364 __tablename__ = 'users_groups'
365 __tablename__ = 'users_groups'
365 __table_args__ = {'extend_existing':True}
366 __table_args__ = {'extend_existing':True}
366
367
367 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
368 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
368 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
369 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
369 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
370 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
370
371
371 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
372 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
372
373
373 def __repr__(self):
374 def __repr__(self):
374 return '<userGroup(%s)>' % (self.users_group_name)
375 return '<userGroup(%s)>' % (self.users_group_name)
375
376
376 @classmethod
377 @classmethod
377 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
378 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
378 if case_insensitive:
379 if case_insensitive:
379 gr = Session.query(cls)\
380 gr = cls.query()\
380 .filter(cls.users_group_name.ilike(group_name))
381 .filter(cls.users_group_name.ilike(group_name))
381 else:
382 else:
382 gr = Session.query(UsersGroup)\
383 gr = cls.query()\
383 .filter(UsersGroup.users_group_name == group_name)
384 .filter(cls.users_group_name == group_name)
384 if cache:
385 if cache:
385 gr = gr.options(FromCache("sql_cache_short",
386 gr = gr.options(FromCache("sql_cache_short",
386 "get_user_%s" % group_name))
387 "get_user_%s" % group_name))
387 return gr.scalar()
388 return gr.scalar()
388
389
389
390
390 @classmethod
391 @classmethod
391 def get(cls, users_group_id, cache=False):
392 def get(cls, users_group_id, cache=False):
392 users_group = Session.query(cls)
393 users_group = cls.query()
393 if cache:
394 if cache:
394 users_group = users_group.options(FromCache("sql_cache_short",
395 users_group = users_group.options(FromCache("sql_cache_short",
395 "get_users_group_%s" % users_group_id))
396 "get_users_group_%s" % users_group_id))
396 return users_group.get(users_group_id)
397 return users_group.get(users_group_id)
397
398
398 @classmethod
399 @classmethod
399 def create(cls, form_data):
400 def create(cls, form_data):
400 try:
401 try:
401 new_users_group = cls()
402 new_users_group = cls()
402 for k, v in form_data.items():
403 for k, v in form_data.items():
403 setattr(new_users_group, k, v)
404 setattr(new_users_group, k, v)
404
405
405 Session.add(new_users_group)
406 Session.add(new_users_group)
406 Session.commit()
407 Session.commit()
407 return new_users_group
408 return new_users_group
408 except:
409 except:
409 log.error(traceback.format_exc())
410 log.error(traceback.format_exc())
410 Session.rollback()
411 Session.rollback()
411 raise
412 raise
412
413
413 @classmethod
414 @classmethod
414 def update(cls, users_group_id, form_data):
415 def update(cls, users_group_id, form_data):
415
416
416 try:
417 try:
417 users_group = cls.get(users_group_id, cache=False)
418 users_group = cls.get(users_group_id, cache=False)
418
419
419 for k, v in form_data.items():
420 for k, v in form_data.items():
420 if k == 'users_group_members':
421 if k == 'users_group_members':
421 users_group.members = []
422 users_group.members = []
422 Session.flush()
423 Session.flush()
423 members_list = []
424 members_list = []
424 if v:
425 if v:
426 v = [v] if isinstance(v, basestring) else v
425 for u_id in set(v):
427 for u_id in set(v):
426 members_list.append(UsersGroupMember(
428 member = UsersGroupMember(users_group_id, u_id)
427 users_group_id,
429 members_list.append(member)
428 u_id))
429 setattr(users_group, 'members', members_list)
430 setattr(users_group, 'members', members_list)
430 setattr(users_group, k, v)
431 setattr(users_group, k, v)
431
432
432 Session.add(users_group)
433 Session.add(users_group)
433 Session.commit()
434 Session.commit()
434 except:
435 except:
435 log.error(traceback.format_exc())
436 log.error(traceback.format_exc())
436 Session.rollback()
437 Session.rollback()
437 raise
438 raise
438
439
439 @classmethod
440 @classmethod
440 def delete(cls, users_group_id):
441 def delete(cls, users_group_id):
441 try:
442 try:
442
443
443 # check if this group is not assigned to repo
444 # check if this group is not assigned to repo
444 assigned_groups = UsersGroupRepoToPerm.query()\
445 assigned_groups = UsersGroupRepoToPerm.query()\
445 .filter(UsersGroupRepoToPerm.users_group_id ==
446 .filter(UsersGroupRepoToPerm.users_group_id ==
446 users_group_id).all()
447 users_group_id).all()
447
448
448 if assigned_groups:
449 if assigned_groups:
449 raise UsersGroupsAssignedException('Group assigned to %s' %
450 raise UsersGroupsAssignedException('Group assigned to %s' %
450 assigned_groups)
451 assigned_groups)
451
452
452 users_group = cls.get(users_group_id, cache=False)
453 users_group = cls.get(users_group_id, cache=False)
453 Session.delete(users_group)
454 Session.delete(users_group)
454 Session.commit()
455 Session.commit()
455 except:
456 except:
456 log.error(traceback.format_exc())
457 log.error(traceback.format_exc())
457 Session.rollback()
458 Session.rollback()
458 raise
459 raise
459
460
460
461 class UsersGroupMember(Base, BaseModel):
461 class UsersGroupMember(Base, BaseModel):
462 __tablename__ = 'users_groups_members'
462 __tablename__ = 'users_groups_members'
463 __table_args__ = {'extend_existing':True}
463 __table_args__ = {'extend_existing':True}
464
464
465 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
465 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
466 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
466 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
468
468
469 user = relationship('User', lazy='joined')
469 user = relationship('User', lazy='joined')
470 users_group = relationship('UsersGroup')
470 users_group = relationship('UsersGroup')
471
471
472 def __init__(self, gr_id='', u_id=''):
472 def __init__(self, gr_id='', u_id=''):
473 self.users_group_id = gr_id
473 self.users_group_id = gr_id
474 self.user_id = u_id
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 class Repository(Base, BaseModel):
485 class Repository(Base, BaseModel):
477 __tablename__ = 'repositories'
486 __tablename__ = 'repositories'
478 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
487 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
479
488
480 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
489 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
481 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
490 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
482 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
491 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
483 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
492 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
484 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
493 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
485 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
494 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
486 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
495 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
487 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
496 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
488 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
497 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
489 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
498 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
490
499
491 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
500 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
492 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
501 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
493
502
494
503
495 user = relationship('User')
504 user = relationship('User')
496 fork = relationship('Repository', remote_side=repo_id)
505 fork = relationship('Repository', remote_side=repo_id)
497 group = relationship('Group')
506 group = relationship('Group')
498 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
507 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
499 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
508 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
500 stats = relationship('Statistics', cascade='all', uselist=False)
509 stats = relationship('Statistics', cascade='all', uselist=False)
501
510
502 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
511 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
503
512
504 logs = relationship('UserLog', cascade='all')
513 logs = relationship('UserLog', cascade='all')
505
514
506 def __repr__(self):
515 def __repr__(self):
507 return "<%s('%s:%s')>" % (self.__class__.__name__,
516 return "<%s('%s:%s')>" % (self.__class__.__name__,
508 self.repo_id, self.repo_name)
517 self.repo_id, self.repo_name)
509
518
510 @classmethod
519 @classmethod
511 def url_sep(cls):
520 def url_sep(cls):
512 return '/'
521 return '/'
513
522
514 @classmethod
523 @classmethod
515 def get_by_repo_name(cls, repo_name):
524 def get_by_repo_name(cls, repo_name):
516 q = Session.query(cls).filter(cls.repo_name == repo_name)
525 q = Session.query(cls).filter(cls.repo_name == repo_name)
517
518 q = q.options(joinedload(Repository.fork))\
526 q = q.options(joinedload(Repository.fork))\
519 .options(joinedload(Repository.user))\
527 .options(joinedload(Repository.user))\
520 .options(joinedload(Repository.group))\
528 .options(joinedload(Repository.group))
521
522 return q.one()
529 return q.one()
523
530
524 @classmethod
531 @classmethod
525 def get_repo_forks(cls, repo_id):
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 @classmethod
535 @classmethod
529 def base_path(cls):
536 def base_path(cls):
530 """
537 """
531 Returns base path when all repos are stored
538 Returns base path when all repos are stored
532
539
533 :param cls:
540 :param cls:
534 """
541 """
535 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
542 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
536 cls.url_sep())
543 cls.url_sep())
537 q.options(FromCache("sql_cache_short", "repository_repo_path"))
544 q.options(FromCache("sql_cache_short", "repository_repo_path"))
538 return q.one().ui_value
545 return q.one().ui_value
539
546
540 @property
547 @property
541 def just_name(self):
548 def just_name(self):
542 return self.repo_name.split(Repository.url_sep())[-1]
549 return self.repo_name.split(Repository.url_sep())[-1]
543
550
544 @property
551 @property
545 def groups_with_parents(self):
552 def groups_with_parents(self):
546 groups = []
553 groups = []
547 if self.group is None:
554 if self.group is None:
548 return groups
555 return groups
549
556
550 cur_gr = self.group
557 cur_gr = self.group
551 groups.insert(0, cur_gr)
558 groups.insert(0, cur_gr)
552 while 1:
559 while 1:
553 gr = getattr(cur_gr, 'parent_group', None)
560 gr = getattr(cur_gr, 'parent_group', None)
554 cur_gr = cur_gr.parent_group
561 cur_gr = cur_gr.parent_group
555 if gr is None:
562 if gr is None:
556 break
563 break
557 groups.insert(0, gr)
564 groups.insert(0, gr)
558
565
559 return groups
566 return groups
560
567
561 @property
568 @property
562 def groups_and_repo(self):
569 def groups_and_repo(self):
563 return self.groups_with_parents, self.just_name
570 return self.groups_with_parents, self.just_name
564
571
565 @LazyProperty
572 @LazyProperty
566 def repo_path(self):
573 def repo_path(self):
567 """
574 """
568 Returns base full path for that repository means where it actually
575 Returns base full path for that repository means where it actually
569 exists on a filesystem
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 Repository.url_sep())
579 Repository.url_sep())
573 q.options(FromCache("sql_cache_short", "repository_repo_path"))
580 q.options(FromCache("sql_cache_short", "repository_repo_path"))
574 return q.one().ui_value
581 return q.one().ui_value
575
582
576 @property
583 @property
577 def repo_full_path(self):
584 def repo_full_path(self):
578 p = [self.repo_path]
585 p = [self.repo_path]
579 # we need to split the name by / since this is how we store the
586 # we need to split the name by / since this is how we store the
580 # names in the database, but that eventually needs to be converted
587 # names in the database, but that eventually needs to be converted
581 # into a valid system path
588 # into a valid system path
582 p += self.repo_name.split(Repository.url_sep())
589 p += self.repo_name.split(Repository.url_sep())
583 return os.path.join(*p)
590 return os.path.join(*p)
584
591
585 def get_new_name(self, repo_name):
592 def get_new_name(self, repo_name):
586 """
593 """
587 returns new full repository name based on assigned group and new new
594 returns new full repository name based on assigned group and new new
588
595
589 :param group_name:
596 :param group_name:
590 """
597 """
591 path_prefix = self.group.full_path_splitted if self.group else []
598 path_prefix = self.group.full_path_splitted if self.group else []
592 return Repository.url_sep().join(path_prefix + [repo_name])
599 return Repository.url_sep().join(path_prefix + [repo_name])
593
600
594 @property
601 @property
595 def _ui(self):
602 def _ui(self):
596 """
603 """
597 Creates an db based ui object for this repository
604 Creates an db based ui object for this repository
598 """
605 """
599 from mercurial import ui
606 from mercurial import ui
600 from mercurial import config
607 from mercurial import config
601 baseui = ui.ui()
608 baseui = ui.ui()
602
609
603 #clean the baseui object
610 #clean the baseui object
604 baseui._ocfg = config.config()
611 baseui._ocfg = config.config()
605 baseui._ucfg = config.config()
612 baseui._ucfg = config.config()
606 baseui._tcfg = config.config()
613 baseui._tcfg = config.config()
607
614
608
615
609 ret = Session.query(RhodeCodeUi)\
616 ret = RhodeCodeUi.query()\
610 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
617 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
611
618
612 hg_ui = ret
619 hg_ui = ret
613 for ui_ in hg_ui:
620 for ui_ in hg_ui:
614 if ui_.ui_active:
621 if ui_.ui_active:
615 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
622 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
616 ui_.ui_key, ui_.ui_value)
623 ui_.ui_key, ui_.ui_value)
617 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
624 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
618
625
619 return baseui
626 return baseui
620
627
621 @classmethod
628 @classmethod
622 def is_valid(cls, repo_name):
629 def is_valid(cls, repo_name):
623 """
630 """
624 returns True if given repo name is a valid filesystem repository
631 returns True if given repo name is a valid filesystem repository
625
632
626 @param cls:
633 @param cls:
627 @param repo_name:
634 @param repo_name:
628 """
635 """
629 from rhodecode.lib.utils import is_valid_repo
636 from rhodecode.lib.utils import is_valid_repo
630
637
631 return is_valid_repo(repo_name, cls.base_path())
638 return is_valid_repo(repo_name, cls.base_path())
632
639
633
640
634 #==========================================================================
641 #==========================================================================
635 # SCM PROPERTIES
642 # SCM PROPERTIES
636 #==========================================================================
643 #==========================================================================
637
644
638 def get_changeset(self, rev):
645 def get_changeset(self, rev):
639 return get_changeset_safe(self.scm_instance, rev)
646 return get_changeset_safe(self.scm_instance, rev)
640
647
641 @property
648 @property
642 def tip(self):
649 def tip(self):
643 return self.get_changeset('tip')
650 return self.get_changeset('tip')
644
651
645 @property
652 @property
646 def author(self):
653 def author(self):
647 return self.tip.author
654 return self.tip.author
648
655
649 @property
656 @property
650 def last_change(self):
657 def last_change(self):
651 return self.scm_instance.last_change
658 return self.scm_instance.last_change
652
659
653 #==========================================================================
660 #==========================================================================
654 # SCM CACHE INSTANCE
661 # SCM CACHE INSTANCE
655 #==========================================================================
662 #==========================================================================
656
663
657 @property
664 @property
658 def invalidate(self):
665 def invalidate(self):
659 """
666 """
660 Returns Invalidation object if this repo should be invalidated
667 Returns Invalidation object if this repo should be invalidated
661 None otherwise. `cache_active = False` means that this cache
668 None otherwise. `cache_active = False` means that this cache
662 state is not valid and needs to be invalidated
669 state is not valid and needs to be invalidated
663 """
670 """
664 return Session.query(CacheInvalidation)\
671 return CacheInvalidation.query()\
665 .filter(CacheInvalidation.cache_key == self.repo_name)\
672 .filter(CacheInvalidation.cache_key == self.repo_name)\
666 .filter(CacheInvalidation.cache_active == False)\
673 .filter(CacheInvalidation.cache_active == False)\
667 .scalar()
674 .scalar()
668
675
669 def set_invalidate(self):
676 def set_invalidate(self):
670 """
677 """
671 set a cache for invalidation for this instance
678 set a cache for invalidation for this instance
672 """
679 """
673 inv = Session.query(CacheInvalidation)\
680 inv = CacheInvalidation.query()\
674 .filter(CacheInvalidation.cache_key == self.repo_name)\
681 .filter(CacheInvalidation.cache_key == self.repo_name)\
675 .scalar()
682 .scalar()
676
683
677 if inv is None:
684 if inv is None:
678 inv = CacheInvalidation(self.repo_name)
685 inv = CacheInvalidation(self.repo_name)
679 inv.cache_active = True
686 inv.cache_active = True
680 Session.add(inv)
687 Session.add(inv)
681 Session.commit()
688 Session.commit()
682
689
683 @LazyProperty
690 @LazyProperty
684 def scm_instance(self):
691 def scm_instance(self):
685 return self.__get_instance()
692 return self.__get_instance()
686
693
687 @property
694 @property
688 def scm_instance_cached(self):
695 def scm_instance_cached(self):
689 @cache_region('long_term')
696 @cache_region('long_term')
690 def _c(repo_name):
697 def _c(repo_name):
691 return self.__get_instance()
698 return self.__get_instance()
692
699
693 # TODO: remove this trick when beaker 1.6 is released
700 # TODO: remove this trick when beaker 1.6 is released
694 # and have fixed this issue with not supporting unicode keys
701 # and have fixed this issue with not supporting unicode keys
695 rn = safe_str(self.repo_name)
702 rn = safe_str(self.repo_name)
696
703
697 inv = self.invalidate
704 inv = self.invalidate
698 if inv is not None:
705 if inv is not None:
699 region_invalidate(_c, None, rn)
706 region_invalidate(_c, None, rn)
700 # update our cache
707 # update our cache
701 inv.cache_active = True
708 inv.cache_active = True
702 Session.add(inv)
709 Session.add(inv)
703 Session.commit()
710 Session.commit()
704
711
705 return _c(rn)
712 return _c(rn)
706
713
707 def __get_instance(self):
714 def __get_instance(self):
708
715
709 repo_full_path = self.repo_full_path
716 repo_full_path = self.repo_full_path
710
717
711 try:
718 try:
712 alias = get_scm(repo_full_path)[0]
719 alias = get_scm(repo_full_path)[0]
713 log.debug('Creating instance of %s repository', alias)
720 log.debug('Creating instance of %s repository', alias)
714 backend = get_backend(alias)
721 backend = get_backend(alias)
715 except VCSError:
722 except VCSError:
716 log.error(traceback.format_exc())
723 log.error(traceback.format_exc())
717 log.error('Perhaps this repository is in db and not in '
724 log.error('Perhaps this repository is in db and not in '
718 'filesystem run rescan repositories with '
725 'filesystem run rescan repositories with '
719 '"destroy old data " option from admin panel')
726 '"destroy old data " option from admin panel')
720 return
727 return
721
728
722 if alias == 'hg':
729 if alias == 'hg':
723
730
724 repo = backend(safe_str(repo_full_path), create=False,
731 repo = backend(safe_str(repo_full_path), create=False,
725 baseui=self._ui)
732 baseui=self._ui)
726 #skip hidden web repository
733 #skip hidden web repository
727 if repo._get_hidden():
734 if repo._get_hidden():
728 return
735 return
729 else:
736 else:
730 repo = backend(repo_full_path, create=False)
737 repo = backend(repo_full_path, create=False)
731
738
732 return repo
739 return repo
733
740
734
741
735 class Group(Base, BaseModel):
742 class Group(Base, BaseModel):
736 __tablename__ = 'groups'
743 __tablename__ = 'groups'
737 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
744 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
738 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
745 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
739 __mapper_args__ = {'order_by':'group_name'}
746 __mapper_args__ = {'order_by':'group_name'}
740
747
741 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
748 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
742 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
749 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
743 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
750 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
744 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
751 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
745
752
746 parent_group = relationship('Group', remote_side=group_id)
753 parent_group = relationship('Group', remote_side=group_id)
747
754
748
755
749 def __init__(self, group_name='', parent_group=None):
756 def __init__(self, group_name='', parent_group=None):
750 self.group_name = group_name
757 self.group_name = group_name
751 self.parent_group = parent_group
758 self.parent_group = parent_group
752
759
753 def __repr__(self):
760 def __repr__(self):
754 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
761 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
755 self.group_name)
762 self.group_name)
756
763
757 @classmethod
764 @classmethod
758 def groups_choices(cls):
765 def groups_choices(cls):
759 from webhelpers.html import literal as _literal
766 from webhelpers.html import literal as _literal
760 repo_groups = [('', '')]
767 repo_groups = [('', '')]
761 sep = ' &raquo; '
768 sep = ' &raquo; '
762 _name = lambda k: _literal(sep.join(k))
769 _name = lambda k: _literal(sep.join(k))
763
770
764 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
771 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
765 for x in cls.query().all()])
772 for x in cls.query().all()])
766
773
767 repo_groups = sorted(repo_groups,key=lambda t: t[1].split(sep)[0])
774 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
768 return repo_groups
775 return repo_groups
769
776
770 @classmethod
777 @classmethod
771 def url_sep(cls):
778 def url_sep(cls):
772 return '/'
779 return '/'
773
780
774 @classmethod
781 @classmethod
775 def get_by_group_name(cls, group_name):
782 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
776 return cls.query().filter(cls.group_name == group_name).scalar()
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 @property
794 @property
779 def parents(self):
795 def parents(self):
780 parents_recursion_limit = 5
796 parents_recursion_limit = 5
781 groups = []
797 groups = []
782 if self.parent_group is None:
798 if self.parent_group is None:
783 return groups
799 return groups
784 cur_gr = self.parent_group
800 cur_gr = self.parent_group
785 groups.insert(0, cur_gr)
801 groups.insert(0, cur_gr)
786 cnt = 0
802 cnt = 0
787 while 1:
803 while 1:
788 cnt += 1
804 cnt += 1
789 gr = getattr(cur_gr, 'parent_group', None)
805 gr = getattr(cur_gr, 'parent_group', None)
790 cur_gr = cur_gr.parent_group
806 cur_gr = cur_gr.parent_group
791 if gr is None:
807 if gr is None:
792 break
808 break
793 if cnt == parents_recursion_limit:
809 if cnt == parents_recursion_limit:
794 # this will prevent accidental infinit loops
810 # this will prevent accidental infinit loops
795 log.error('group nested more than %s' %
811 log.error('group nested more than %s' %
796 parents_recursion_limit)
812 parents_recursion_limit)
797 break
813 break
798
814
799 groups.insert(0, gr)
815 groups.insert(0, gr)
800 return groups
816 return groups
801
817
802 @property
818 @property
803 def children(self):
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 @property
822 @property
807 def name(self):
823 def name(self):
808 return self.group_name.split(Group.url_sep())[-1]
824 return self.group_name.split(Group.url_sep())[-1]
809
825
810 @property
826 @property
811 def full_path(self):
827 def full_path(self):
812 return self.group_name
828 return self.group_name
813
829
814 @property
830 @property
815 def full_path_splitted(self):
831 def full_path_splitted(self):
816 return self.group_name.split(Group.url_sep())
832 return self.group_name.split(Group.url_sep())
817
833
818 @property
834 @property
819 def repositories(self):
835 def repositories(self):
820 return Session.query(Repository).filter(Repository.group == self)
836 return Repository.query().filter(Repository.group == self)
821
837
822 @property
838 @property
823 def repositories_recursive_count(self):
839 def repositories_recursive_count(self):
824 cnt = self.repositories.count()
840 cnt = self.repositories.count()
825
841
826 def children_count(group):
842 def children_count(group):
827 cnt = 0
843 cnt = 0
828 for child in group.children:
844 for child in group.children:
829 cnt += child.repositories.count()
845 cnt += child.repositories.count()
830 cnt += children_count(child)
846 cnt += children_count(child)
831 return cnt
847 return cnt
832
848
833 return cnt + children_count(self)
849 return cnt + children_count(self)
834
850
835
851
836 def get_new_name(self, group_name):
852 def get_new_name(self, group_name):
837 """
853 """
838 returns new full group name based on parent and new name
854 returns new full group name based on parent and new name
839
855
840 :param group_name:
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 return Group.url_sep().join(path_prefix + [group_name])
860 return Group.url_sep().join(path_prefix + [group_name])
844
861
845
862
846 class Permission(Base, BaseModel):
863 class Permission(Base, BaseModel):
847 __tablename__ = 'permissions'
864 __tablename__ = 'permissions'
848 __table_args__ = {'extend_existing':True}
865 __table_args__ = {'extend_existing':True}
849 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
866 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
850 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
867 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
851 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
868 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
852
869
853 def __repr__(self):
870 def __repr__(self):
854 return "<%s('%s:%s')>" % (self.__class__.__name__,
871 return "<%s('%s:%s')>" % (self.__class__.__name__,
855 self.permission_id, self.permission_name)
872 self.permission_id, self.permission_name)
856
873
857 @classmethod
874 @classmethod
858 def get_by_key(cls, key):
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 class RepoToPerm(Base, BaseModel):
878 class RepoToPerm(Base, BaseModel):
862 __tablename__ = 'repo_to_perm'
879 __tablename__ = 'repo_to_perm'
863 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
880 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
864 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
881 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
865 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
882 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
866 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
883 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
867 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
884 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
868
885
869 user = relationship('User')
886 user = relationship('User')
870 permission = relationship('Permission')
887 permission = relationship('Permission')
871 repository = relationship('Repository')
888 repository = relationship('Repository')
872
889
873 class UserToPerm(Base, BaseModel):
890 class UserToPerm(Base, BaseModel):
874 __tablename__ = 'user_to_perm'
891 __tablename__ = 'user_to_perm'
875 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
892 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
876 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
893 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
877 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
894 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
878 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
895 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
879
896
880 user = relationship('User')
897 user = relationship('User')
881 permission = relationship('Permission')
898 permission = relationship('Permission')
882
899
883 @classmethod
900 @classmethod
884 def has_perm(cls, user_id, perm):
901 def has_perm(cls, user_id, perm):
885 if not isinstance(perm, Permission):
902 if not isinstance(perm, Permission):
886 raise Exception('perm needs to be an instance of Permission class')
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 .filter(cls.permission == perm).scalar() is not None
906 .filter(cls.permission == perm).scalar() is not None
890
907
891 @classmethod
908 @classmethod
892 def grant_perm(cls, user_id, perm):
909 def grant_perm(cls, user_id, perm):
893 if not isinstance(perm, Permission):
910 if not isinstance(perm, Permission):
894 raise Exception('perm needs to be an instance of Permission class')
911 raise Exception('perm needs to be an instance of Permission class')
895
912
896 new = cls()
913 new = cls()
897 new.user_id = user_id
914 new.user_id = user_id
898 new.permission = perm
915 new.permission = perm
899 try:
916 try:
900 Session.add(new)
917 Session.add(new)
901 Session.commit()
918 Session.commit()
902 except:
919 except:
903 Session.rollback()
920 Session.rollback()
904
921
905
922
906 @classmethod
923 @classmethod
907 def revoke_perm(cls, user_id, perm):
924 def revoke_perm(cls, user_id, perm):
908 if not isinstance(perm, Permission):
925 if not isinstance(perm, Permission):
909 raise Exception('perm needs to be an instance of Permission class')
926 raise Exception('perm needs to be an instance of Permission class')
910
927
911 try:
928 try:
912 Session.query(cls).filter(cls.user_id == user_id)\
929 cls.query().filter(cls.user_id == user_id)\
913 .filter(cls.permission == perm).delete()
930 .filter(cls.permission == perm).delete()
914 Session.commit()
931 Session.commit()
915 except:
932 except:
916 Session.rollback()
933 Session.rollback()
917
934
918 class UsersGroupRepoToPerm(Base, BaseModel):
935 class UsersGroupRepoToPerm(Base, BaseModel):
919 __tablename__ = 'users_group_repo_to_perm'
936 __tablename__ = 'users_group_repo_to_perm'
920 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
937 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
921 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
938 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
922 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
939 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
923 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
940 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
924 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
941 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
925
942
926 users_group = relationship('UsersGroup')
943 users_group = relationship('UsersGroup')
927 permission = relationship('Permission')
944 permission = relationship('Permission')
928 repository = relationship('Repository')
945 repository = relationship('Repository')
929
946
930 def __repr__(self):
947 def __repr__(self):
931 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
948 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
932
949
933 class UsersGroupToPerm(Base, BaseModel):
950 class UsersGroupToPerm(Base, BaseModel):
934 __tablename__ = 'users_group_to_perm'
951 __tablename__ = 'users_group_to_perm'
935 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
952 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
936 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
953 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
937 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
954 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
938
955
939 users_group = relationship('UsersGroup')
956 users_group = relationship('UsersGroup')
940 permission = relationship('Permission')
957 permission = relationship('Permission')
941
958
942
959
943 @classmethod
960 @classmethod
944 def has_perm(cls, users_group_id, perm):
961 def has_perm(cls, users_group_id, perm):
945 if not isinstance(perm, Permission):
962 if not isinstance(perm, Permission):
946 raise Exception('perm needs to be an instance of Permission class')
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 users_group_id)\
966 users_group_id)\
950 .filter(cls.permission == perm)\
967 .filter(cls.permission == perm)\
951 .scalar() is not None
968 .scalar() is not None
952
969
953 @classmethod
970 @classmethod
954 def grant_perm(cls, users_group_id, perm):
971 def grant_perm(cls, users_group_id, perm):
955 if not isinstance(perm, Permission):
972 if not isinstance(perm, Permission):
956 raise Exception('perm needs to be an instance of Permission class')
973 raise Exception('perm needs to be an instance of Permission class')
957
974
958 new = cls()
975 new = cls()
959 new.users_group_id = users_group_id
976 new.users_group_id = users_group_id
960 new.permission = perm
977 new.permission = perm
961 try:
978 try:
962 Session.add(new)
979 Session.add(new)
963 Session.commit()
980 Session.commit()
964 except:
981 except:
965 Session.rollback()
982 Session.rollback()
966
983
967
984
968 @classmethod
985 @classmethod
969 def revoke_perm(cls, users_group_id, perm):
986 def revoke_perm(cls, users_group_id, perm):
970 if not isinstance(perm, Permission):
987 if not isinstance(perm, Permission):
971 raise Exception('perm needs to be an instance of Permission class')
988 raise Exception('perm needs to be an instance of Permission class')
972
989
973 try:
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 .filter(cls.permission == perm).delete()
992 .filter(cls.permission == perm).delete()
976 Session.commit()
993 Session.commit()
977 except:
994 except:
978 Session.rollback()
995 Session.rollback()
979
996
980
997
981 class GroupToPerm(Base, BaseModel):
998 class GroupToPerm(Base, BaseModel):
982 __tablename__ = 'group_to_perm'
999 __tablename__ = 'group_to_perm'
983 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
1000 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
984
1001
985 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1002 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
986 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1003 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
987 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1004 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
988 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1005 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
989
1006
990 user = relationship('User')
1007 user = relationship('User')
991 permission = relationship('Permission')
1008 permission = relationship('Permission')
992 group = relationship('Group')
1009 group = relationship('Group')
993
1010
994 class Statistics(Base, BaseModel):
1011 class Statistics(Base, BaseModel):
995 __tablename__ = 'statistics'
1012 __tablename__ = 'statistics'
996 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
1013 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
997 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1014 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
998 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1015 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
999 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1016 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1000 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1017 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1001 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1018 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1002 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1019 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1003
1020
1004 repository = relationship('Repository', single_parent=True)
1021 repository = relationship('Repository', single_parent=True)
1005
1022
1006 class UserFollowing(Base, BaseModel):
1023 class UserFollowing(Base, BaseModel):
1007 __tablename__ = 'user_followings'
1024 __tablename__ = 'user_followings'
1008 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1025 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1009 UniqueConstraint('user_id', 'follows_user_id')
1026 UniqueConstraint('user_id', 'follows_user_id')
1010 , {'extend_existing':True})
1027 , {'extend_existing':True})
1011
1028
1012 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1029 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1013 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1030 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1014 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1031 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1015 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1032 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1016 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1033 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1017
1034
1018 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1035 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1019
1036
1020 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1037 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1021 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1038 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1022
1039
1023
1040
1024 @classmethod
1041 @classmethod
1025 def get_repo_followers(cls, repo_id):
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 class CacheInvalidation(Base, BaseModel):
1045 class CacheInvalidation(Base, BaseModel):
1029 __tablename__ = 'cache_invalidation'
1046 __tablename__ = 'cache_invalidation'
1030 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1047 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1031 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1048 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1032 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1049 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1033 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1050 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1034 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1051 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1035
1052
1036
1053
1037 def __init__(self, cache_key, cache_args=''):
1054 def __init__(self, cache_key, cache_args=''):
1038 self.cache_key = cache_key
1055 self.cache_key = cache_key
1039 self.cache_args = cache_args
1056 self.cache_args = cache_args
1040 self.cache_active = False
1057 self.cache_active = False
1041
1058
1042 def __repr__(self):
1059 def __repr__(self):
1043 return "<%s('%s:%s')>" % (self.__class__.__name__,
1060 return "<%s('%s:%s')>" % (self.__class__.__name__,
1044 self.cache_id, self.cache_key)
1061 self.cache_id, self.cache_key)
1045
1062
1046 class DbMigrateVersion(Base, BaseModel):
1063 class DbMigrateVersion(Base, BaseModel):
1047 __tablename__ = 'db_migrate_version'
1064 __tablename__ = 'db_migrate_version'
1048 __table_args__ = {'extend_existing':True}
1065 __table_args__ = {'extend_existing':True}
1049 repository_id = Column('repository_id', String(250), primary_key=True)
1066 repository_id = Column('repository_id', String(250), primary_key=True)
1050 repository_path = Column('repository_path', Text)
1067 repository_path = Column('repository_path', Text)
1051 version = Column('version', Integer)
1068 version = Column('version', Integer)
1069
@@ -1,688 +1,694 b''
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
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 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
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.
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 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
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 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 import os
22 import os
23 import re
23 import re
24 import logging
24 import logging
25 import traceback
25 import traceback
26
26
27 import formencode
27 import formencode
28 from formencode import All
28 from formencode import All
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 Email, Bool, StringBoolean, Set
30 Email, Bool, StringBoolean, Set
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from webhelpers.pylonslib.secure_form import authentication_token
33 from webhelpers.pylonslib.secure_form import authentication_token
34
34
35 from rhodecode.config.routing import ADMIN_PREFIX
35 from rhodecode.config.routing import ADMIN_PREFIX
36 from rhodecode.lib.utils import repo_name_slug
36 from rhodecode.lib.utils import repo_name_slug
37 from rhodecode.lib.auth import authenticate, get_crypt_password
37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 from rhodecode.lib.exceptions import LdapImportError
38 from rhodecode.lib.exceptions import LdapImportError
39 from rhodecode.model.user import UserModel
39 from rhodecode.model.user import UserModel
40 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.db import User, UsersGroup, Group
41 from rhodecode.model.db import User, UsersGroup, Group
42 from rhodecode import BACKENDS
42 from rhodecode import BACKENDS
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46 #this is needed to translate the messages using _() in validators
46 #this is needed to translate the messages using _() in validators
47 class State_obj(object):
47 class State_obj(object):
48 _ = staticmethod(_)
48 _ = staticmethod(_)
49
49
50 #==============================================================================
50 #==============================================================================
51 # VALIDATORS
51 # VALIDATORS
52 #==============================================================================
52 #==============================================================================
53 class ValidAuthToken(formencode.validators.FancyValidator):
53 class ValidAuthToken(formencode.validators.FancyValidator):
54 messages = {'invalid_token':_('Token mismatch')}
54 messages = {'invalid_token':_('Token mismatch')}
55
55
56 def validate_python(self, value, state):
56 def validate_python(self, value, state):
57
57
58 if value != authentication_token():
58 if value != authentication_token():
59 raise formencode.Invalid(self.message('invalid_token', state,
59 raise formencode.Invalid(self.message('invalid_token', state,
60 search_number=value), value, state)
60 search_number=value), value, state)
61
61
62 def ValidUsername(edit, old_data):
62 def ValidUsername(edit, old_data):
63 class _ValidUsername(formencode.validators.FancyValidator):
63 class _ValidUsername(formencode.validators.FancyValidator):
64
64
65 def validate_python(self, value, state):
65 def validate_python(self, value, state):
66 if value in ['default', 'new_user']:
66 if value in ['default', 'new_user']:
67 raise formencode.Invalid(_('Invalid username'), value, state)
67 raise formencode.Invalid(_('Invalid username'), value, state)
68 #check if user is unique
68 #check if user is unique
69 old_un = None
69 old_un = None
70 if edit:
70 if edit:
71 old_un = UserModel().get(old_data.get('user_id')).username
71 old_un = UserModel().get(old_data.get('user_id')).username
72
72
73 if old_un != value or not edit:
73 if old_un != value or not edit:
74 if User.get_by_username(value, case_insensitive=True):
74 if User.get_by_username(value, case_insensitive=True):
75 raise formencode.Invalid(_('This username already '
75 raise formencode.Invalid(_('This username already '
76 'exists') , value, state)
76 'exists') , value, state)
77
77
78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
79 raise formencode.Invalid(_('Username may only contain '
79 raise formencode.Invalid(_('Username may only contain '
80 'alphanumeric characters '
80 'alphanumeric characters '
81 'underscores, periods or dashes '
81 'underscores, periods or dashes '
82 'and must begin with alphanumeric '
82 'and must begin with alphanumeric '
83 'character'), value, state)
83 'character'), value, state)
84
84
85 return _ValidUsername
85 return _ValidUsername
86
86
87
87
88 def ValidUsersGroup(edit, old_data):
88 def ValidUsersGroup(edit, old_data):
89
89
90 class _ValidUsersGroup(formencode.validators.FancyValidator):
90 class _ValidUsersGroup(formencode.validators.FancyValidator):
91
91
92 def validate_python(self, value, state):
92 def validate_python(self, value, state):
93 if value in ['default']:
93 if value in ['default']:
94 raise formencode.Invalid(_('Invalid group name'), value, state)
94 raise formencode.Invalid(_('Invalid group name'), value, state)
95 #check if group is unique
95 #check if group is unique
96 old_ugname = None
96 old_ugname = None
97 if edit:
97 if edit:
98 old_ugname = UsersGroup.get(
98 old_ugname = UsersGroup.get(
99 old_data.get('users_group_id')).users_group_name
99 old_data.get('users_group_id')).users_group_name
100
100
101 if old_ugname != value or not edit:
101 if old_ugname != value or not edit:
102 if UsersGroup.get_by_group_name(value, cache=False,
102 if UsersGroup.get_by_group_name(value, cache=False,
103 case_insensitive=True):
103 case_insensitive=True):
104 raise formencode.Invalid(_('This users group '
104 raise formencode.Invalid(_('This users group '
105 'already exists') , value,
105 'already exists') , value,
106 state)
106 state)
107
107
108
108
109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
110 raise formencode.Invalid(_('Group name may only contain '
110 raise formencode.Invalid(_('Group name may only contain '
111 'alphanumeric characters '
111 'alphanumeric characters '
112 'underscores, periods or dashes '
112 'underscores, periods or dashes '
113 'and must begin with alphanumeric '
113 'and must begin with alphanumeric '
114 'character'), value, state)
114 'character'), value, state)
115
115
116 return _ValidUsersGroup
116 return _ValidUsersGroup
117
117
118
118
119 def ValidReposGroup(edit, old_data):
119 def ValidReposGroup(edit, old_data):
120 class _ValidReposGroup(formencode.validators.FancyValidator):
120 class _ValidReposGroup(formencode.validators.FancyValidator):
121
121
122 def validate_python(self, value, state):
122 def validate_python(self, value, state):
123 #TODO WRITE VALIDATIONS
123 #TODO WRITE VALIDATIONS
124 group_name = value.get('group_name')
124 group_name = value.get('group_name')
125 group_parent_id = int(value.get('group_parent_id') or -1)
125 group_parent_id = int(value.get('group_parent_id') or -1)
126
126
127 # slugify repo group just in case :)
127 # slugify repo group just in case :)
128 slug = repo_name_slug(group_name)
128 slug = repo_name_slug(group_name)
129
129
130 # check for parent of self
130 # check for parent of self
131 if edit and old_data['group_id'] == group_parent_id:
131 if edit and old_data['group_id'] == group_parent_id:
132 e_dict = {'group_parent_id':_('Cannot assign this group '
132 e_dict = {'group_parent_id':_('Cannot assign this group '
133 'as parent')}
133 'as parent')}
134 raise formencode.Invalid('', value, state,
134 raise formencode.Invalid('', value, state,
135 error_dict=e_dict)
135 error_dict=e_dict)
136
136
137 old_gname = None
137 old_gname = None
138 if edit:
138 if edit:
139 old_gname = Group.get(
139 old_gname = Group.get(
140 old_data.get('group_id')).group_name
140 old_data.get('group_id')).group_name
141
141
142 if old_gname != group_name or not edit:
142 if old_gname != group_name or not edit:
143 # check filesystem
143 # check filesystem
144 gr = Group.query().filter(Group.group_name == slug)\
144 gr = Group.query().filter(Group.group_name == slug)\
145 .filter(Group.group_parent_id == group_parent_id).scalar()
145 .filter(Group.group_parent_id == group_parent_id).scalar()
146
146
147 if gr:
147 if gr:
148 e_dict = {'group_name':_('This group already exists')}
148 e_dict = {'group_name':_('This group already exists')}
149 raise formencode.Invalid('', value, state,
149 raise formencode.Invalid('', value, state,
150 error_dict=e_dict)
150 error_dict=e_dict)
151
151
152 return _ValidReposGroup
152 return _ValidReposGroup
153
153
154 class ValidPassword(formencode.validators.FancyValidator):
154 class ValidPassword(formencode.validators.FancyValidator):
155
155
156 def to_python(self, value, state):
156 def to_python(self, value, state):
157
157
158 if value:
158 if value:
159
159
160 if value.get('password'):
160 if value.get('password'):
161 try:
161 try:
162 value['password'] = get_crypt_password(value['password'])
162 value['password'] = get_crypt_password(value['password'])
163 except UnicodeEncodeError:
163 except UnicodeEncodeError:
164 e_dict = {'password':_('Invalid characters in password')}
164 e_dict = {'password':_('Invalid characters in password')}
165 raise formencode.Invalid('', value, state, error_dict=e_dict)
165 raise formencode.Invalid('', value, state, error_dict=e_dict)
166
166
167 if value.get('password_confirmation'):
167 if value.get('password_confirmation'):
168 try:
168 try:
169 value['password_confirmation'] = \
169 value['password_confirmation'] = \
170 get_crypt_password(value['password_confirmation'])
170 get_crypt_password(value['password_confirmation'])
171 except UnicodeEncodeError:
171 except UnicodeEncodeError:
172 e_dict = {'password_confirmation':_('Invalid characters in password')}
172 e_dict = {'password_confirmation':_('Invalid characters in password')}
173 raise formencode.Invalid('', value, state, error_dict=e_dict)
173 raise formencode.Invalid('', value, state, error_dict=e_dict)
174
174
175 if value.get('new_password'):
175 if value.get('new_password'):
176 try:
176 try:
177 value['new_password'] = \
177 value['new_password'] = \
178 get_crypt_password(value['new_password'])
178 get_crypt_password(value['new_password'])
179 except UnicodeEncodeError:
179 except UnicodeEncodeError:
180 e_dict = {'new_password':_('Invalid characters in password')}
180 e_dict = {'new_password':_('Invalid characters in password')}
181 raise formencode.Invalid('', value, state, error_dict=e_dict)
181 raise formencode.Invalid('', value, state, error_dict=e_dict)
182
182
183 return value
183 return value
184
184
185 class ValidPasswordsMatch(formencode.validators.FancyValidator):
185 class ValidPasswordsMatch(formencode.validators.FancyValidator):
186
186
187 def validate_python(self, value, state):
187 def validate_python(self, value, state):
188
188
189 if value['password'] != value['password_confirmation']:
189 pass_val = value.get('password') or value.get('new_password')
190 if pass_val != value['password_confirmation']:
190 e_dict = {'password_confirmation':
191 e_dict = {'password_confirmation':
191 _('Passwords do not match')}
192 _('Passwords do not match')}
192 raise formencode.Invalid('', value, state, error_dict=e_dict)
193 raise formencode.Invalid('', value, state, error_dict=e_dict)
193
194
194 class ValidAuth(formencode.validators.FancyValidator):
195 class ValidAuth(formencode.validators.FancyValidator):
195 messages = {
196 messages = {
196 'invalid_password':_('invalid password'),
197 'invalid_password':_('invalid password'),
197 'invalid_login':_('invalid user name'),
198 'invalid_login':_('invalid user name'),
198 'disabled_account':_('Your account is disabled')
199 'disabled_account':_('Your account is disabled')
199
200 }
200 }
201
201 #error mapping
202 # error mapping
202 e_dict = {'username':messages['invalid_login'],
203 e_dict = {'username':messages['invalid_login'],
203 'password':messages['invalid_password']}
204 'password':messages['invalid_password']}
204 e_dict_disable = {'username':messages['disabled_account']}
205 e_dict_disable = {'username':messages['disabled_account']}
205
206
206 def validate_python(self, value, state):
207 def validate_python(self, value, state):
207 password = value['password']
208 password = value['password']
208 username = value['username']
209 username = value['username']
209 user = User.get_by_username(username)
210 user = User.get_by_username(username)
210
211
211 if authenticate(username, password):
212 if authenticate(username, password):
212 return value
213 return value
213 else:
214 else:
214 if user and user.active is False:
215 if user and user.active is False:
215 log.warning('user %s is disabled', username)
216 log.warning('user %s is disabled', username)
216 raise formencode.Invalid(self.message('disabled_account',
217 raise formencode.Invalid(self.message('disabled_account',
217 state=State_obj),
218 state=State_obj),
218 value, state,
219 value, state,
219 error_dict=self.e_dict_disable)
220 error_dict=self.e_dict_disable)
220 else:
221 else:
221 log.warning('user %s not authenticated', username)
222 log.warning('user %s not authenticated', username)
222 raise formencode.Invalid(self.message('invalid_password',
223 raise formencode.Invalid(self.message('invalid_password',
223 state=State_obj), value, state,
224 state=State_obj), value, state,
224 error_dict=self.e_dict)
225 error_dict=self.e_dict)
225
226
226 class ValidRepoUser(formencode.validators.FancyValidator):
227 class ValidRepoUser(formencode.validators.FancyValidator):
227
228
228 def to_python(self, value, state):
229 def to_python(self, value, state):
229 try:
230 try:
230 User.query().filter(User.active == True)\
231 User.query().filter(User.active == True)\
231 .filter(User.username == value).one()
232 .filter(User.username == value).one()
232 except Exception:
233 except Exception:
233 raise formencode.Invalid(_('This username is not valid'),
234 raise formencode.Invalid(_('This username is not valid'),
234 value, state)
235 value, state)
235 return value
236 return value
236
237
237 def ValidRepoName(edit, old_data):
238 def ValidRepoName(edit, old_data):
238 class _ValidRepoName(formencode.validators.FancyValidator):
239 class _ValidRepoName(formencode.validators.FancyValidator):
239 def to_python(self, value, state):
240 def to_python(self, value, state):
240
241
241 repo_name = value.get('repo_name')
242 repo_name = value.get('repo_name')
242
243
243 slug = repo_name_slug(repo_name)
244 slug = repo_name_slug(repo_name)
244 if slug in [ADMIN_PREFIX, '']:
245 if slug in [ADMIN_PREFIX, '']:
245 e_dict = {'repo_name': _('This repository name is disallowed')}
246 e_dict = {'repo_name': _('This repository name is disallowed')}
246 raise formencode.Invalid('', value, state, error_dict=e_dict)
247 raise formencode.Invalid('', value, state, error_dict=e_dict)
247
248
248
249
249 if value.get('repo_group'):
250 if value.get('repo_group'):
250 gr = Group.get(value.get('repo_group'))
251 gr = Group.get(value.get('repo_group'))
251 group_path = gr.full_path
252 group_path = gr.full_path
252 # value needs to be aware of group name in order to check
253 # value needs to be aware of group name in order to check
253 # db key This is an actual just the name to store in the
254 # db key This is an actual just the name to store in the
254 # database
255 # database
255 repo_name_full = group_path + Group.url_sep() + repo_name
256 repo_name_full = group_path + Group.url_sep() + repo_name
257
256 else:
258 else:
257 group_path = ''
259 group_path = ''
258 repo_name_full = repo_name
260 repo_name_full = repo_name
259
261
260
262
261 value['repo_name_full'] = repo_name_full
263 value['repo_name_full'] = repo_name_full
262 rename = old_data.get('repo_name') != repo_name_full
264 rename = old_data.get('repo_name') != repo_name_full
263 create = not edit
265 create = not edit
264 if rename or create:
266 if rename or create:
265
267
266 if group_path != '':
268 if group_path != '':
267 if RepoModel().get_by_repo_name(repo_name_full,):
269 if RepoModel().get_by_repo_name(repo_name_full,):
268 e_dict = {'repo_name':_('This repository already '
270 e_dict = {'repo_name':_('This repository already '
269 'exists in a group "%s"') %
271 'exists in a group "%s"') %
270 gr.group_name}
272 gr.group_name}
271 raise formencode.Invalid('', value, state,
273 raise formencode.Invalid('', value, state,
272 error_dict=e_dict)
274 error_dict=e_dict)
273 elif Group.get_by_group_name(repo_name_full):
275 elif Group.get_by_group_name(repo_name_full):
274 e_dict = {'repo_name':_('There is a group with this'
276 e_dict = {'repo_name':_('There is a group with this'
275 ' name already "%s"') %
277 ' name already "%s"') %
276 repo_name_full}
278 repo_name_full}
277 raise formencode.Invalid('', value, state,
279 raise formencode.Invalid('', value, state,
278 error_dict=e_dict)
280 error_dict=e_dict)
279
281
280 elif RepoModel().get_by_repo_name(repo_name_full):
282 elif RepoModel().get_by_repo_name(repo_name_full):
281 e_dict = {'repo_name':_('This repository '
283 e_dict = {'repo_name':_('This repository '
282 'already exists')}
284 'already exists')}
283 raise formencode.Invalid('', value, state,
285 raise formencode.Invalid('', value, state,
284 error_dict=e_dict)
286 error_dict=e_dict)
285
287
286 return value
288 return value
287
289
288 return _ValidRepoName
290 return _ValidRepoName
289
291
290 def ValidForkName():
292 def ValidForkName():
291 class _ValidForkName(formencode.validators.FancyValidator):
293 class _ValidForkName(formencode.validators.FancyValidator):
292 def to_python(self, value, state):
294 def to_python(self, value, state):
293
295
294 repo_name = value.get('fork_name')
296 repo_name = value.get('fork_name')
295
297
296 slug = repo_name_slug(repo_name)
298 slug = repo_name_slug(repo_name)
297 if slug in [ADMIN_PREFIX, '']:
299 if slug in [ADMIN_PREFIX, '']:
298 e_dict = {'repo_name': _('This repository name is disallowed')}
300 e_dict = {'repo_name': _('This repository name is disallowed')}
299 raise formencode.Invalid('', value, state, error_dict=e_dict)
301 raise formencode.Invalid('', value, state, error_dict=e_dict)
300
302
301 if RepoModel().get_by_repo_name(repo_name):
303 if RepoModel().get_by_repo_name(repo_name):
302 e_dict = {'fork_name':_('This repository '
304 e_dict = {'fork_name':_('This repository '
303 'already exists')}
305 'already exists')}
304 raise formencode.Invalid('', value, state,
306 raise formencode.Invalid('', value, state,
305 error_dict=e_dict)
307 error_dict=e_dict)
306 return value
308 return value
307 return _ValidForkName
309 return _ValidForkName
308
310
309
311
310 def SlugifyName():
312 def SlugifyName():
311 class _SlugifyName(formencode.validators.FancyValidator):
313 class _SlugifyName(formencode.validators.FancyValidator):
312
314
313 def to_python(self, value, state):
315 def to_python(self, value, state):
314 return repo_name_slug(value)
316 return repo_name_slug(value)
315
317
316 return _SlugifyName
318 return _SlugifyName
317
319
318 def ValidCloneUri():
320 def ValidCloneUri():
319 from mercurial.httprepo import httprepository, httpsrepository
321 from mercurial.httprepo import httprepository, httpsrepository
320 from rhodecode.lib.utils import make_ui
322 from rhodecode.lib.utils import make_ui
321
323
322 class _ValidCloneUri(formencode.validators.FancyValidator):
324 class _ValidCloneUri(formencode.validators.FancyValidator):
323
325
324 def to_python(self, value, state):
326 def to_python(self, value, state):
325 if not value:
327 if not value:
326 pass
328 pass
327 elif value.startswith('https'):
329 elif value.startswith('https'):
328 try:
330 try:
329 httpsrepository(make_ui('db'), value).capabilities
331 httpsrepository(make_ui('db'), value).capabilities
330 except Exception, e:
332 except Exception, e:
331 log.error(traceback.format_exc())
333 log.error(traceback.format_exc())
332 raise formencode.Invalid(_('invalid clone url'), value,
334 raise formencode.Invalid(_('invalid clone url'), value,
333 state)
335 state)
334 elif value.startswith('http'):
336 elif value.startswith('http'):
335 try:
337 try:
336 httprepository(make_ui('db'), value).capabilities
338 httprepository(make_ui('db'), value).capabilities
337 except Exception, e:
339 except Exception, e:
338 log.error(traceback.format_exc())
340 log.error(traceback.format_exc())
339 raise formencode.Invalid(_('invalid clone url'), value,
341 raise formencode.Invalid(_('invalid clone url'), value,
340 state)
342 state)
341 else:
343 else:
342 raise formencode.Invalid(_('Invalid clone url, provide a '
344 raise formencode.Invalid(_('Invalid clone url, provide a '
343 'valid clone http\s url'), value,
345 'valid clone http\s url'), value,
344 state)
346 state)
345 return value
347 return value
346
348
347 return _ValidCloneUri
349 return _ValidCloneUri
348
350
349 def ValidForkType(old_data):
351 def ValidForkType(old_data):
350 class _ValidForkType(formencode.validators.FancyValidator):
352 class _ValidForkType(formencode.validators.FancyValidator):
351
353
352 def to_python(self, value, state):
354 def to_python(self, value, state):
353 if old_data['repo_type'] != value:
355 if old_data['repo_type'] != value:
354 raise formencode.Invalid(_('Fork have to be the same '
356 raise formencode.Invalid(_('Fork have to be the same '
355 'type as original'), value, state)
357 'type as original'), value, state)
356
358
357 return value
359 return value
358 return _ValidForkType
360 return _ValidForkType
359
361
360 class ValidPerms(formencode.validators.FancyValidator):
362 class ValidPerms(formencode.validators.FancyValidator):
361 messages = {'perm_new_member_name':_('This username or users group name'
363 messages = {'perm_new_member_name':_('This username or users group name'
362 ' is not valid')}
364 ' is not valid')}
363
365
364 def to_python(self, value, state):
366 def to_python(self, value, state):
365 perms_update = []
367 perms_update = []
366 perms_new = []
368 perms_new = []
367 #build a list of permission to update and new permission to create
369 #build a list of permission to update and new permission to create
368 for k, v in value.items():
370 for k, v in value.items():
369 #means new added member to permissions
371 #means new added member to permissions
370 if k.startswith('perm_new_member'):
372 if k.startswith('perm_new_member'):
371 new_perm = value.get('perm_new_member', False)
373 new_perm = value.get('perm_new_member', False)
372 new_member = value.get('perm_new_member_name', False)
374 new_member = value.get('perm_new_member_name', False)
373 new_type = value.get('perm_new_member_type')
375 new_type = value.get('perm_new_member_type')
374
376
375 if new_member and new_perm:
377 if new_member and new_perm:
376 if (new_member, new_perm, new_type) not in perms_new:
378 if (new_member, new_perm, new_type) not in perms_new:
377 perms_new.append((new_member, new_perm, new_type))
379 perms_new.append((new_member, new_perm, new_type))
378 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
380 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
379 member = k[7:]
381 member = k[7:]
380 t = {'u':'user',
382 t = {'u':'user',
381 'g':'users_group'}[k[0]]
383 'g':'users_group'}[k[0]]
382 if member == 'default':
384 if member == 'default':
383 if value['private']:
385 if value['private']:
384 #set none for default when updating to private repo
386 #set none for default when updating to private repo
385 v = 'repository.none'
387 v = 'repository.none'
386 perms_update.append((member, v, t))
388 perms_update.append((member, v, t))
387
389
388 value['perms_updates'] = perms_update
390 value['perms_updates'] = perms_update
389 value['perms_new'] = perms_new
391 value['perms_new'] = perms_new
390
392
391 #update permissions
393 #update permissions
392 for k, v, t in perms_new:
394 for k, v, t in perms_new:
393 try:
395 try:
394 if t is 'user':
396 if t is 'user':
395 self.user_db = User.query()\
397 self.user_db = User.query()\
396 .filter(User.active == True)\
398 .filter(User.active == True)\
397 .filter(User.username == k).one()
399 .filter(User.username == k).one()
398 if t is 'users_group':
400 if t is 'users_group':
399 self.user_db = UsersGroup.query()\
401 self.user_db = UsersGroup.query()\
400 .filter(UsersGroup.users_group_active == True)\
402 .filter(UsersGroup.users_group_active == True)\
401 .filter(UsersGroup.users_group_name == k).one()
403 .filter(UsersGroup.users_group_name == k).one()
402
404
403 except Exception:
405 except Exception:
404 msg = self.message('perm_new_member_name',
406 msg = self.message('perm_new_member_name',
405 state=State_obj)
407 state=State_obj)
406 raise formencode.Invalid(msg, value, state,
408 raise formencode.Invalid(msg, value, state,
407 error_dict={'perm_new_member_name':msg})
409 error_dict={'perm_new_member_name':msg})
408 return value
410 return value
409
411
410 class ValidSettings(formencode.validators.FancyValidator):
412 class ValidSettings(formencode.validators.FancyValidator):
411
413
412 def to_python(self, value, state):
414 def to_python(self, value, state):
413 #settings form can't edit user
415 #settings form can't edit user
414 if value.has_key('user'):
416 if value.has_key('user'):
415 del['value']['user']
417 del['value']['user']
416
418
417 return value
419 return value
418
420
419 class ValidPath(formencode.validators.FancyValidator):
421 class ValidPath(formencode.validators.FancyValidator):
420 def to_python(self, value, state):
422 def to_python(self, value, state):
421
423
422 if not os.path.isdir(value):
424 if not os.path.isdir(value):
423 msg = _('This is not a valid path')
425 msg = _('This is not a valid path')
424 raise formencode.Invalid(msg, value, state,
426 raise formencode.Invalid(msg, value, state,
425 error_dict={'paths_root_path':msg})
427 error_dict={'paths_root_path':msg})
426 return value
428 return value
427
429
428 def UniqSystemEmail(old_data):
430 def UniqSystemEmail(old_data):
429 class _UniqSystemEmail(formencode.validators.FancyValidator):
431 class _UniqSystemEmail(formencode.validators.FancyValidator):
430 def to_python(self, value, state):
432 def to_python(self, value, state):
431 value = value.lower()
433 value = value.lower()
432 if old_data.get('email') != value:
434 if old_data.get('email') != value:
433 user = User.query().filter(User.email == value).scalar()
435 user = User.query().filter(User.email == value).scalar()
434 if user:
436 if user:
435 raise formencode.Invalid(
437 raise formencode.Invalid(
436 _("This e-mail address is already taken"),
438 _("This e-mail address is already taken"),
437 value, state)
439 value, state)
438 return value
440 return value
439
441
440 return _UniqSystemEmail
442 return _UniqSystemEmail
441
443
442 class ValidSystemEmail(formencode.validators.FancyValidator):
444 class ValidSystemEmail(formencode.validators.FancyValidator):
443 def to_python(self, value, state):
445 def to_python(self, value, state):
444 value = value.lower()
446 value = value.lower()
445 user = User.query().filter(User.email == value).scalar()
447 user = User.query().filter(User.email == value).scalar()
446 if user is None:
448 if user is None:
447 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
449 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
448 value, state)
450 value, state)
449
451
450 return value
452 return value
451
453
452 class LdapLibValidator(formencode.validators.FancyValidator):
454 class LdapLibValidator(formencode.validators.FancyValidator):
453
455
454 def to_python(self, value, state):
456 def to_python(self, value, state):
455
457
456 try:
458 try:
457 import ldap
459 import ldap
458 except ImportError:
460 except ImportError:
459 raise LdapImportError
461 raise LdapImportError
460 return value
462 return value
461
463
462 class AttrLoginValidator(formencode.validators.FancyValidator):
464 class AttrLoginValidator(formencode.validators.FancyValidator):
463
465
464 def to_python(self, value, state):
466 def to_python(self, value, state):
465
467
466 if not value or not isinstance(value, (str, unicode)):
468 if not value or not isinstance(value, (str, unicode)):
467 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
469 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
468 "must be specified - this is the name "
470 "must be specified - this is the name "
469 "of the attribute that is equivalent "
471 "of the attribute that is equivalent "
470 "to 'username'"),
472 "to 'username'"),
471 value, state)
473 value, state)
472
474
473 return value
475 return value
474
476
475 #===============================================================================
477 #===============================================================================
476 # FORMS
478 # FORMS
477 #===============================================================================
479 #===============================================================================
478 class LoginForm(formencode.Schema):
480 class LoginForm(formencode.Schema):
479 allow_extra_fields = True
481 allow_extra_fields = True
480 filter_extra_fields = True
482 filter_extra_fields = True
481 username = UnicodeString(
483 username = UnicodeString(
482 strip=True,
484 strip=True,
483 min=1,
485 min=1,
484 not_empty=True,
486 not_empty=True,
485 messages={
487 messages={
486 'empty':_('Please enter a login'),
488 'empty':_('Please enter a login'),
487 'tooShort':_('Enter a value %(min)i characters long or more')}
489 'tooShort':_('Enter a value %(min)i characters long or more')}
488 )
490 )
489
491
490 password = UnicodeString(
492 password = UnicodeString(
491 strip=True,
493 strip=True,
492 min=3,
494 min=3,
493 not_empty=True,
495 not_empty=True,
494 messages={
496 messages={
495 'empty':_('Please enter a password'),
497 'empty':_('Please enter a password'),
496 'tooShort':_('Enter %(min)i characters or more')}
498 'tooShort':_('Enter %(min)i characters or more')}
497 )
499 )
498
500
499
500 #chained validators have access to all data
501 chained_validators = [ValidAuth]
501 chained_validators = [ValidAuth]
502
502
503 def UserForm(edit=False, old_data={}):
503 def UserForm(edit=False, old_data={}):
504 class _UserForm(formencode.Schema):
504 class _UserForm(formencode.Schema):
505 allow_extra_fields = True
505 allow_extra_fields = True
506 filter_extra_fields = True
506 filter_extra_fields = True
507 username = All(UnicodeString(strip=True, min=1, not_empty=True),
507 username = All(UnicodeString(strip=True, min=1, not_empty=True),
508 ValidUsername(edit, old_data))
508 ValidUsername(edit, old_data))
509 if edit:
509 if edit:
510 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
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 admin = StringBoolean(if_missing=False)
512 admin = StringBoolean(if_missing=False)
512 else:
513 else:
513 password = All(UnicodeString(strip=True, min=6, not_empty=True))
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 active = StringBoolean(if_missing=False)
517 active = StringBoolean(if_missing=False)
515 name = UnicodeString(strip=True, min=1, not_empty=True)
518 name = UnicodeString(strip=True, min=1, not_empty=True)
516 lastname = UnicodeString(strip=True, min=1, not_empty=True)
519 lastname = UnicodeString(strip=True, min=1, not_empty=True)
517 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
520 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
518
521
519 chained_validators = [ValidPassword]
522 chained_validators = [ValidPasswordsMatch, ValidPassword]
520
523
521 return _UserForm
524 return _UserForm
522
525
523
526
524 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
527 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
525 class _UsersGroupForm(formencode.Schema):
528 class _UsersGroupForm(formencode.Schema):
526 allow_extra_fields = True
529 allow_extra_fields = True
527 filter_extra_fields = True
530 filter_extra_fields = True
528
531
529 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
532 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
530 ValidUsersGroup(edit, old_data))
533 ValidUsersGroup(edit, old_data))
531
534
532 users_group_active = StringBoolean(if_missing=False)
535 users_group_active = StringBoolean(if_missing=False)
533
536
534 if edit:
537 if edit:
535 users_group_members = OneOf(available_members, hideList=False,
538 users_group_members = OneOf(available_members, hideList=False,
536 testValueList=True,
539 testValueList=True,
537 if_missing=None, not_empty=False)
540 if_missing=None, not_empty=False)
538
541
539 return _UsersGroupForm
542 return _UsersGroupForm
540
543
541 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
544 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
542 class _ReposGroupForm(formencode.Schema):
545 class _ReposGroupForm(formencode.Schema):
543 allow_extra_fields = True
546 allow_extra_fields = True
544 filter_extra_fields = True
547 filter_extra_fields = True
545
548
546 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
549 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
547 SlugifyName())
550 SlugifyName())
548 group_description = UnicodeString(strip=True, min=1,
551 group_description = UnicodeString(strip=True, min=1,
549 not_empty=True)
552 not_empty=True)
550 group_parent_id = OneOf(available_groups, hideList=False,
553 group_parent_id = OneOf(available_groups, hideList=False,
551 testValueList=True,
554 testValueList=True,
552 if_missing=None, not_empty=False)
555 if_missing=None, not_empty=False)
553
556
554 chained_validators = [ValidReposGroup(edit, old_data)]
557 chained_validators = [ValidReposGroup(edit, old_data)]
555
558
556 return _ReposGroupForm
559 return _ReposGroupForm
557
560
558 def RegisterForm(edit=False, old_data={}):
561 def RegisterForm(edit=False, old_data={}):
559 class _RegisterForm(formencode.Schema):
562 class _RegisterForm(formencode.Schema):
560 allow_extra_fields = True
563 allow_extra_fields = True
561 filter_extra_fields = True
564 filter_extra_fields = True
562 username = All(ValidUsername(edit, old_data),
565 username = All(ValidUsername(edit, old_data),
563 UnicodeString(strip=True, min=1, not_empty=True))
566 UnicodeString(strip=True, min=1, not_empty=True))
564 password = All(UnicodeString(strip=True, min=6, not_empty=True))
567 password = All(UnicodeString(strip=True, min=6, not_empty=True))
565 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
568 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
566 active = StringBoolean(if_missing=False)
569 active = StringBoolean(if_missing=False)
567 name = UnicodeString(strip=True, min=1, not_empty=True)
570 name = UnicodeString(strip=True, min=1, not_empty=True)
568 lastname = UnicodeString(strip=True, min=1, not_empty=True)
571 lastname = UnicodeString(strip=True, min=1, not_empty=True)
569 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
572 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
570
573
571 chained_validators = [ValidPasswordsMatch, ValidPassword]
574 chained_validators = [ValidPasswordsMatch, ValidPassword]
572
575
573 return _RegisterForm
576 return _RegisterForm
574
577
575 def PasswordResetForm():
578 def PasswordResetForm():
576 class _PasswordResetForm(formencode.Schema):
579 class _PasswordResetForm(formencode.Schema):
577 allow_extra_fields = True
580 allow_extra_fields = True
578 filter_extra_fields = True
581 filter_extra_fields = True
579 email = All(ValidSystemEmail(), Email(not_empty=True))
582 email = All(ValidSystemEmail(), Email(not_empty=True))
580 return _PasswordResetForm
583 return _PasswordResetForm
581
584
582 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
585 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
583 repo_groups=[]):
586 repo_groups=[]):
584 class _RepoForm(formencode.Schema):
587 class _RepoForm(formencode.Schema):
585 allow_extra_fields = True
588 allow_extra_fields = True
586 filter_extra_fields = False
589 filter_extra_fields = False
587 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
590 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
588 SlugifyName())
591 SlugifyName())
589 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
592 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
590 ValidCloneUri()())
593 ValidCloneUri()())
591 repo_group = OneOf(repo_groups, hideList=True)
594 repo_group = OneOf(repo_groups, hideList=True)
592 repo_type = OneOf(supported_backends)
595 repo_type = OneOf(supported_backends)
593 description = UnicodeString(strip=True, min=1, not_empty=True)
596 description = UnicodeString(strip=True, min=1, not_empty=True)
594 private = StringBoolean(if_missing=False)
597 private = StringBoolean(if_missing=False)
595 enable_statistics = StringBoolean(if_missing=False)
598 enable_statistics = StringBoolean(if_missing=False)
596 enable_downloads = StringBoolean(if_missing=False)
599 enable_downloads = StringBoolean(if_missing=False)
597
600
598 if edit:
601 if edit:
599 #this is repo owner
602 #this is repo owner
600 user = All(UnicodeString(not_empty=True), ValidRepoUser)
603 user = All(UnicodeString(not_empty=True), ValidRepoUser)
601
604
602 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
605 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
603 return _RepoForm
606 return _RepoForm
604
607
605 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
608 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
606 class _RepoForkForm(formencode.Schema):
609 class _RepoForkForm(formencode.Schema):
607 allow_extra_fields = True
610 allow_extra_fields = True
608 filter_extra_fields = False
611 filter_extra_fields = False
609 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
612 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
610 SlugifyName())
613 SlugifyName())
611 description = UnicodeString(strip=True, min=1, not_empty=True)
614 description = UnicodeString(strip=True, min=1, not_empty=True)
612 private = StringBoolean(if_missing=False)
615 private = StringBoolean(if_missing=False)
613 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
616 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
614
617
615 chained_validators = [ValidForkName()]
618 chained_validators = [ValidForkName()]
616
619
617 return _RepoForkForm
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 class _RepoForm(formencode.Schema):
624 class _RepoForm(formencode.Schema):
621 allow_extra_fields = True
625 allow_extra_fields = True
622 filter_extra_fields = False
626 filter_extra_fields = False
623 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
627 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
624 SlugifyName())
628 SlugifyName())
625 description = UnicodeString(strip=True, min=1, not_empty=True)
629 description = UnicodeString(strip=True, min=1, not_empty=True)
630 repo_group = OneOf(repo_groups, hideList=True)
626 private = StringBoolean(if_missing=False)
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 return _RepoForm
635 return _RepoForm
630
636
631
637
632 def ApplicationSettingsForm():
638 def ApplicationSettingsForm():
633 class _ApplicationSettingsForm(formencode.Schema):
639 class _ApplicationSettingsForm(formencode.Schema):
634 allow_extra_fields = True
640 allow_extra_fields = True
635 filter_extra_fields = False
641 filter_extra_fields = False
636 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
642 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
637 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
643 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
638 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
644 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
639
645
640 return _ApplicationSettingsForm
646 return _ApplicationSettingsForm
641
647
642 def ApplicationUiSettingsForm():
648 def ApplicationUiSettingsForm():
643 class _ApplicationUiSettingsForm(formencode.Schema):
649 class _ApplicationUiSettingsForm(formencode.Schema):
644 allow_extra_fields = True
650 allow_extra_fields = True
645 filter_extra_fields = False
651 filter_extra_fields = False
646 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
652 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
647 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
653 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
648 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
654 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
649 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
655 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
650 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
656 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
651 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
657 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
652
658
653 return _ApplicationUiSettingsForm
659 return _ApplicationUiSettingsForm
654
660
655 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
661 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
656 class _DefaultPermissionsForm(formencode.Schema):
662 class _DefaultPermissionsForm(formencode.Schema):
657 allow_extra_fields = True
663 allow_extra_fields = True
658 filter_extra_fields = True
664 filter_extra_fields = True
659 overwrite_default = StringBoolean(if_missing=False)
665 overwrite_default = StringBoolean(if_missing=False)
660 anonymous = OneOf(['True', 'False'], if_missing=False)
666 anonymous = OneOf(['True', 'False'], if_missing=False)
661 default_perm = OneOf(perms_choices)
667 default_perm = OneOf(perms_choices)
662 default_register = OneOf(register_choices)
668 default_register = OneOf(register_choices)
663 default_create = OneOf(create_choices)
669 default_create = OneOf(create_choices)
664
670
665 return _DefaultPermissionsForm
671 return _DefaultPermissionsForm
666
672
667
673
668 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
674 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
669 class _LdapSettingsForm(formencode.Schema):
675 class _LdapSettingsForm(formencode.Schema):
670 allow_extra_fields = True
676 allow_extra_fields = True
671 filter_extra_fields = True
677 filter_extra_fields = True
672 pre_validators = [LdapLibValidator]
678 pre_validators = [LdapLibValidator]
673 ldap_active = StringBoolean(if_missing=False)
679 ldap_active = StringBoolean(if_missing=False)
674 ldap_host = UnicodeString(strip=True,)
680 ldap_host = UnicodeString(strip=True,)
675 ldap_port = Number(strip=True,)
681 ldap_port = Number(strip=True,)
676 ldap_tls_kind = OneOf(tls_kind_choices)
682 ldap_tls_kind = OneOf(tls_kind_choices)
677 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
683 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
678 ldap_dn_user = UnicodeString(strip=True,)
684 ldap_dn_user = UnicodeString(strip=True,)
679 ldap_dn_pass = UnicodeString(strip=True,)
685 ldap_dn_pass = UnicodeString(strip=True,)
680 ldap_base_dn = UnicodeString(strip=True,)
686 ldap_base_dn = UnicodeString(strip=True,)
681 ldap_filter = UnicodeString(strip=True,)
687 ldap_filter = UnicodeString(strip=True,)
682 ldap_search_scope = OneOf(search_scope_choices)
688 ldap_search_scope = OneOf(search_scope_choices)
683 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
689 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
684 ldap_attr_firstname = UnicodeString(strip=True,)
690 ldap_attr_firstname = UnicodeString(strip=True,)
685 ldap_attr_lastname = UnicodeString(strip=True,)
691 ldap_attr_lastname = UnicodeString(strip=True,)
686 ldap_attr_email = UnicodeString(strip=True,)
692 ldap_attr_email = UnicodeString(strip=True,)
687
693
688 return _LdapSettingsForm
694 return _LdapSettingsForm
@@ -1,370 +1,411 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.repo
3 rhodecode.model.repo
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository model for rhodecode
6 Repository model for rhodecode
7
7
8 :created_on: Jun 5, 2010
8 :created_on: Jun 5, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import shutil
26 import shutil
27 import logging
27 import logging
28 import traceback
28 import traceback
29 from datetime import datetime
29 from datetime import datetime
30
30
31 from sqlalchemy.orm import joinedload, make_transient
31 from sqlalchemy.orm import joinedload, make_transient
32
32
33 from vcs.utils.lazy import LazyProperty
33 from vcs.utils.lazy import LazyProperty
34 from vcs.backends import get_backend
34 from vcs.backends import get_backend
35
35
36 from rhodecode.lib import safe_str
36 from rhodecode.lib import safe_str
37
37
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.caching_query import FromCache
39 from rhodecode.model.caching_query import FromCache
40 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
40 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
41 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
41 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class RepoModel(BaseModel):
47 class RepoModel(BaseModel):
48
48
49 @LazyProperty
49 @LazyProperty
50 def repos_path(self):
50 def repos_path(self):
51 """Get's the repositories root path from database
51 """Get's the repositories root path from database
52 """
52 """
53
53
54 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
54 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
55 return q.ui_value
55 return q.ui_value
56
56
57 def get(self, repo_id, cache=False):
57 def get(self, repo_id, cache=False):
58 repo = self.sa.query(Repository)\
58 repo = self.sa.query(Repository)\
59 .filter(Repository.repo_id == repo_id)
59 .filter(Repository.repo_id == repo_id)
60
60
61 if cache:
61 if cache:
62 repo = repo.options(FromCache("sql_cache_short",
62 repo = repo.options(FromCache("sql_cache_short",
63 "get_repo_%s" % repo_id))
63 "get_repo_%s" % repo_id))
64 return repo.scalar()
64 return repo.scalar()
65
65
66 def get_by_repo_name(self, repo_name, cache=False):
66 def get_by_repo_name(self, repo_name, cache=False):
67 repo = self.sa.query(Repository)\
67 repo = self.sa.query(Repository)\
68 .filter(Repository.repo_name == repo_name)
68 .filter(Repository.repo_name == repo_name)
69
69
70 if cache:
70 if cache:
71 repo = repo.options(FromCache("sql_cache_short",
71 repo = repo.options(FromCache("sql_cache_short",
72 "get_repo_%s" % repo_name))
72 "get_repo_%s" % repo_name))
73 return repo.scalar()
73 return repo.scalar()
74
74
75
75
76 def get_users_js(self):
76 def get_users_js(self):
77
77
78 users = self.sa.query(User).filter(User.active == True).all()
78 users = self.sa.query(User).filter(User.active == True).all()
79 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
79 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
80 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
80 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
81 u.lastname, u.username)
81 u.lastname, u.username)
82 for u in users])
82 for u in users])
83 return users_array
83 return users_array
84
84
85 def get_users_groups_js(self):
85 def get_users_groups_js(self):
86 users_groups = self.sa.query(UsersGroup)\
86 users_groups = self.sa.query(UsersGroup)\
87 .filter(UsersGroup.users_group_active == True).all()
87 .filter(UsersGroup.users_group_active == True).all()
88
88
89 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
89 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
90
90
91 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
91 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
92 (gr.users_group_id, gr.users_group_name,
92 (gr.users_group_id, gr.users_group_name,
93 len(gr.members))
93 len(gr.members))
94 for gr in users_groups])
94 for gr in users_groups])
95 return users_groups_array
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 def update(self, repo_name, form_data):
137 def update(self, repo_name, form_data):
98 try:
138 try:
99 cur_repo = self.get_by_repo_name(repo_name, cache=False)
139 cur_repo = self.get_by_repo_name(repo_name, cache=False)
100
140
101 # update permissions
141 # update permissions
102 for member, perm, member_type in form_data['perms_updates']:
142 for member, perm, member_type in form_data['perms_updates']:
103 if member_type == 'user':
143 if member_type == 'user':
104 r2p = self.sa.query(RepoToPerm)\
144 r2p = self.sa.query(RepoToPerm)\
105 .filter(RepoToPerm.user == User.get_by_username(member))\
145 .filter(RepoToPerm.user == User.get_by_username(member))\
106 .filter(RepoToPerm.repository == cur_repo)\
146 .filter(RepoToPerm.repository == cur_repo)\
107 .one()
147 .one()
108
148
109 r2p.permission = self.sa.query(Permission)\
149 r2p.permission = self.sa.query(Permission)\
110 .filter(Permission.permission_name ==
150 .filter(Permission.permission_name ==
111 perm).scalar()
151 perm).scalar()
112 self.sa.add(r2p)
152 self.sa.add(r2p)
113 else:
153 else:
114 g2p = self.sa.query(UsersGroupRepoToPerm)\
154 g2p = self.sa.query(UsersGroupRepoToPerm)\
115 .filter(UsersGroupRepoToPerm.users_group ==
155 .filter(UsersGroupRepoToPerm.users_group ==
116 UsersGroup.get_by_group_name(member))\
156 UsersGroup.get_by_group_name(member))\
117 .filter(UsersGroupRepoToPerm.repository ==
157 .filter(UsersGroupRepoToPerm.repository ==
118 cur_repo).one()
158 cur_repo).one()
119
159
120 g2p.permission = self.sa.query(Permission)\
160 g2p.permission = self.sa.query(Permission)\
121 .filter(Permission.permission_name ==
161 .filter(Permission.permission_name ==
122 perm).scalar()
162 perm).scalar()
123 self.sa.add(g2p)
163 self.sa.add(g2p)
124
164
125 # set new permissions
165 # set new permissions
126 for member, perm, member_type in form_data['perms_new']:
166 for member, perm, member_type in form_data['perms_new']:
127 if member_type == 'user':
167 if member_type == 'user':
128 r2p = RepoToPerm()
168 r2p = RepoToPerm()
129 r2p.repository = cur_repo
169 r2p.repository = cur_repo
130 r2p.user = User.get_by_username(member)
170 r2p.user = User.get_by_username(member)
131
171
132 r2p.permission = self.sa.query(Permission)\
172 r2p.permission = self.sa.query(Permission)\
133 .filter(Permission.
173 .filter(Permission.
134 permission_name == perm)\
174 permission_name == perm)\
135 .scalar()
175 .scalar()
136 self.sa.add(r2p)
176 self.sa.add(r2p)
137 else:
177 else:
138 g2p = UsersGroupRepoToPerm()
178 g2p = UsersGroupRepoToPerm()
139 g2p.repository = cur_repo
179 g2p.repository = cur_repo
140 g2p.users_group = UsersGroup.get_by_group_name(member)
180 g2p.users_group = UsersGroup.get_by_group_name(member)
141 g2p.permission = self.sa.query(Permission)\
181 g2p.permission = self.sa.query(Permission)\
142 .filter(Permission.
182 .filter(Permission.
143 permission_name == perm)\
183 permission_name == perm)\
144 .scalar()
184 .scalar()
145 self.sa.add(g2p)
185 self.sa.add(g2p)
146
186
147 # update current repo
187 # update current repo
148 for k, v in form_data.items():
188 for k, v in form_data.items():
149 if k == 'user':
189 if k == 'user':
150 cur_repo.user = User.get_by_username(v)
190 cur_repo.user = User.get_by_username(v)
151 elif k == 'repo_name':
191 elif k == 'repo_name':
152 pass
192 pass
153 elif k == 'repo_group':
193 elif k == 'repo_group':
154 cur_repo.group_id = v
194 cur_repo.group = Group.get(v)
155
195
156 else:
196 else:
157 setattr(cur_repo, k, v)
197 setattr(cur_repo, k, v)
158
198
159 new_name = cur_repo.get_new_name(form_data['repo_name'])
199 new_name = cur_repo.get_new_name(form_data['repo_name'])
160 cur_repo.repo_name = new_name
200 cur_repo.repo_name = new_name
161
201
162 self.sa.add(cur_repo)
202 self.sa.add(cur_repo)
163
203
164 if repo_name != new_name:
204 if repo_name != new_name:
165 # rename repository
205 # rename repository
166 self.__rename_repo(old=repo_name, new=new_name)
206 self.__rename_repo(old=repo_name, new=new_name)
167
207
168 self.sa.commit()
208 self.sa.commit()
169 return cur_repo
209 return cur_repo
170 except:
210 except:
171 log.error(traceback.format_exc())
211 log.error(traceback.format_exc())
172 self.sa.rollback()
212 self.sa.rollback()
173 raise
213 raise
174
214
175 def create(self, form_data, cur_user, just_db=False, fork=False):
215 def create(self, form_data, cur_user, just_db=False, fork=False):
176
216
177 try:
217 try:
178 if fork:
218 if fork:
179 repo_name = form_data['fork_name']
219 repo_name = form_data['fork_name']
180 org_name = form_data['repo_name']
220 org_name = form_data['repo_name']
181 org_full_name = org_name
221 org_full_name = org_name
182
222
183 else:
223 else:
184 org_name = repo_name = form_data['repo_name']
224 org_name = repo_name = form_data['repo_name']
185 repo_name_full = form_data['repo_name_full']
225 repo_name_full = form_data['repo_name_full']
186
226
187 new_repo = Repository()
227 new_repo = Repository()
188 new_repo.enable_statistics = False
228 new_repo.enable_statistics = False
189 for k, v in form_data.items():
229 for k, v in form_data.items():
190 if k == 'repo_name':
230 if k == 'repo_name':
191 if fork:
231 if fork:
192 v = repo_name
232 v = repo_name
193 else:
233 else:
194 v = repo_name_full
234 v = repo_name_full
195 if k == 'repo_group':
235 if k == 'repo_group':
196 k = 'group_id'
236 k = 'group_id'
197
237
198 if k == 'description':
238 if k == 'description':
199 v = v or repo_name
239 v = v or repo_name
200
240
201 setattr(new_repo, k, v)
241 setattr(new_repo, k, v)
202
242
203 if fork:
243 if fork:
204 parent_repo = self.sa.query(Repository)\
244 parent_repo = self.sa.query(Repository)\
205 .filter(Repository.repo_name == org_full_name).one()
245 .filter(Repository.repo_name == org_full_name).one()
206 new_repo.fork = parent_repo
246 new_repo.fork = parent_repo
207
247
208 new_repo.user_id = cur_user.user_id
248 new_repo.user_id = cur_user.user_id
209 self.sa.add(new_repo)
249 self.sa.add(new_repo)
210
250
211 #create default permission
251 #create default permission
212 repo_to_perm = RepoToPerm()
252 repo_to_perm = RepoToPerm()
213 default = 'repository.read'
253 default = 'repository.read'
214 for p in User.get_by_username('default').user_perms:
254 for p in User.get_by_username('default').user_perms:
215 if p.permission.permission_name.startswith('repository.'):
255 if p.permission.permission_name.startswith('repository.'):
216 default = p.permission.permission_name
256 default = p.permission.permission_name
217 break
257 break
218
258
219 default_perm = 'repository.none' if form_data['private'] else default
259 default_perm = 'repository.none' if form_data['private'] else default
220
260
221 repo_to_perm.permission_id = self.sa.query(Permission)\
261 repo_to_perm.permission_id = self.sa.query(Permission)\
222 .filter(Permission.permission_name == default_perm)\
262 .filter(Permission.permission_name == default_perm)\
223 .one().permission_id
263 .one().permission_id
224
264
225 repo_to_perm.repository = new_repo
265 repo_to_perm.repository = new_repo
226 repo_to_perm.user_id = User.get_by_username('default').user_id
266 repo_to_perm.user_id = User.get_by_username('default').user_id
227
267
228 self.sa.add(repo_to_perm)
268 self.sa.add(repo_to_perm)
229
269
230 if not just_db:
270 if not just_db:
231 self.__create_repo(repo_name, form_data['repo_type'],
271 self.__create_repo(repo_name, form_data['repo_type'],
232 form_data['repo_group'],
272 form_data['repo_group'],
233 form_data['clone_uri'])
273 form_data['clone_uri'])
234
274
235 self.sa.commit()
275 self.sa.commit()
236
276
237 #now automatically start following this repository as owner
277 #now automatically start following this repository as owner
238 from rhodecode.model.scm import ScmModel
278 from rhodecode.model.scm import ScmModel
239 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
279 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
240 cur_user.user_id)
280 cur_user.user_id)
241 return new_repo
281 return new_repo
242 except:
282 except:
243 log.error(traceback.format_exc())
283 log.error(traceback.format_exc())
244 self.sa.rollback()
284 self.sa.rollback()
245 raise
285 raise
246
286
247 def create_fork(self, form_data, cur_user):
287 def create_fork(self, form_data, cur_user):
248 from rhodecode.lib.celerylib import tasks, run_task
288 from rhodecode.lib.celerylib import tasks, run_task
249 run_task(tasks.create_repo_fork, form_data, cur_user)
289 run_task(tasks.create_repo_fork, form_data, cur_user)
250
290
251 def delete(self, repo):
291 def delete(self, repo):
252 try:
292 try:
253 self.sa.delete(repo)
293 self.sa.delete(repo)
254 self.__delete_repo(repo)
294 self.__delete_repo(repo)
255 self.sa.commit()
295 self.sa.commit()
256 except:
296 except:
257 log.error(traceback.format_exc())
297 log.error(traceback.format_exc())
258 self.sa.rollback()
298 self.sa.rollback()
259 raise
299 raise
260
300
261 def delete_perm_user(self, form_data, repo_name):
301 def delete_perm_user(self, form_data, repo_name):
262 try:
302 try:
263 self.sa.query(RepoToPerm)\
303 self.sa.query(RepoToPerm)\
264 .filter(RepoToPerm.repository \
304 .filter(RepoToPerm.repository \
265 == self.get_by_repo_name(repo_name))\
305 == self.get_by_repo_name(repo_name))\
266 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
306 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
267 self.sa.commit()
307 self.sa.commit()
268 except:
308 except:
269 log.error(traceback.format_exc())
309 log.error(traceback.format_exc())
270 self.sa.rollback()
310 self.sa.rollback()
271 raise
311 raise
272
312
273 def delete_perm_users_group(self, form_data, repo_name):
313 def delete_perm_users_group(self, form_data, repo_name):
274 try:
314 try:
275 self.sa.query(UsersGroupRepoToPerm)\
315 self.sa.query(UsersGroupRepoToPerm)\
276 .filter(UsersGroupRepoToPerm.repository \
316 .filter(UsersGroupRepoToPerm.repository \
277 == self.get_by_repo_name(repo_name))\
317 == self.get_by_repo_name(repo_name))\
278 .filter(UsersGroupRepoToPerm.users_group_id \
318 .filter(UsersGroupRepoToPerm.users_group_id \
279 == form_data['users_group_id']).delete()
319 == form_data['users_group_id']).delete()
280 self.sa.commit()
320 self.sa.commit()
281 except:
321 except:
282 log.error(traceback.format_exc())
322 log.error(traceback.format_exc())
283 self.sa.rollback()
323 self.sa.rollback()
284 raise
324 raise
285
325
286 def delete_stats(self, repo_name):
326 def delete_stats(self, repo_name):
287 try:
327 try:
288 self.sa.query(Statistics)\
328 self.sa.query(Statistics)\
289 .filter(Statistics.repository == \
329 .filter(Statistics.repository == \
290 self.get_by_repo_name(repo_name)).delete()
330 self.get_by_repo_name(repo_name)).delete()
291 self.sa.commit()
331 self.sa.commit()
292 except:
332 except:
293 log.error(traceback.format_exc())
333 log.error(traceback.format_exc())
294 self.sa.rollback()
334 self.sa.rollback()
295 raise
335 raise
296
336
297 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
337 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
298 """
338 """
299 makes repository on filesystem. It's group aware means it'll create
339 makes repository on filesystem. It's group aware means it'll create
300 a repository within a group, and alter the paths accordingly of
340 a repository within a group, and alter the paths accordingly of
301 group location
341 group location
302
342
303 :param repo_name:
343 :param repo_name:
304 :param alias:
344 :param alias:
305 :param parent_id:
345 :param parent_id:
306 :param clone_uri:
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 if new_parent_id:
350 if new_parent_id:
311 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
351 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
312 new_parent_path = os.sep.join(paths)
352 new_parent_path = os.sep.join(paths)
313 else:
353 else:
314 new_parent_path = ''
354 new_parent_path = ''
315
355
316 repo_path = os.path.join(*map(lambda x:safe_str(x),
356 repo_path = os.path.join(*map(lambda x:safe_str(x),
317 [self.repos_path, new_parent_path, repo_name]))
357 [self.repos_path, new_parent_path, repo_name]))
318
358
319
359
320 # check if this path is not a repository
360 # check if this path is not a repository
321 if is_valid_repo(repo_path, self.repos_path):
361 if is_valid_repo(repo_path, self.repos_path):
322 raise Exception('This path %s is a valid repository' % repo_path)
362 raise Exception('This path %s is a valid repository' % repo_path)
323
363
324 # check if this path is a group
364 # check if this path is a group
325 if is_valid_repos_group(repo_path, self.repos_path):
365 if is_valid_repos_group(repo_path, self.repos_path):
326 raise Exception('This path %s is a valid group' % repo_path)
366 raise Exception('This path %s is a valid group' % repo_path)
327
367
328 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
368 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
329 clone_uri)
369 clone_uri)
330 backend = get_backend(alias)
370 backend = get_backend(alias)
331
371
332 backend(repo_path, create=True, src_url=clone_uri)
372 backend(repo_path, create=True, src_url=clone_uri)
333
373
334
374
335 def __rename_repo(self, old, new):
375 def __rename_repo(self, old, new):
336 """
376 """
337 renames repository on filesystem
377 renames repository on filesystem
338
378
339 :param old: old name
379 :param old: old name
340 :param new: new name
380 :param new: new name
341 """
381 """
342 log.info('renaming repo from %s to %s', old, new)
382 log.info('renaming repo from %s to %s', old, new)
343
383
344 old_path = os.path.join(self.repos_path, old)
384 old_path = os.path.join(self.repos_path, old)
345 new_path = os.path.join(self.repos_path, new)
385 new_path = os.path.join(self.repos_path, new)
346 if os.path.isdir(new_path):
386 if os.path.isdir(new_path):
347 raise Exception('Was trying to rename to already existing dir %s' \
387 raise Exception('Was trying to rename to already existing dir %s' \
348 % new_path)
388 % new_path)
349 shutil.move(old_path, new_path)
389 shutil.move(old_path, new_path)
350
390
351 def __delete_repo(self, repo):
391 def __delete_repo(self, repo):
352 """
392 """
353 removes repo from filesystem, the removal is acctually made by
393 removes repo from filesystem, the removal is acctually made by
354 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
394 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
355 repository is no longer valid for rhodecode, can be undeleted later on
395 repository is no longer valid for rhodecode, can be undeleted later on
356 by reverting the renames on this repository
396 by reverting the renames on this repository
357
397
358 :param repo: repo object
398 :param repo: repo object
359 """
399 """
360 rm_path = os.path.join(self.repos_path, repo.repo_name)
400 rm_path = os.path.join(self.repos_path, repo.repo_name)
361 log.info("Removing %s", rm_path)
401 log.info("Removing %s", rm_path)
362 #disable hg/git
402 #disable hg/git
363 alias = repo.repo_type
403 alias = repo.repo_type
364 shutil.move(os.path.join(rm_path, '.%s' % alias),
404 shutil.move(os.path.join(rm_path, '.%s' % alias),
365 os.path.join(rm_path, 'rm__.%s' % alias))
405 os.path.join(rm_path, 'rm__.%s' % alias))
366 #disable repo
406 #disable repo
367 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
407 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
368 % (datetime.today()\
408 % (datetime.today()\
369 .strftime('%Y%m%d_%H%M%S_%f'),
409 .strftime('%Y%m%d_%H%M%S_%f'),
370 repo.repo_name)))
410 repo.repo_name)))
411
@@ -1,164 +1,164 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user_group
3 rhodecode.model.user_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 users groups model for RhodeCode
6 users groups model for RhodeCode
7
7
8 :created_on: Jan 25, 2011
8 :created_on: Jan 25, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import shutil
29 import shutil
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from vcs.utils.lazy import LazyProperty
33 from vcs.utils.lazy import LazyProperty
34
34
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.caching_query import FromCache
36 from rhodecode.model.caching_query import FromCache
37 from rhodecode.model.db import Group, RhodeCodeUi
37 from rhodecode.model.db import Group, RhodeCodeUi
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class ReposGroupModel(BaseModel):
42 class ReposGroupModel(BaseModel):
43
43
44 @LazyProperty
44 @LazyProperty
45 def repos_path(self):
45 def repos_path(self):
46 """
46 """
47 Get's the repositories root path from database
47 Get's the repositories root path from database
48 """
48 """
49
49
50 q = RhodeCodeUi.get_by_key('/').one()
50 q = RhodeCodeUi.get_by_key('/').one()
51 return q.ui_value
51 return q.ui_value
52
52
53 def __create_group(self, group_name):
53 def __create_group(self, group_name):
54 """
54 """
55 makes repositories group on filesystem
55 makes repositories group on filesystem
56
56
57 :param repo_name:
57 :param repo_name:
58 :param parent_id:
58 :param parent_id:
59 """
59 """
60
60
61 create_path = os.path.join(self.repos_path, group_name)
61 create_path = os.path.join(self.repos_path, group_name)
62 log.debug('creating new group in %s', create_path)
62 log.debug('creating new group in %s', create_path)
63
63
64 if os.path.isdir(create_path):
64 if os.path.isdir(create_path):
65 raise Exception('That directory already exists !')
65 raise Exception('That directory already exists !')
66
66
67 os.makedirs(create_path)
67 os.makedirs(create_path)
68
68
69 def __rename_group(self, old, new):
69 def __rename_group(self, old, new):
70 """
70 """
71 Renames a group on filesystem
71 Renames a group on filesystem
72
72
73 :param group_name:
73 :param group_name:
74 """
74 """
75
75
76 if old == new:
76 if old == new:
77 log.debug('skipping group rename')
77 log.debug('skipping group rename')
78 return
78 return
79
79
80 log.debug('renaming repos group from %s to %s', old, new)
80 log.debug('renaming repos group from %s to %s', old, new)
81
81
82
82
83 old_path = os.path.join(self.repos_path, old)
83 old_path = os.path.join(self.repos_path, old)
84 new_path = os.path.join(self.repos_path, new)
84 new_path = os.path.join(self.repos_path, new)
85
85
86 log.debug('renaming repos paths from %s to %s', old_path, new_path)
86 log.debug('renaming repos paths from %s to %s', old_path, new_path)
87
87
88 if os.path.isdir(new_path):
88 if os.path.isdir(new_path):
89 raise Exception('Was trying to rename to already '
89 raise Exception('Was trying to rename to already '
90 'existing dir %s' % new_path)
90 'existing dir %s' % new_path)
91 shutil.move(old_path, new_path)
91 shutil.move(old_path, new_path)
92
92
93 def __delete_group(self, group):
93 def __delete_group(self, group):
94 """
94 """
95 Deletes a group from a filesystem
95 Deletes a group from a filesystem
96
96
97 :param group: instance of group from database
97 :param group: instance of group from database
98 """
98 """
99 paths = group.full_path.split(Group.url_sep())
99 paths = group.full_path.split(Group.url_sep())
100 paths = os.sep.join(paths)
100 paths = os.sep.join(paths)
101
101
102 rm_path = os.path.join(self.repos_path, paths)
102 rm_path = os.path.join(self.repos_path, paths)
103 if os.path.isdir(rm_path):
103 if os.path.isdir(rm_path):
104 # delete only if that path really exists
104 # delete only if that path really exists
105 os.rmdir(rm_path)
105 os.rmdir(rm_path)
106
106
107 def create(self, form_data):
107 def create(self, form_data):
108 try:
108 try:
109 new_repos_group = Group()
109 new_repos_group = Group()
110 new_repos_group.group_description = form_data['group_description']
110 new_repos_group.group_description = form_data['group_description']
111 new_repos_group.parent_group = Group.get(form_data['group_parent_id'])
111 new_repos_group.parent_group = Group.get(form_data['group_parent_id'])
112 new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name'])
112 new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name'])
113
113
114 self.sa.add(new_repos_group)
114 self.sa.add(new_repos_group)
115
115
116 self.__create_group(new_repos_group.group_name)
116 self.__create_group(new_repos_group.group_name)
117
117
118 self.sa.commit()
118 self.sa.commit()
119 return new_repos_group
119 return new_repos_group
120 except:
120 except:
121 log.error(traceback.format_exc())
121 log.error(traceback.format_exc())
122 self.sa.rollback()
122 self.sa.rollback()
123 raise
123 raise
124
124
125 def update(self, repos_group_id, form_data):
125 def update(self, repos_group_id, form_data):
126
126
127 try:
127 try:
128 repos_group = Group.get(repos_group_id)
128 repos_group = Group.get(repos_group_id)
129 old_path = repos_group.full_path
129 old_path = repos_group.full_path
130
130
131 #change properties
131 # change properties
132 repos_group.group_description = form_data['group_description']
132 repos_group.group_description = form_data['group_description']
133 repos_group.parent_group = Group.get(form_data['group_parent_id'])
133 repos_group.parent_group = Group.get(form_data['group_parent_id'])
134 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
134 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
135
135
136 new_path = repos_group.full_path
136 new_path = repos_group.full_path
137
137
138 self.sa.add(repos_group)
138 self.sa.add(repos_group)
139
139
140 self.__rename_group(old_path, new_path)
140 self.__rename_group(old_path, new_path)
141
141
142 # we need to get all repositories from this new group and
142 # we need to get all repositories from this new group and
143 # rename them accordingly to new group path
143 # rename them accordingly to new group path
144 for r in repos_group.repositories:
144 for r in repos_group.repositories:
145 r.repo_name = r.get_new_name(r.just_name)
145 r.repo_name = r.get_new_name(r.just_name)
146 self.sa.add(r)
146 self.sa.add(r)
147
147
148 self.sa.commit()
148 self.sa.commit()
149 return repos_group
149 return repos_group
150 except:
150 except:
151 log.error(traceback.format_exc())
151 log.error(traceback.format_exc())
152 self.sa.rollback()
152 self.sa.rollback()
153 raise
153 raise
154
154
155 def delete(self, users_group_id):
155 def delete(self, users_group_id):
156 try:
156 try:
157 users_group = Group.get(users_group_id)
157 users_group = Group.get(users_group_id)
158 self.sa.delete(users_group)
158 self.sa.delete(users_group)
159 self.__delete_group(users_group)
159 self.__delete_group(users_group)
160 self.sa.commit()
160 self.sa.commit()
161 except:
161 except:
162 log.error(traceback.format_exc())
162 log.error(traceback.format_exc())
163 self.sa.rollback()
163 self.sa.rollback()
164 raise
164 raise
@@ -1,388 +1,390 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user
3 rhodecode.model.user
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 users model for RhodeCode
6 users model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 from rhodecode.lib import safe_unicode
31 from rhodecode.lib import safe_unicode
32 from rhodecode.model import BaseModel
32 from rhodecode.model import BaseModel
33 from rhodecode.model.caching_query import FromCache
33 from rhodecode.model.caching_query import FromCache
34 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
34 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
35 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember
35 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember
36 from rhodecode.lib.exceptions import DefaultUserException, \
36 from rhodecode.lib.exceptions import DefaultUserException, \
37 UserOwnsReposException
37 UserOwnsReposException
38
38
39 from sqlalchemy.exc import DatabaseError
39 from sqlalchemy.exc import DatabaseError
40 from rhodecode.lib import generate_api_key
40 from rhodecode.lib import generate_api_key
41 from sqlalchemy.orm import joinedload
41 from sqlalchemy.orm import joinedload
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45 PERM_WEIGHTS = {'repository.none': 0,
45 PERM_WEIGHTS = {'repository.none': 0,
46 'repository.read': 1,
46 'repository.read': 1,
47 'repository.write': 3,
47 'repository.write': 3,
48 'repository.admin': 3}
48 'repository.admin': 3}
49
49
50
50
51 class UserModel(BaseModel):
51 class UserModel(BaseModel):
52
53 def get(self, user_id, cache=False):
52 def get(self, user_id, cache=False):
54 user = self.sa.query(User)
53 user = self.sa.query(User)
55 if cache:
54 if cache:
56 user = user.options(FromCache("sql_cache_short",
55 user = user.options(FromCache("sql_cache_short",
57 "get_user_%s" % user_id))
56 "get_user_%s" % user_id))
58 return user.get(user_id)
57 return user.get(user_id)
59
58
60 def get_by_username(self, username, cache=False, case_insensitive=False):
59 def get_by_username(self, username, cache=False, case_insensitive=False):
61
60
62 if case_insensitive:
61 if case_insensitive:
63 user = self.sa.query(User).filter(User.username.ilike(username))
62 user = self.sa.query(User).filter(User.username.ilike(username))
64 else:
63 else:
65 user = self.sa.query(User)\
64 user = self.sa.query(User)\
66 .filter(User.username == username)
65 .filter(User.username == username)
67 if cache:
66 if cache:
68 user = user.options(FromCache("sql_cache_short",
67 user = user.options(FromCache("sql_cache_short",
69 "get_user_%s" % username))
68 "get_user_%s" % username))
70 return user.scalar()
69 return user.scalar()
71
70
72 def get_by_api_key(self, api_key, cache=False):
71 def get_by_api_key(self, api_key, cache=False):
73
72
74 user = self.sa.query(User)\
73 user = self.sa.query(User)\
75 .filter(User.api_key == api_key)
74 .filter(User.api_key == api_key)
76 if cache:
75 if cache:
77 user = user.options(FromCache("sql_cache_short",
76 user = user.options(FromCache("sql_cache_short",
78 "get_user_%s" % api_key))
77 "get_user_%s" % api_key))
79 return user.scalar()
78 return user.scalar()
80
79
81 def create(self, form_data):
80 def create(self, form_data):
82 try:
81 try:
83 new_user = User()
82 new_user = User()
84 for k, v in form_data.items():
83 for k, v in form_data.items():
85 setattr(new_user, k, v)
84 setattr(new_user, k, v)
86
85
87 new_user.api_key = generate_api_key(form_data['username'])
86 new_user.api_key = generate_api_key(form_data['username'])
88 self.sa.add(new_user)
87 self.sa.add(new_user)
89 self.sa.commit()
88 self.sa.commit()
89 return new_user
90 except:
90 except:
91 log.error(traceback.format_exc())
91 log.error(traceback.format_exc())
92 self.sa.rollback()
92 self.sa.rollback()
93 raise
93 raise
94
94
95 def create_ldap(self, username, password, user_dn, attrs):
95 def create_ldap(self, username, password, user_dn, attrs):
96 """
96 """
97 Checks if user is in database, if not creates this user marked
97 Checks if user is in database, if not creates this user marked
98 as ldap user
98 as ldap user
99
99 :param username:
100 :param username:
100 :param password:
101 :param password:
101 :param user_dn:
102 :param user_dn:
102 :param attrs:
103 :param attrs:
103 """
104 """
104 from rhodecode.lib.auth import get_crypt_password
105 from rhodecode.lib.auth import get_crypt_password
105 log.debug('Checking for such ldap account in RhodeCode database')
106 log.debug('Checking for such ldap account in RhodeCode database')
106 if self.get_by_username(username, case_insensitive=True) is None:
107 if self.get_by_username(username, case_insensitive=True) is None:
107 try:
108 try:
108 new_user = User()
109 new_user = User()
109 # add ldap account always lowercase
110 # add ldap account always lowercase
110 new_user.username = username.lower()
111 new_user.username = username.lower()
111 new_user.password = get_crypt_password(password)
112 new_user.password = get_crypt_password(password)
112 new_user.api_key = generate_api_key(username)
113 new_user.api_key = generate_api_key(username)
113 new_user.email = attrs['email']
114 new_user.email = attrs['email']
114 new_user.active = True
115 new_user.active = True
115 new_user.ldap_dn = safe_unicode(user_dn)
116 new_user.ldap_dn = safe_unicode(user_dn)
116 new_user.name = attrs['name']
117 new_user.name = attrs['name']
117 new_user.lastname = attrs['lastname']
118 new_user.lastname = attrs['lastname']
118
119
119 self.sa.add(new_user)
120 self.sa.add(new_user)
120 self.sa.commit()
121 self.sa.commit()
121 return True
122 return True
122 except (DatabaseError,):
123 except (DatabaseError,):
123 log.error(traceback.format_exc())
124 log.error(traceback.format_exc())
124 self.sa.rollback()
125 self.sa.rollback()
125 raise
126 raise
126 log.debug('this %s user exists skipping creation of ldap account',
127 log.debug('this %s user exists skipping creation of ldap account',
127 username)
128 username)
128 return False
129 return False
129
130
130 def create_registration(self, form_data):
131 def create_registration(self, form_data):
131 from rhodecode.lib.celerylib import tasks, run_task
132 from rhodecode.lib.celerylib import tasks, run_task
132 try:
133 try:
133 new_user = User()
134 new_user = User()
134 for k, v in form_data.items():
135 for k, v in form_data.items():
135 if k != 'admin':
136 if k != 'admin':
136 setattr(new_user, k, v)
137 setattr(new_user, k, v)
137
138
138 self.sa.add(new_user)
139 self.sa.add(new_user)
139 self.sa.commit()
140 self.sa.commit()
140 body = ('New user registration\n'
141 body = ('New user registration\n'
141 'username: %s\n'
142 'username: %s\n'
142 'email: %s\n')
143 'email: %s\n')
143 body = body % (form_data['username'], form_data['email'])
144 body = body % (form_data['username'], form_data['email'])
144
145
145 run_task(tasks.send_email, None,
146 run_task(tasks.send_email, None,
146 _('[RhodeCode] New User registration'),
147 _('[RhodeCode] New User registration'),
147 body)
148 body)
148 except:
149 except:
149 log.error(traceback.format_exc())
150 log.error(traceback.format_exc())
150 self.sa.rollback()
151 self.sa.rollback()
151 raise
152 raise
152
153
153 def update(self, user_id, form_data):
154 def update(self, user_id, form_data):
154 try:
155 try:
155 user = self.get(user_id, cache=False)
156 user = self.get(user_id, cache=False)
156 if user.username == 'default':
157 if user.username == 'default':
157 raise DefaultUserException(
158 raise DefaultUserException(
158 _("You can't Edit this user since it's"
159 _("You can't Edit this user since it's"
159 " crucial for entire application"))
160 " crucial for entire application"))
160
161
161 for k, v in form_data.items():
162 for k, v in form_data.items():
162 if k == 'new_password' and v != '':
163 if k == 'new_password' and v != '':
163 user.password = v
164 user.password = v
164 user.api_key = generate_api_key(user.username)
165 user.api_key = generate_api_key(user.username)
165 else:
166 else:
166 setattr(user, k, v)
167 setattr(user, k, v)
167
168
168 self.sa.add(user)
169 self.sa.add(user)
169 self.sa.commit()
170 self.sa.commit()
170 except:
171 except:
171 log.error(traceback.format_exc())
172 log.error(traceback.format_exc())
172 self.sa.rollback()
173 self.sa.rollback()
173 raise
174 raise
174
175
175 def update_my_account(self, user_id, form_data):
176 def update_my_account(self, user_id, form_data):
176 try:
177 try:
177 user = self.get(user_id, cache=False)
178 user = self.get(user_id, cache=False)
178 if user.username == 'default':
179 if user.username == 'default':
179 raise DefaultUserException(
180 raise DefaultUserException(
180 _("You can't Edit this user since it's"
181 _("You can't Edit this user since it's"
181 " crucial for entire application"))
182 " crucial for entire application"))
182 for k, v in form_data.items():
183 for k, v in form_data.items():
183 if k == 'new_password' and v != '':
184 if k == 'new_password' and v != '':
184 user.password = v
185 user.password = v
185 user.api_key = generate_api_key(user.username)
186 user.api_key = generate_api_key(user.username)
186 else:
187 else:
187 if k not in ['admin', 'active']:
188 if k not in ['admin', 'active']:
188 setattr(user, k, v)
189 setattr(user, k, v)
189
190
190 self.sa.add(user)
191 self.sa.add(user)
191 self.sa.commit()
192 self.sa.commit()
192 except:
193 except:
193 log.error(traceback.format_exc())
194 log.error(traceback.format_exc())
194 self.sa.rollback()
195 self.sa.rollback()
195 raise
196 raise
196
197
197 def delete(self, user_id):
198 def delete(self, user_id):
198 try:
199 try:
199 user = self.get(user_id, cache=False)
200 user = self.get(user_id, cache=False)
200 if user.username == 'default':
201 if user.username == 'default':
201 raise DefaultUserException(
202 raise DefaultUserException(
202 _("You can't remove this user since it's"
203 _("You can't remove this user since it's"
203 " crucial for entire application"))
204 " crucial for entire application"))
204 if user.repositories:
205 if user.repositories:
205 raise UserOwnsReposException(_('This user still owns %s '
206 raise UserOwnsReposException(_('This user still owns %s '
206 'repositories and cannot be '
207 'repositories and cannot be '
207 'removed. Switch owners or '
208 'removed. Switch owners or '
208 'remove those repositories') \
209 'remove those repositories') \
209 % user.repositories)
210 % user.repositories)
210 self.sa.delete(user)
211 self.sa.delete(user)
211 self.sa.commit()
212 self.sa.commit()
212 except:
213 except:
213 log.error(traceback.format_exc())
214 log.error(traceback.format_exc())
214 self.sa.rollback()
215 self.sa.rollback()
215 raise
216 raise
216
217
217 def reset_password_link(self, data):
218 def reset_password_link(self, data):
218 from rhodecode.lib.celerylib import tasks, run_task
219 from rhodecode.lib.celerylib import tasks, run_task
219 run_task(tasks.send_password_link, data['email'])
220 run_task(tasks.send_password_link, data['email'])
220
221
221 def reset_password(self, data):
222 def reset_password(self, data):
222 from rhodecode.lib.celerylib import tasks, run_task
223 from rhodecode.lib.celerylib import tasks, run_task
223 run_task(tasks.reset_user_password, data['email'])
224 run_task(tasks.reset_user_password, data['email'])
224
225
225 def fill_data(self, auth_user, user_id=None, api_key=None):
226 def fill_data(self, auth_user, user_id=None, api_key=None):
226 """
227 """
227 Fetches auth_user by user_id,or api_key if present.
228 Fetches auth_user by user_id,or api_key if present.
228 Fills auth_user attributes with those taken from database.
229 Fills auth_user attributes with those taken from database.
229 Additionally set's is_authenitated if lookup fails
230 Additionally set's is_authenitated if lookup fails
230 present in database
231 present in database
231
232
232 :param auth_user: instance of user to set attributes
233 :param auth_user: instance of user to set attributes
233 :param user_id: user id to fetch by
234 :param user_id: user id to fetch by
234 :param api_key: api key to fetch by
235 :param api_key: api key to fetch by
235 """
236 """
236 if user_id is None and api_key is None:
237 if user_id is None and api_key is None:
237 raise Exception('You need to pass user_id or api_key')
238 raise Exception('You need to pass user_id or api_key')
238
239
239 try:
240 try:
240 if api_key:
241 if api_key:
241 dbuser = self.get_by_api_key(api_key)
242 dbuser = self.get_by_api_key(api_key)
242 else:
243 else:
243 dbuser = self.get(user_id)
244 dbuser = self.get(user_id)
244
245
245 if dbuser is not None:
246 if dbuser is not None:
246 log.debug('filling %s data', dbuser)
247 log.debug('filling %s data', dbuser)
247 for k, v in dbuser.get_dict().items():
248 for k, v in dbuser.get_dict().items():
248 setattr(auth_user, k, v)
249 setattr(auth_user, k, v)
249
250
250 except:
251 except:
251 log.error(traceback.format_exc())
252 log.error(traceback.format_exc())
252 auth_user.is_authenticated = False
253 auth_user.is_authenticated = False
253
254
254 return auth_user
255 return auth_user
255
256
256 def fill_perms(self, user):
257 def fill_perms(self, user):
257 """
258 """
258 Fills user permission attribute with permissions taken from database
259 Fills user permission attribute with permissions taken from database
259 works for permissions given for repositories, and for permissions that
260 works for permissions given for repositories, and for permissions that
260 are granted to groups
261 are granted to groups
261
262
262 :param user: user instance to fill his perms
263 :param user: user instance to fill his perms
263 """
264 """
264
265
265 user.permissions['repositories'] = {}
266 user.permissions['repositories'] = {}
266 user.permissions['global'] = set()
267 user.permissions['global'] = set()
267
268
268 #======================================================================
269 #======================================================================
269 # fetch default permissions
270 # fetch default permissions
270 #======================================================================
271 #======================================================================
271 default_user = self.get_by_username('default', cache=True)
272 default_user = self.get_by_username('default', cache=True)
272
273
273 default_perms = self.sa.query(RepoToPerm, Repository, Permission)\
274 default_perms = self.sa.query(RepoToPerm, Repository, Permission)\
274 .join((Repository, RepoToPerm.repository_id ==
275 .join((Repository, RepoToPerm.repository_id ==
275 Repository.repo_id))\
276 Repository.repo_id))\
276 .join((Permission, RepoToPerm.permission_id ==
277 .join((Permission, RepoToPerm.permission_id ==
277 Permission.permission_id))\
278 Permission.permission_id))\
278 .filter(RepoToPerm.user == default_user).all()
279 .filter(RepoToPerm.user == default_user).all()
279
280
280 if user.is_admin:
281 if user.is_admin:
281 #==================================================================
282 #==================================================================
282 # #admin have all default rights set to admin
283 # #admin have all default rights set to admin
283 #==================================================================
284 #==================================================================
284 user.permissions['global'].add('hg.admin')
285 user.permissions['global'].add('hg.admin')
285
286
286 for perm in default_perms:
287 for perm in default_perms:
287 p = 'repository.admin'
288 p = 'repository.admin'
288 user.permissions['repositories'][perm.RepoToPerm.
289 user.permissions['repositories'][perm.RepoToPerm.
289 repository.repo_name] = p
290 repository.repo_name] = p
290
291
291 else:
292 else:
292 #==================================================================
293 #==================================================================
293 # set default permissions
294 # set default permissions
294 #==================================================================
295 #==================================================================
295 uid = user.user_id
296 uid = user.user_id
296
297
297 #default global
298 #default global
298 default_global_perms = self.sa.query(UserToPerm)\
299 default_global_perms = self.sa.query(UserToPerm)\
299 .filter(UserToPerm.user == default_user)
300 .filter(UserToPerm.user == default_user)
300
301
301 for perm in default_global_perms:
302 for perm in default_global_perms:
302 user.permissions['global'].add(perm.permission.permission_name)
303 user.permissions['global'].add(perm.permission.permission_name)
303
304
304 #default for repositories
305 #default for repositories
305 for perm in default_perms:
306 for perm in default_perms:
306 if perm.Repository.private and not (perm.Repository.user_id ==
307 if perm.Repository.private and not (perm.Repository.user_id ==
307 uid):
308 uid):
308 #diself.sable defaults for private repos,
309 #diself.sable defaults for private repos,
309 p = 'repository.none'
310 p = 'repository.none'
310 elif perm.Repository.user_id == uid:
311 elif perm.Repository.user_id == uid:
311 #set admin if owner
312 #set admin if owner
312 p = 'repository.admin'
313 p = 'repository.admin'
313 else:
314 else:
314 p = perm.Permission.permission_name
315 p = perm.Permission.permission_name
315
316
316 user.permissions['repositories'][perm.RepoToPerm.
317 user.permissions['repositories'][perm.RepoToPerm.
317 repository.repo_name] = p
318 repository.repo_name] = p
318
319
319 #==================================================================
320 #==================================================================
320 # overwrite default with user permissions if any
321 # overwrite default with user permissions if any
321 #==================================================================
322 #==================================================================
322
323
323 #user global
324 #user global
324 user_perms = self.sa.query(UserToPerm)\
325 user_perms = self.sa.query(UserToPerm)\
325 .options(joinedload(UserToPerm.permission))\
326 .options(joinedload(UserToPerm.permission))\
326 .filter(UserToPerm.user_id == uid).all()
327 .filter(UserToPerm.user_id == uid).all()
327
328
328 for perm in user_perms:
329 for perm in user_perms:
329 user.permissions['global'].add(perm.permission.
330 user.permissions['global'].add(perm.permission.
330 permission_name)
331 permission_name)
331
332
332 #user repositories
333 #user repositories
333 user_repo_perms = self.sa.query(RepoToPerm, Permission,
334 user_repo_perms = self.sa.query(RepoToPerm, Permission,
334 Repository)\
335 Repository)\
335 .join((Repository, RepoToPerm.repository_id ==
336 .join((Repository, RepoToPerm.repository_id ==
336 Repository.repo_id))\
337 Repository.repo_id))\
337 .join((Permission, RepoToPerm.permission_id ==
338 .join((Permission, RepoToPerm.permission_id ==
338 Permission.permission_id))\
339 Permission.permission_id))\
339 .filter(RepoToPerm.user_id == uid).all()
340 .filter(RepoToPerm.user_id == uid).all()
340
341
341 for perm in user_repo_perms:
342 for perm in user_repo_perms:
342 # set admin if owner
343 # set admin if owner
343 if perm.Repository.user_id == uid:
344 if perm.Repository.user_id == uid:
344 p = 'repository.admin'
345 p = 'repository.admin'
345 else:
346 else:
346 p = perm.Permission.permission_name
347 p = perm.Permission.permission_name
347 user.permissions['repositories'][perm.RepoToPerm.
348 user.permissions['repositories'][perm.RepoToPerm.
348 repository.repo_name] = p
349 repository.repo_name] = p
349
350
350 #==================================================================
351 #==================================================================
351 # check if user is part of groups for this repository and fill in
352 # check if user is part of groups for this repository and fill in
352 # (or replace with higher) permissions
353 # (or replace with higher) permissions
353 #==================================================================
354 #==================================================================
354
355
355 #users group global
356 #users group global
356 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
357 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
357 .options(joinedload(UsersGroupToPerm.permission))\
358 .options(joinedload(UsersGroupToPerm.permission))\
358 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
359 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
359 UsersGroupMember.users_group_id))\
360 UsersGroupMember.users_group_id))\
360 .filter(UsersGroupMember.user_id == uid).all()
361 .filter(UsersGroupMember.user_id == uid).all()
361
362
362 for perm in user_perms_from_users_groups:
363 for perm in user_perms_from_users_groups:
363 user.permissions['global'].add(perm.permission.permission_name)
364 user.permissions['global'].add(perm.permission.permission_name)
364
365
365 #users group repositories
366 #users group repositories
366 user_repo_perms_from_users_groups = self.sa.query(
367 user_repo_perms_from_users_groups = self.sa.query(
367 UsersGroupRepoToPerm,
368 UsersGroupRepoToPerm,
368 Permission, Repository,)\
369 Permission, Repository,)\
369 .join((Repository, UsersGroupRepoToPerm.repository_id ==
370 .join((Repository, UsersGroupRepoToPerm.repository_id ==
370 Repository.repo_id))\
371 Repository.repo_id))\
371 .join((Permission, UsersGroupRepoToPerm.permission_id ==
372 .join((Permission, UsersGroupRepoToPerm.permission_id ==
372 Permission.permission_id))\
373 Permission.permission_id))\
373 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
374 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
374 UsersGroupMember.users_group_id))\
375 UsersGroupMember.users_group_id))\
375 .filter(UsersGroupMember.user_id == uid).all()
376 .filter(UsersGroupMember.user_id == uid).all()
376
377
377 for perm in user_repo_perms_from_users_groups:
378 for perm in user_repo_perms_from_users_groups:
378 p = perm.Permission.permission_name
379 p = perm.Permission.permission_name
379 cur_perm = user.permissions['repositories'][perm.
380 cur_perm = user.permissions['repositories'][perm.
380 UsersGroupRepoToPerm.
381 UsersGroupRepoToPerm.
381 repository.repo_name]
382 repository.repo_name]
382 #overwrite permission only if it's greater than permission
383 #overwrite permission only if it's greater than permission
383 # given from other sources
384 # given from other sources
384 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
385 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
385 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
386 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
386 repository.repo_name] = p
387 repository.repo_name] = p
387
388
388 return user
389 return user
390
@@ -1,64 +1,64 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Add repos group')} - ${c.rhodecode_name}
5 ${_('Add repos group')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 &raquo;
11 &raquo;
12 ${_('add new repos group')}
12 ${_('add new repos group')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('admin')}
16 ${self.menu('admin')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 <!-- end box / title -->
25 <!-- end box / title -->
26 ${h.form(url('repos_groups'))}
26 ${h.form(url('repos_groups'))}
27 <div class="form">
27 <div class="form">
28 <!-- fields -->
28 <!-- fields -->
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="users_group_name">${_('Group name')}:</label>
32 <label for="group_name">${_('Group name')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('group_name',class_='medium')}
35 ${h.text('group_name',class_='medium')}
36 </div>
36 </div>
37 </div>
37 </div>
38
38
39 <div class="field">
39 <div class="field">
40 <div class="label label-textarea">
40 <div class="label label-textarea">
41 <label for="description">${_('Description')}:</label>
41 <label for="group_description">${_('Description')}:</label>
42 </div>
42 </div>
43 <div class="textarea text-area editor">
43 <div class="textarea text-area editor">
44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
45 </div>
45 </div>
46 </div>
46 </div>
47
47
48 <div class="field">
48 <div class="field">
49 <div class="label">
49 <div class="label">
50 <label for="repo_group">${_('Group parent')}:</label>
50 <label for="group_parent_id">${_('Group parent')}:</label>
51 </div>
51 </div>
52 <div class="input">
52 <div class="input">
53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 <div class="buttons">
57 <div class="buttons">
58 ${h.submit('save',_('save'),class_="ui-button")}
58 ${h.submit('save',_('save'),class_="ui-button")}
59 </div>
59 </div>
60 </div>
60 </div>
61 </div>
61 </div>
62 ${h.end_form()}
62 ${h.end_form()}
63 </div>
63 </div>
64 </%def>
64 </%def>
@@ -1,64 +1,64 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name}
5 ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 &raquo;
11 &raquo;
12 ${_('edit repos group')} "${c.repos_group.name}"
12 ${_('edit repos group')} "${c.repos_group.name}"
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('admin')}
16 ${self.menu('admin')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 <!-- end box / title -->
25 <!-- end box / title -->
26 ${h.form(url('repos_group',id=c.repos_group.group_id),method='put')}
26 ${h.form(url('repos_group',id=c.repos_group.group_id),method='put')}
27 <div class="form">
27 <div class="form">
28 <!-- fields -->
28 <!-- fields -->
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="users_group_name">${_('Group name')}:</label>
32 <label for="group_name">${_('Group name')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('group_name',class_='medium')}
35 ${h.text('group_name',class_='medium')}
36 </div>
36 </div>
37 </div>
37 </div>
38
38
39 <div class="field">
39 <div class="field">
40 <div class="label label-textarea">
40 <div class="label label-textarea">
41 <label for="description">${_('Description')}:</label>
41 <label for="group_description">${_('Description')}:</label>
42 </div>
42 </div>
43 <div class="textarea text-area editor">
43 <div class="textarea text-area editor">
44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
45 </div>
45 </div>
46 </div>
46 </div>
47
47
48 <div class="field">
48 <div class="field">
49 <div class="label">
49 <div class="label">
50 <label for="repo_group">${_('Group parent')}:</label>
50 <label for="group_parent_id">${_('Group parent')}:</label>
51 </div>
51 </div>
52 <div class="input">
52 <div class="input">
53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 <div class="buttons">
57 <div class="buttons">
58 ${h.submit('save',_('save'),class_="ui-button")}
58 ${h.submit('save',_('save'),class_="ui-button")}
59 </div>
59 </div>
60 </div>
60 </div>
61 </div>
61 </div>
62 ${h.end_form()}
62 ${h.end_form()}
63 </div>
63 </div>
64 </%def>
64 </%def>
@@ -1,192 +1,192 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Settings administration')} - ${c.rhodecode_name}
5 ${_('Settings administration')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
10 </%def>
10 </%def>
11
11
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="main()">
16 <%def name="main()">
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 <!-- end box / title -->
22 <!-- end box / title -->
23
23
24 <h3>${_('Remap and rescan repositories')}</h3>
24 <h3>${_('Remap and rescan repositories')}</h3>
25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 <div class="form">
26 <div class="form">
27 <!-- fields -->
27 <!-- fields -->
28
28
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label label-checkbox">
31 <div class="label label-checkbox">
32 <label for="destroy">${_('rescan option')}:</label>
32 <label for="destroy">${_('rescan option')}:</label>
33 </div>
33 </div>
34 <div class="checkboxes">
34 <div class="checkboxes">
35 <div class="checkbox">
35 <div class="checkbox">
36 ${h.checkbox('destroy',True)}
36 ${h.checkbox('destroy',True)}
37 <label for="checkbox-1">
37 <label for="destroy">
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.'))}">
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 ${_('destroy old data')}</span> </label>
39 ${_('destroy old data')}</span> </label>
40 </div>
40 </div>
41 </div>
41 </div>
42 </div>
42 </div>
43
43
44 <div class="buttons">
44 <div class="buttons">
45 ${h.submit('rescan',_('Rescan repositories'),class_="ui-button")}
45 ${h.submit('rescan',_('Rescan repositories'),class_="ui-button")}
46 </div>
46 </div>
47 </div>
47 </div>
48 </div>
48 </div>
49 ${h.end_form()}
49 ${h.end_form()}
50
50
51 <h3>${_('Whoosh indexing')}</h3>
51 <h3>${_('Whoosh indexing')}</h3>
52 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
52 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
53 <div class="form">
53 <div class="form">
54 <!-- fields -->
54 <!-- fields -->
55
55
56 <div class="fields">
56 <div class="fields">
57 <div class="field">
57 <div class="field">
58 <div class="label label-checkbox">
58 <div class="label label-checkbox">
59 <label for="destroy">${_('index build option')}:</label>
59 <label>${_('index build option')}:</label>
60 </div>
60 </div>
61 <div class="checkboxes">
61 <div class="checkboxes">
62 <div class="checkbox">
62 <div class="checkbox">
63 ${h.checkbox('full_index',True)}
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 </div>
65 </div>
66 </div>
66 </div>
67 </div>
67 </div>
68
68
69 <div class="buttons">
69 <div class="buttons">
70 ${h.submit('reindex',_('Reindex'),class_="ui-button")}
70 ${h.submit('reindex',_('Reindex'),class_="ui-button")}
71 </div>
71 </div>
72 </div>
72 </div>
73 </div>
73 </div>
74 ${h.end_form()}
74 ${h.end_form()}
75
75
76 <h3>${_('Global application settings')}</h3>
76 <h3>${_('Global application settings')}</h3>
77 ${h.form(url('admin_setting', setting_id='global'),method='put')}
77 ${h.form(url('admin_setting', setting_id='global'),method='put')}
78 <div class="form">
78 <div class="form">
79 <!-- fields -->
79 <!-- fields -->
80
80
81 <div class="fields">
81 <div class="fields">
82
82
83 <div class="field">
83 <div class="field">
84 <div class="label">
84 <div class="label">
85 <label for="rhodecode_title">${_('Application name')}:</label>
85 <label for="rhodecode_title">${_('Application name')}:</label>
86 </div>
86 </div>
87 <div class="input">
87 <div class="input">
88 ${h.text('rhodecode_title',size=30)}
88 ${h.text('rhodecode_title',size=30)}
89 </div>
89 </div>
90 </div>
90 </div>
91
91
92 <div class="field">
92 <div class="field">
93 <div class="label">
93 <div class="label">
94 <label for="rhodecode_realm">${_('Realm text')}:</label>
94 <label for="rhodecode_realm">${_('Realm text')}:</label>
95 </div>
95 </div>
96 <div class="input">
96 <div class="input">
97 ${h.text('rhodecode_realm',size=30)}
97 ${h.text('rhodecode_realm',size=30)}
98 </div>
98 </div>
99 </div>
99 </div>
100
100
101 <div class="field">
101 <div class="field">
102 <div class="label">
102 <div class="label">
103 <label for="ga_code">${_('GA code')}:</label>
103 <label for="rhodecode_ga_code">${_('GA code')}:</label>
104 </div>
104 </div>
105 <div class="input">
105 <div class="input">
106 ${h.text('rhodecode_ga_code',size=30)}
106 ${h.text('rhodecode_ga_code',size=30)}
107 </div>
107 </div>
108 </div>
108 </div>
109
109
110 <div class="buttons">
110 <div class="buttons">
111 ${h.submit('save',_('Save settings'),class_="ui-button")}
111 ${h.submit('save',_('Save settings'),class_="ui-button")}
112 ${h.reset('reset',_('Reset'),class_="ui-button")}
112 ${h.reset('reset',_('Reset'),class_="ui-button")}
113 </div>
113 </div>
114 </div>
114 </div>
115 </div>
115 </div>
116 ${h.end_form()}
116 ${h.end_form()}
117
117
118 <h3>${_('Mercurial settings')}</h3>
118 <h3>${_('Mercurial settings')}</h3>
119 ${h.form(url('admin_setting', setting_id='mercurial'),method='put')}
119 ${h.form(url('admin_setting', setting_id='mercurial'),method='put')}
120 <div class="form">
120 <div class="form">
121 <!-- fields -->
121 <!-- fields -->
122
122
123 <div class="fields">
123 <div class="fields">
124
124
125 <div class="field">
125 <div class="field">
126 <div class="label label-checkbox">
126 <div class="label label-checkbox">
127 <label for="web_push_ssl">${_('Web')}:</label>
127 <label>${_('Web')}:</label>
128 </div>
128 </div>
129 <div class="checkboxes">
129 <div class="checkboxes">
130 <div class="checkbox">
130 <div class="checkbox">
131 ${h.checkbox('web_push_ssl','true')}
131 ${h.checkbox('web_push_ssl','true')}
132 <label for="web_push_ssl">${_('require ssl for pushing')}</label>
132 <label for="web_push_ssl">${_('require ssl for pushing')}</label>
133 </div>
133 </div>
134 </div>
134 </div>
135 </div>
135 </div>
136
136
137 <div class="field">
137 <div class="field">
138 <div class="label label-checkbox">
138 <div class="label label-checkbox">
139 <label for="web_push_ssl">${_('Hooks')}:</label>
139 <label>${_('Hooks')}:</label>
140 </div>
140 </div>
141 <div class="input">
141 <div class="input">
142 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'))}
142 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'))}
143 </div>
143 </div>
144 <div class="checkboxes">
144 <div class="checkboxes">
145 <div class="checkbox">
145 <div class="checkbox">
146 ${h.checkbox('hooks_changegroup_update','True')}
146 ${h.checkbox('hooks_changegroup_update','True')}
147 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
147 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
148 </div>
148 </div>
149 <div class="checkbox">
149 <div class="checkbox">
150 ${h.checkbox('hooks_changegroup_repo_size','True')}
150 ${h.checkbox('hooks_changegroup_repo_size','True')}
151 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
151 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
152 </div>
152 </div>
153 <div class="checkbox">
153 <div class="checkbox">
154 ${h.checkbox('hooks_pretxnchangegroup_push_logger','True')}
154 ${h.checkbox('hooks_pretxnchangegroup_push_logger','True')}
155 <label for="hooks_pretxnchangegroup_push_logger">${_('Log user push commands')}</label>
155 <label for="hooks_pretxnchangegroup_push_logger">${_('Log user push commands')}</label>
156 </div>
156 </div>
157 <div class="checkbox">
157 <div class="checkbox">
158 ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
158 ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
159 <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
159 <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
160 </div>
160 </div>
161 </div>
161 </div>
162 </div>
162 </div>
163
163
164 <div class="field">
164 <div class="field">
165 <div class="label">
165 <div class="label">
166 <label for="paths_root_path">${_('Repositories location')}:</label>
166 <label for="paths_root_path">${_('Repositories location')}:</label>
167 </div>
167 </div>
168 <div class="input">
168 <div class="input">
169 ${h.text('paths_root_path',size=30,readonly="readonly")}
169 ${h.text('paths_root_path',size=30,readonly="readonly")}
170 <span id="path_unlock" class="tooltip"
170 <span id="path_unlock" class="tooltip"
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.'))}">
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 ${_('unlock')}</span>
172 ${_('unlock')}</span>
173 </div>
173 </div>
174 </div>
174 </div>
175
175
176 <div class="buttons">
176 <div class="buttons">
177 ${h.submit('save',_('Save settings'),class_="ui-button")}
177 ${h.submit('save',_('Save settings'),class_="ui-button")}
178 ${h.reset('reset',_('Reset'),class_="ui-button")}
178 ${h.reset('reset',_('Reset'),class_="ui-button")}
179 </div>
179 </div>
180 </div>
180 </div>
181 </div>
181 </div>
182 ${h.end_form()}
182 ${h.end_form()}
183
183
184 <script type="text/javascript">
184 <script type="text/javascript">
185 YAHOO.util.Event.onDOMReady(function(){
185 YAHOO.util.Event.onDOMReady(function(){
186 YAHOO.util.Event.addListener('path_unlock','click',function(){
186 YAHOO.util.Event.addListener('path_unlock','click',function(){
187 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
187 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
188 });
188 });
189 });
189 });
190 </script>
190 </script>
191 </div>
191 </div>
192 </%def>
192 </%def>
@@ -1,91 +1,100 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Add user')} - ${c.rhodecode_name}
5 ${_('Add user')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Users'),h.url('users'))}
10 ${h.link_to(_('Users'),h.url('users'))}
11 &raquo;
11 &raquo;
12 ${_('add new user')}
12 ${_('add new user')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('admin')}
16 ${self.menu('admin')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 <!-- end box / title -->
25 <!-- end box / title -->
26 ${h.form(url('users'))}
26 ${h.form(url('users'))}
27 <div class="form">
27 <div class="form">
28 <!-- fields -->
28 <!-- fields -->
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="username">${_('Username')}:</label>
32 <label for="username">${_('Username')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('username',class_='small')}
35 ${h.text('username',class_='small')}
36 </div>
36 </div>
37 </div>
37 </div>
38
38
39 <div class="field">
39 <div class="field">
40 <div class="label">
40 <div class="label">
41 <label for="password">${_('Password')}:</label>
41 <label for="password">${_('Password')}:</label>
42 </div>
42 </div>
43 <div class="input">
43 <div class="input">
44 ${h.password('password',class_='small')}
44 ${h.password('password',class_='small')}
45 </div>
45 </div>
46 </div>
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 <div class="field">
57 <div class="field">
49 <div class="label">
58 <div class="label">
50 <label for="name">${_('First Name')}:</label>
59 <label for="name">${_('First Name')}:</label>
51 </div>
60 </div>
52 <div class="input">
61 <div class="input">
53 ${h.text('name',class_='small')}
62 ${h.text('name',class_='small')}
54 </div>
63 </div>
55 </div>
64 </div>
56
65
57 <div class="field">
66 <div class="field">
58 <div class="label">
67 <div class="label">
59 <label for="lastname">${_('Last Name')}:</label>
68 <label for="lastname">${_('Last Name')}:</label>
60 </div>
69 </div>
61 <div class="input">
70 <div class="input">
62 ${h.text('lastname',class_='small')}
71 ${h.text('lastname',class_='small')}
63 </div>
72 </div>
64 </div>
73 </div>
65
74
66 <div class="field">
75 <div class="field">
67 <div class="label">
76 <div class="label">
68 <label for="email">${_('Email')}:</label>
77 <label for="email">${_('Email')}:</label>
69 </div>
78 </div>
70 <div class="input">
79 <div class="input">
71 ${h.text('email',class_='small')}
80 ${h.text('email',class_='small')}
72 </div>
81 </div>
73 </div>
82 </div>
74
83
75 <div class="field">
84 <div class="field">
76 <div class="label label-checkbox">
85 <div class="label label-checkbox">
77 <label for="active">${_('Active')}:</label>
86 <label for="active">${_('Active')}:</label>
78 </div>
87 </div>
79 <div class="checkboxes">
88 <div class="checkboxes">
80 ${h.checkbox('active',value=True)}
89 ${h.checkbox('active',value=True)}
81 </div>
90 </div>
82 </div>
91 </div>
83
92
84 <div class="buttons">
93 <div class="buttons">
85 ${h.submit('save',_('save'),class_="ui-button")}
94 ${h.submit('save',_('save'),class_="ui-button")}
86 </div>
95 </div>
87 </div>
96 </div>
88 </div>
97 </div>
89 ${h.end_form()}
98 ${h.end_form()}
90 </div>
99 </div>
91 </%def>
100 </%def>
@@ -1,149 +1,158 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Edit user')} ${c.user.username} - ${c.rhodecode_name}
5 ${_('Edit user')} ${c.user.username} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(_('Users'),h.url('users'))}
11 ${h.link_to(_('Users'),h.url('users'))}
12 &raquo;
12 &raquo;
13 ${_('edit')} "${c.user.username}"
13 ${_('edit')} "${c.user.username}"
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box box-left">
21 <div class="box box-left">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 <!-- end box / title -->
26 <!-- end box / title -->
27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
28 <div class="form">
28 <div class="form">
29 <div class="field">
29 <div class="field">
30 <div class="gravatar_box">
30 <div class="gravatar_box">
31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
32 <p>
32 <p>
33 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong><br/>
33 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong><br/>
34 ${_('Using')} ${c.user.email}
34 ${_('Using')} ${c.user.email}
35 </p>
35 </p>
36 </div>
36 </div>
37 </div>
37 </div>
38 <div class="field">
38 <div class="field">
39 <div class="label">
39 <div class="label">
40 <label>${_('API key')}</label> ${c.user.api_key}
40 <label>${_('API key')}</label> ${c.user.api_key}
41 </div>
41 </div>
42 </div>
42 </div>
43
43
44 <div class="fields">
44 <div class="fields">
45 <div class="field">
45 <div class="field">
46 <div class="label">
46 <div class="label">
47 <label for="username">${_('Username')}:</label>
47 <label for="username">${_('Username')}:</label>
48 </div>
48 </div>
49 <div class="input">
49 <div class="input">
50 ${h.text('username',class_='medium')}
50 ${h.text('username',class_='medium')}
51 </div>
51 </div>
52 </div>
52 </div>
53
53
54 <div class="field">
54 <div class="field">
55 <div class="label">
55 <div class="label">
56 <label for="ldap_dn">${_('LDAP DN')}:</label>
56 <label for="ldap_dn">${_('LDAP DN')}:</label>
57 </div>
57 </div>
58 <div class="input">
58 <div class="input">
59 ${h.text('ldap_dn',class_='medium')}
59 ${h.text('ldap_dn',class_='medium')}
60 </div>
60 </div>
61 </div>
61 </div>
62
62
63 <div class="field">
63 <div class="field">
64 <div class="label">
64 <div class="label">
65 <label for="new_password">${_('New password')}:</label>
65 <label for="new_password">${_('New password')}:</label>
66 </div>
66 </div>
67 <div class="input">
67 <div class="input">
68 ${h.password('new_password',class_='medium',autocomplete="off")}
68 ${h.password('new_password',class_='medium',autocomplete="off")}
69 </div>
69 </div>
70 </div>
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 <div class="field">
81 <div class="field">
73 <div class="label">
82 <div class="label">
74 <label for="name">${_('First Name')}:</label>
83 <label for="name">${_('First Name')}:</label>
75 </div>
84 </div>
76 <div class="input">
85 <div class="input">
77 ${h.text('name',class_='medium')}
86 ${h.text('name',class_='medium')}
78 </div>
87 </div>
79 </div>
88 </div>
80
89
81 <div class="field">
90 <div class="field">
82 <div class="label">
91 <div class="label">
83 <label for="lastname">${_('Last Name')}:</label>
92 <label for="lastname">${_('Last Name')}:</label>
84 </div>
93 </div>
85 <div class="input">
94 <div class="input">
86 ${h.text('lastname',class_='medium')}
95 ${h.text('lastname',class_='medium')}
87 </div>
96 </div>
88 </div>
97 </div>
89
98
90 <div class="field">
99 <div class="field">
91 <div class="label">
100 <div class="label">
92 <label for="email">${_('Email')}:</label>
101 <label for="email">${_('Email')}:</label>
93 </div>
102 </div>
94 <div class="input">
103 <div class="input">
95 ${h.text('email',class_='medium')}
104 ${h.text('email',class_='medium')}
96 </div>
105 </div>
97 </div>
106 </div>
98
107
99 <div class="field">
108 <div class="field">
100 <div class="label label-checkbox">
109 <div class="label label-checkbox">
101 <label for="active">${_('Active')}:</label>
110 <label for="active">${_('Active')}:</label>
102 </div>
111 </div>
103 <div class="checkboxes">
112 <div class="checkboxes">
104 ${h.checkbox('active',value=True)}
113 ${h.checkbox('active',value=True)}
105 </div>
114 </div>
106 </div>
115 </div>
107
116
108 <div class="field">
117 <div class="field">
109 <div class="label label-checkbox">
118 <div class="label label-checkbox">
110 <label for="admin">${_('Admin')}:</label>
119 <label for="admin">${_('Admin')}:</label>
111 </div>
120 </div>
112 <div class="checkboxes">
121 <div class="checkboxes">
113 ${h.checkbox('admin',value=True)}
122 ${h.checkbox('admin',value=True)}
114 </div>
123 </div>
115 </div>
124 </div>
116 <div class="buttons">
125 <div class="buttons">
117 ${h.submit('save',_('Save'),class_="ui-button")}
126 ${h.submit('save',_('Save'),class_="ui-button")}
118 ${h.reset('reset',_('Reset'),class_="ui-button")}
127 ${h.reset('reset',_('Reset'),class_="ui-button")}
119 </div>
128 </div>
120 </div>
129 </div>
121 </div>
130 </div>
122 ${h.end_form()}
131 ${h.end_form()}
123 </div>
132 </div>
124 <div class="box box-right">
133 <div class="box box-right">
125 <!-- box / title -->
134 <!-- box / title -->
126 <div class="title">
135 <div class="title">
127 <h5>${_('Permissions')}</h5>
136 <h5>${_('Permissions')}</h5>
128 </div>
137 </div>
129 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
138 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
130 <div class="form">
139 <div class="form">
131 <!-- fields -->
140 <!-- fields -->
132 <div class="fields">
141 <div class="fields">
133 <div class="field">
142 <div class="field">
134 <div class="label label-checkbox">
143 <div class="label label-checkbox">
135 <label for="">${_('Create repositories')}:</label>
144 <label for="create_repo_perm">${_('Create repositories')}:</label>
136 </div>
145 </div>
137 <div class="checkboxes">
146 <div class="checkboxes">
138 ${h.checkbox('create_repo_perm',value=True)}
147 ${h.checkbox('create_repo_perm',value=True)}
139 </div>
148 </div>
140 </div>
149 </div>
141 <div class="buttons">
150 <div class="buttons">
142 ${h.submit('save',_('Save'),class_="ui-button")}
151 ${h.submit('save',_('Save'),class_="ui-button")}
143 ${h.reset('reset',_('Reset'),class_="ui-button")}
152 ${h.reset('reset',_('Reset'),class_="ui-button")}
144 </div>
153 </div>
145 </div>
154 </div>
146 </div>
155 </div>
147 ${h.end_form()}
156 ${h.end_form()}
148 </div>
157 </div>
149 </%def>
158 </%def>
@@ -1,211 +1,222 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${_('My Account')}
9 ${_('My Account')}
10 </%def>
10 </%def>
11
11
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="main()">
16 <%def name="main()">
17
17
18 <div class="box box-left">
18 <div class="box box-left">
19 <!-- box / title -->
19 <!-- box / title -->
20 <div class="title">
20 <div class="title">
21 ${self.breadcrumbs()}
21 ${self.breadcrumbs()}
22 </div>
22 </div>
23 <!-- end box / title -->
23 <!-- end box / title -->
24 <div>
24 <div>
25 ${h.form(url('admin_settings_my_account_update'),method='put')}
25 ${h.form(url('admin_settings_my_account_update'),method='put')}
26 <div class="form">
26 <div class="form">
27
27
28 <div class="field">
28 <div class="field">
29 <div class="gravatar_box">
29 <div class="gravatar_box">
30 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
30 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
31 <p>
31 <p>
32 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong><br/>
32 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong><br/>
33 ${_('Using')} ${c.user.email}
33 ${_('Using')} ${c.user.email}
34 </p>
34 </p>
35 </div>
35 </div>
36 </div>
36 </div>
37 <div class="field">
37 <div class="field">
38 <div class="label">
38 <div class="label">
39 <label>${_('API key')}</label> ${c.user.api_key}
39 <label>${_('API key')}</label> ${c.user.api_key}
40 </div>
40 </div>
41 </div>
41 </div>
42 <div class="fields">
42 <div class="fields">
43 <div class="field">
43 <div class="field">
44 <div class="label">
44 <div class="label">
45 <label for="username">${_('Username')}:</label>
45 <label for="username">${_('Username')}:</label>
46 </div>
46 </div>
47 <div class="input">
47 <div class="input">
48 ${h.text('username',class_="medium")}
48 ${h.text('username',class_="medium")}
49 </div>
49 </div>
50 </div>
50 </div>
51
51
52 <div class="field">
52 <div class="field">
53 <div class="label">
53 <div class="label">
54 <label for="new_password">${_('New password')}:</label>
54 <label for="new_password">${_('New password')}:</label>
55 </div>
55 </div>
56 <div class="input">
56 <div class="input">
57 ${h.password('new_password',class_="medium",autocomplete="off")}
57 ${h.password('new_password',class_="medium",autocomplete="off")}
58 </div>
58 </div>
59 </div>
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 <div class="field">
70 <div class="field">
62 <div class="label">
71 <div class="label">
63 <label for="name">${_('First Name')}:</label>
72 <label for="name">${_('First Name')}:</label>
64 </div>
73 </div>
65 <div class="input">
74 <div class="input">
66 ${h.text('name',class_="medium")}
75 ${h.text('name',class_="medium")}
67 </div>
76 </div>
68 </div>
77 </div>
69
78
70 <div class="field">
79 <div class="field">
71 <div class="label">
80 <div class="label">
72 <label for="lastname">${_('Last Name')}:</label>
81 <label for="lastname">${_('Last Name')}:</label>
73 </div>
82 </div>
74 <div class="input">
83 <div class="input">
75 ${h.text('lastname',class_="medium")}
84 ${h.text('lastname',class_="medium")}
76 </div>
85 </div>
77 </div>
86 </div>
78
87
79 <div class="field">
88 <div class="field">
80 <div class="label">
89 <div class="label">
81 <label for="email">${_('Email')}:</label>
90 <label for="email">${_('Email')}:</label>
82 </div>
91 </div>
83 <div class="input">
92 <div class="input">
84 ${h.text('email',class_="medium")}
93 ${h.text('email',class_="medium")}
85 </div>
94 </div>
86 </div>
95 </div>
87
96
88 <div class="buttons">
97 <div class="buttons">
89 ${h.submit('save',_('Save'),class_="ui-button")}
98 ${h.submit('save',_('Save'),class_="ui-button")}
90 ${h.reset('reset',_('Reset'),class_="ui-button")}
99 ${h.reset('reset',_('Reset'),class_="ui-button")}
91 </div>
100 </div>
92 </div>
101 </div>
93 </div>
102 </div>
94 ${h.end_form()}
103 ${h.end_form()}
95 </div>
104 </div>
96 </div>
105 </div>
97
106
98 <div class="box box-right">
107 <div class="box box-right">
99 <!-- box / title -->
108 <!-- box / title -->
100 <div class="title">
109 <div class="title">
101 <h5>${_('My repositories')}
110 <h5>${_('My repositories')}
102 <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...')}"/>
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 </h5>
112 </h5>
104 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
113 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
105 <ul class="links">
114 <ul class="links">
106 <li>
115 <li>
107 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
116 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
108 </li>
117 </li>
109 </ul>
118 </ul>
110 %endif
119 %endif
111 </div>
120 </div>
112 <!-- end box / title -->
121 <!-- end box / title -->
113 <div class="table">
122 <div class="table">
114 <table>
123 <table>
115 <thead>
124 <thead>
116 <tr>
125 <tr>
117 <th class="left">${_('Name')}</th>
126 <th class="left">${_('Name')}</th>
118 <th class="left">${_('revision')}</th>
127 <th class="left">${_('revision')}</th>
119 <th colspan="2" class="left">${_('action')}</th>
128 <th colspan="2" class="left">${_('action')}</th>
120 </thead>
129 </thead>
121 <tbody>
130 <tbody>
122 %if c.user_repos:
131 %if c.user_repos:
123 %for repo in c.user_repos:
132 %for repo in c.user_repos:
124 <tr>
133 <tr>
125 <td>
134 <td>
126 %if repo['dbrepo']['repo_type'] =='hg':
135 %if repo['dbrepo']['repo_type'] =='hg':
127 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
136 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
128 %elif repo['dbrepo']['repo_type'] =='git':
137 %elif repo['dbrepo']['repo_type'] =='git':
129 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
138 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
130 %else:
139 %else:
131
140
132 %endif
141 %endif
133 %if repo['dbrepo']['private']:
142 %if repo['dbrepo']['private']:
134 <img class="icon" alt="${_('private')}" src="${h.url("/images/icons/lock.png")}"/>
143 <img class="icon" alt="${_('private')}" src="${h.url("/images/icons/lock.png")}"/>
135 %else:
144 %else:
136 <img class="icon" alt="${_('public')}" src="${h.url("/images/icons/lock_open.png")}"/>
145 <img class="icon" alt="${_('public')}" src="${h.url("/images/icons/lock_open.png")}"/>
137 %endif
146 %endif
138
147
139 ${h.link_to(repo['name'], h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
148 ${h.link_to(repo['name'], h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
140 %if repo['dbrepo_fork']:
149 %if repo['dbrepo_fork']:
141 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
150 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
142 <img class="icon" alt="${_('public')}"
151 <img class="icon" alt="${_('public')}"
143 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
152 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
144 src="${h.url('/images/icons/arrow_divide.png')}"/></a>
153 src="${h.url('/images/icons/arrow_divide.png')}"/></a>
145 %endif
154 %endif
146 </td>
155 </td>
147 <td><span class="tooltip" title="${repo['last_change']}">${("r%s:%s") % (repo['rev'],h.short_id(repo['tip']))}</span></td>
156 <td><span class="tooltip" title="${repo['last_change']}">${("r%s:%s") % (repo['rev'],h.short_id(repo['tip']))}</span></td>
148 <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>
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 <td>
158 <td>
150 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
159 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
151 ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
160 ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
152 ${h.end_form()}
161 ${h.end_form()}
153 </td>
162 </td>
154 </tr>
163 </tr>
155 %endfor
164 %endfor
156 %else:
165 %else:
166 <div style="padding:5px 0px 10px 0px;">
157 ${_('No repositories yet')}
167 ${_('No repositories yet')}
158 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
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 %endif
170 %endif
171 </div>
161 %endif
172 %endif
162 </tbody>
173 </tbody>
163 </table>
174 </table>
164 </div>
175 </div>
165
176
166 </div>
177 </div>
167 <script type="text/javascript">
178 <script type="text/javascript">
168 var D = YAHOO.util.Dom;
179 var D = YAHOO.util.Dom;
169 var E = YAHOO.util.Event;
180 var E = YAHOO.util.Event;
170 var S = YAHOO.util.Selector;
181 var S = YAHOO.util.Selector;
171
182
172 var q_filter = D.get('q_filter');
183 var q_filter = D.get('q_filter');
173 var F = YAHOO.namespace('q_filter');
184 var F = YAHOO.namespace('q_filter');
174
185
175 E.on(q_filter,'click',function(){
186 E.on(q_filter,'click',function(){
176 q_filter.value = '';
187 q_filter.value = '';
177 });
188 });
178
189
179 F.filterTimeout = null;
190 F.filterTimeout = null;
180
191
181 F.updateFilter = function() {
192 F.updateFilter = function() {
182 // Reset timeout
193 // Reset timeout
183 F.filterTimeout = null;
194 F.filterTimeout = null;
184
195
185 var obsolete = [];
196 var obsolete = [];
186 var nodes = S.query('div.table tr td a.repo_name');
197 var nodes = S.query('div.table tr td a.repo_name');
187 var req = q_filter.value.toLowerCase();
198 var req = q_filter.value.toLowerCase();
188 for (n in nodes){
199 for (n in nodes){
189 D.setStyle(nodes[n].parentNode.parentNode,'display','')
200 D.setStyle(nodes[n].parentNode.parentNode,'display','')
190 }
201 }
191 if (req){
202 if (req){
192 for (n in nodes){
203 for (n in nodes){
193 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
204 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
194 obsolete.push(nodes[n]);
205 obsolete.push(nodes[n]);
195 }
206 }
196 }
207 }
197 if(obsolete){
208 if(obsolete){
198 for (n in obsolete){
209 for (n in obsolete){
199 D.setStyle(obsolete[n].parentNode.parentNode,'display','none');
210 D.setStyle(obsolete[n].parentNode.parentNode,'display','none');
200 }
211 }
201 }
212 }
202 }
213 }
203 }
214 }
204
215
205 E.on(q_filter,'keyup',function(e){
216 E.on(q_filter,'keyup',function(e){
206 clearTimeout(F.filterTimeout);
217 clearTimeout(F.filterTimeout);
207 F.filterTimeout = setTimeout(F.updateFilter,600);
218 F.filterTimeout = setTimeout(F.updateFilter,600);
208 });
219 });
209
220
210 </script>
221 </script>
211 </%def>
222 </%def>
@@ -1,270 +1,270 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Edit users group')} ${c.users_group.users_group_name} - ${c.rhodecode_name}
5 ${_('Edit users group')} ${c.users_group.users_group_name} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(_('UsersGroups'),h.url('users_groups'))}
11 ${h.link_to(_('UsersGroups'),h.url('users_groups'))}
12 &raquo;
12 &raquo;
13 ${_('edit')} "${c.users_group.users_group_name}"
13 ${_('edit')} "${c.users_group.users_group_name}"
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box box-left">
21 <div class="box box-left">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 <!-- end box / title -->
26 <!-- end box / title -->
27 ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')}
27 ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')}
28 <div class="form">
28 <div class="form">
29 <!-- fields -->
29 <!-- fields -->
30 <div class="fields">
30 <div class="fields">
31 <div class="field">
31 <div class="field">
32 <div class="label">
32 <div class="label">
33 <label for="users_group_name">${_('Group name')}:</label>
33 <label for="users_group_name">${_('Group name')}:</label>
34 </div>
34 </div>
35 <div class="input">
35 <div class="input">
36 ${h.text('users_group_name',class_='small')}
36 ${h.text('users_group_name',class_='small')}
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <div class="field">
40 <div class="field">
41 <div class="label label-checkbox">
41 <div class="label label-checkbox">
42 <label for="users_group_active">${_('Active')}:</label>
42 <label for="users_group_active">${_('Active')}:</label>
43 </div>
43 </div>
44 <div class="checkboxes">
44 <div class="checkboxes">
45 ${h.checkbox('users_group_active',value=True)}
45 ${h.checkbox('users_group_active',value=True)}
46 </div>
46 </div>
47 </div>
47 </div>
48 <div class="field">
48 <div class="field">
49 <div class="label">
49 <div class="label">
50 <label for="users_group_active">${_('Members')}:</label>
50 <label for="users_group_active">${_('Members')}:</label>
51 </div>
51 </div>
52 <div class="select">
52 <div class="select">
53 <table>
53 <table>
54 <tr>
54 <tr>
55 <td>
55 <td>
56 <div>
56 <div>
57 <div style="float:left">
57 <div style="float:left">
58 <div class="text" style="padding: 0px 0px 6px;">${_('Choosen group members')}</div>
58 <div class="text" style="padding: 0px 0px 6px;">${_('Choosen group members')}</div>
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")}
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 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
60 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
61 ${_('Remove all elements')}
61 ${_('Remove all elements')}
62 <img alt="remove" style="vertical-align:text-bottom" src="${h.url("/images/icons/arrow_right.png")}"/>
62 <img alt="remove" style="vertical-align:text-bottom" src="${h.url("/images/icons/arrow_right.png")}"/>
63 </div>
63 </div>
64 </div>
64 </div>
65 <div style="float:left;width:20px;padding-top:50px">
65 <div style="float:left;width:20px;padding-top:50px">
66 <img alt="add" id="add_element"
66 <img alt="add" id="add_element"
67 style="padding:2px;cursor:pointer"
67 style="padding:2px;cursor:pointer"
68 src="${h.url("/images/icons/arrow_left.png")}"/>
68 src="${h.url("/images/icons/arrow_left.png")}"/>
69 <br />
69 <br />
70 <img alt="remove" id="remove_element"
70 <img alt="remove" id="remove_element"
71 style="padding:2px;cursor:pointer"
71 style="padding:2px;cursor:pointer"
72 src="${h.url("/images/icons/arrow_right.png")}"/>
72 src="${h.url("/images/icons/arrow_right.png")}"/>
73 </div>
73 </div>
74 <div style="float:left">
74 <div style="float:left">
75 <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
75 <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
76 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
76 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
77 <div id="add_all_elements" style="cursor:pointer;text-align:center">
77 <div id="add_all_elements" style="cursor:pointer;text-align:center">
78 <img alt="add" style="vertical-align:text-bottom" src="${h.url("/images/icons/arrow_left.png")}"/>
78 <img alt="add" style="vertical-align:text-bottom" src="${h.url("/images/icons/arrow_left.png")}"/>
79 ${_('Add all elements')}
79 ${_('Add all elements')}
80 </div>
80 </div>
81 </div>
81 </div>
82 </div>
82 </div>
83 </td>
83 </td>
84 </tr>
84 </tr>
85 </table>
85 </table>
86 </div>
86 </div>
87
87
88 </div>
88 </div>
89 <div class="buttons">
89 <div class="buttons">
90 ${h.submit('save',_('save'),class_="ui-button")}
90 ${h.submit('save',_('save'),class_="ui-button")}
91 </div>
91 </div>
92 </div>
92 </div>
93 </div>
93 </div>
94 ${h.end_form()}
94 ${h.end_form()}
95 </div>
95 </div>
96
96
97 <script type="text/javascript">
97 <script type="text/javascript">
98 YAHOO.util.Event.onDOMReady(function(){
98 YAHOO.util.Event.onDOMReady(function(){
99 var D = YAHOO.util.Dom;
99 var D = YAHOO.util.Dom;
100 var E = YAHOO.util.Event;
100 var E = YAHOO.util.Event;
101
101
102 //definition of containers ID's
102 //definition of containers ID's
103 var available_container = 'available_members';
103 var available_container = 'available_members';
104 var selected_container = 'users_group_members';
104 var selected_container = 'users_group_members';
105
105
106 //form containing containers id
106 //form containing containers id
107 var form_id = 'edit_users_group';
107 var form_id = 'edit_users_group';
108
108
109 //temp container for selected storage.
109 //temp container for selected storage.
110 var cache = new Array();
110 var cache = new Array();
111 var av_cache = new Array();
111 var av_cache = new Array();
112 var c = D.get(selected_container);
112 var c = D.get(selected_container);
113 var ac = D.get(available_container);
113 var ac = D.get(available_container);
114
114
115 //get only selected options for further fullfilment
115 //get only selected options for further fullfilment
116 for(var i = 0;node =c.options[i];i++){
116 for(var i = 0;node =c.options[i];i++){
117 if(node.selected){
117 if(node.selected){
118 //push selected to my temp storage left overs :)
118 //push selected to my temp storage left overs :)
119 cache.push(node);
119 cache.push(node);
120 }
120 }
121 }
121 }
122
122
123 //clear 'selected' select
123 //clear 'selected' select
124 //c.options.length = 0;
124 //c.options.length = 0;
125
125
126 //fill it with remembered options
126 //fill it with remembered options
127 //for(var i = 0;node = cache[i];i++){
127 //for(var i = 0;node = cache[i];i++){
128 // c.options[i]=new Option(node.text, node.value, false, false);
128 // c.options[i]=new Option(node.text, node.value, false, false);
129 //}
129 //}
130
130
131
131
132 //get all available options to cache
132 //get all available options to cache
133 for(var i = 0;node =ac.options[i];i++){
133 for(var i = 0;node =ac.options[i];i++){
134 //push selected to my temp storage left overs :)
134 //push selected to my temp storage left overs :)
135 av_cache.push(node);
135 av_cache.push(node);
136 }
136 }
137
137
138 //fill available only with those not in choosen
138 //fill available only with those not in choosen
139 ac.options.length=0;
139 ac.options.length=0;
140 tmp_cache = new Array();
140 tmp_cache = new Array();
141
141
142 for(var i = 0;node = av_cache[i];i++){
142 for(var i = 0;node = av_cache[i];i++){
143 var add = true;
143 var add = true;
144 for(var i2 = 0;node_2 = cache[i2];i2++){
144 for(var i2 = 0;node_2 = cache[i2];i2++){
145 if(node.value == node_2.value){
145 if(node.value == node_2.value){
146 add=false;
146 add=false;
147 break;
147 break;
148 }
148 }
149 }
149 }
150 if(add){
150 if(add){
151 tmp_cache.push(new Option(node.text, node.value, false, false));
151 tmp_cache.push(new Option(node.text, node.value, false, false));
152 }
152 }
153 }
153 }
154
154
155 for(var i = 0;node = tmp_cache[i];i++){
155 for(var i = 0;node = tmp_cache[i];i++){
156 ac.options[i] = node;
156 ac.options[i] = node;
157 }
157 }
158
158
159 function prompts_action_callback(e){
159 function prompts_action_callback(e){
160
160
161 var choosen = D.get(selected_container);
161 var choosen = D.get(selected_container);
162 var available = D.get(available_container);
162 var available = D.get(available_container);
163
163
164 //get checked and unchecked options from field
164 //get checked and unchecked options from field
165 function get_checked(from_field){
165 function get_checked(from_field){
166 //temp container for storage.
166 //temp container for storage.
167 var sel_cache = new Array();
167 var sel_cache = new Array();
168 var oth_cache = new Array();
168 var oth_cache = new Array();
169
169
170 for(var i = 0;node = from_field.options[i];i++){
170 for(var i = 0;node = from_field.options[i];i++){
171 if(node.selected){
171 if(node.selected){
172 //push selected fields :)
172 //push selected fields :)
173 sel_cache.push(node);
173 sel_cache.push(node);
174 }
174 }
175 else{
175 else{
176 oth_cache.push(node)
176 oth_cache.push(node)
177 }
177 }
178 }
178 }
179
179
180 return [sel_cache,oth_cache]
180 return [sel_cache,oth_cache]
181 }
181 }
182
182
183 //fill the field with given options
183 //fill the field with given options
184 function fill_with(field,options){
184 function fill_with(field,options){
185 //clear firtst
185 //clear firtst
186 field.options.length=0;
186 field.options.length=0;
187 for(var i = 0;node = options[i];i++){
187 for(var i = 0;node = options[i];i++){
188 field.options[i]=new Option(node.text, node.value,
188 field.options[i]=new Option(node.text, node.value,
189 false, false);
189 false, false);
190 }
190 }
191
191
192 }
192 }
193 //adds to current field
193 //adds to current field
194 function add_to(field,options){
194 function add_to(field,options){
195 for(var i = 0;node = options[i];i++){
195 for(var i = 0;node = options[i];i++){
196 field.appendChild(new Option(node.text, node.value,
196 field.appendChild(new Option(node.text, node.value,
197 false, false));
197 false, false));
198 }
198 }
199 }
199 }
200
200
201 // add action
201 // add action
202 if (this.id=='add_element'){
202 if (this.id=='add_element'){
203 var c = get_checked(available);
203 var c = get_checked(available);
204 add_to(choosen,c[0]);
204 add_to(choosen,c[0]);
205 fill_with(available,c[1]);
205 fill_with(available,c[1]);
206 }
206 }
207 // remove action
207 // remove action
208 if (this.id=='remove_element'){
208 if (this.id=='remove_element'){
209 var c = get_checked(choosen);
209 var c = get_checked(choosen);
210 add_to(available,c[0]);
210 add_to(available,c[0]);
211 fill_with(choosen,c[1]);
211 fill_with(choosen,c[1]);
212 }
212 }
213 // add all elements
213 // add all elements
214 if(this.id=='add_all_elements'){
214 if(this.id=='add_all_elements'){
215 for(var i=0; node = available.options[i];i++){
215 for(var i=0; node = available.options[i];i++){
216 choosen.appendChild(new Option(node.text,
216 choosen.appendChild(new Option(node.text,
217 node.value, false, false));
217 node.value, false, false));
218 }
218 }
219 available.options.length = 0;
219 available.options.length = 0;
220 }
220 }
221 //remove all elements
221 //remove all elements
222 if(this.id=='remove_all_elements'){
222 if(this.id=='remove_all_elements'){
223 for(var i=0; node = choosen.options[i];i++){
223 for(var i=0; node = choosen.options[i];i++){
224 available.appendChild(new Option(node.text,
224 available.appendChild(new Option(node.text,
225 node.value, false, false));
225 node.value, false, false));
226 }
226 }
227 choosen.options.length = 0;
227 choosen.options.length = 0;
228 }
228 }
229
229
230 }
230 }
231
231
232
232
233 E.addListener(['add_element','remove_element',
233 E.addListener(['add_element','remove_element',
234 'add_all_elements','remove_all_elements'],'click',
234 'add_all_elements','remove_all_elements'],'click',
235 prompts_action_callback)
235 prompts_action_callback)
236
236
237 E.addListener(form_id,'submit',function(){
237 E.addListener(form_id,'submit',function(){
238 var choosen = D.get(selected_container);
238 var choosen = D.get(selected_container);
239 for (var i = 0; i < choosen.options.length; i++) {
239 for (var i = 0; i < choosen.options.length; i++) {
240 choosen.options[i].selected = 'selected';
240 choosen.options[i].selected = 'selected';
241 }
241 }
242 })
242 })
243 });
243 });
244 </script>
244 </script>
245 <div class="box box-right">
245 <div class="box box-right">
246 <!-- box / title -->
246 <!-- box / title -->
247 <div class="title">
247 <div class="title">
248 <h5>${_('Permissions')}</h5>
248 <h5>${_('Permissions')}</h5>
249 </div>
249 </div>
250 ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')}
250 ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')}
251 <div class="form">
251 <div class="form">
252 <!-- fields -->
252 <!-- fields -->
253 <div class="fields">
253 <div class="fields">
254 <div class="field">
254 <div class="field">
255 <div class="label label-checkbox">
255 <div class="label label-checkbox">
256 <label for="">${_('Create repositories')}:</label>
256 <label for="create_repo_perm">${_('Create repositories')}:</label>
257 </div>
257 </div>
258 <div class="checkboxes">
258 <div class="checkboxes">
259 ${h.checkbox('create_repo_perm',value=True)}
259 ${h.checkbox('create_repo_perm',value=True)}
260 </div>
260 </div>
261 </div>
261 </div>
262 <div class="buttons">
262 <div class="buttons">
263 ${h.submit('save',_('Save'),class_="ui-button")}
263 ${h.submit('save',_('Save'),class_="ui-button")}
264 ${h.reset('reset',_('Reset'),class_="ui-button")}
264 ${h.reset('reset',_('Reset'),class_="ui-button")}
265 </div>
265 </div>
266 </div>
266 </div>
267 </div>
267 </div>
268 ${h.end_form()}
268 ${h.end_form()}
269 </div>
269 </div>
270 </%def>
270 </%def>
@@ -1,75 +1,82 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${c.repo_name} ${_('Settings')} - ${c.rhodecode_name}
5 ${c.repo_name} ${_('Settings')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(u'Home',h.url('/'))}
9 ${h.link_to(u'Home',h.url('/'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
11 ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
12 &raquo;
12 &raquo;
13 ${_('Settings')}
13 ${_('Settings')}
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('settings')}
17 ${self.menu('settings')}
18 </%def>
18 </%def>
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 ${h.form(url('repo_settings_update', repo_name=c.repo_info.repo_name),method='put')}
25 ${h.form(url('repo_settings_update', repo_name=c.repo_info.repo_name),method='put')}
26 <div class="form">
26 <div class="form">
27 <!-- fields -->
27 <!-- fields -->
28 <div class="fields">
28 <div class="fields">
29 <div class="field">
29 <div class="field">
30 <div class="label">
30 <div class="label">
31 <label for="repo_name">${_('Name')}:</label>
31 <label for="repo_name">${_('Name')}:</label>
32 </div>
32 </div>
33 <div class="input input-medium">
33 <div class="input input-medium">
34 ${h.text('repo_name',class_="small")}
34 ${h.text('repo_name',class_="small")}
35 </div>
35 </div>
36 </div>
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 <div class="field">
45 <div class="field">
39 <div class="label label-textarea">
46 <div class="label label-textarea">
40 <label for="description">${_('Description')}:</label>
47 <label for="description">${_('Description')}:</label>
41 </div>
48 </div>
42 <div class="textarea text-area editor">
49 <div class="textarea text-area editor">
43 ${h.textarea('description',cols=23,rows=5)}
50 ${h.textarea('description',cols=23,rows=5)}
44 </div>
51 </div>
45 </div>
52 </div>
46
53
47 <div class="field">
54 <div class="field">
48 <div class="label label-checkbox">
55 <div class="label label-checkbox">
49 <label for="private">${_('Private')}:</label>
56 <label for="private">${_('Private')}:</label>
50 </div>
57 </div>
51 <div class="checkboxes">
58 <div class="checkboxes">
52 ${h.checkbox('private',value="True")}
59 ${h.checkbox('private',value="True")}
53 </div>
60 </div>
54 </div>
61 </div>
55
62
56 <div class="field">
63 <div class="field">
57 <div class="label">
64 <div class="label">
58 <label for="">${_('Permissions')}:</label>
65 <label for="">${_('Permissions')}:</label>
59 </div>
66 </div>
60 <div class="input">
67 <div class="input">
61 <%include file="../admin/repos/repo_edit_perms.html"/>
68 <%include file="../admin/repos/repo_edit_perms.html"/>
62 </div>
69 </div>
63
70
64 <div class="buttons">
71 <div class="buttons">
65 ${h.submit('save','Save',class_="ui-button")}
72 ${h.submit('save','Save',class_="ui-button")}
66 ${h.reset('reset','Reset',class_="ui-button")}
73 ${h.reset('reset','Reset',class_="ui-button")}
67 </div>
74 </div>
68 </div>
75 </div>
69 </div>
76 </div>
70 ${h.end_form()}
77 ${h.end_form()}
71 </div>
78 </div>
72 </div>
79 </div>
73 </%def>
80 </%def>
74
81
75
82
@@ -1,85 +1,83 b''
1 """Pylons application test package
1 """Pylons application test package
2
2
3 This package assumes the Pylons environment is already loaded, such as
3 This package assumes the Pylons environment is already loaded, such as
4 when this script is imported from the `nosetests --with-pylons=test.ini`
4 when this script is imported from the `nosetests --with-pylons=test.ini`
5 command.
5 command.
6
6
7 This module initializes the application via ``websetup`` (`paster
7 This module initializes the application via ``websetup`` (`paster
8 setup-app`) and provides the base testing objects.
8 setup-app`) and provides the base testing objects.
9 """
9 """
10 import os
10 import os
11 from os.path import join as jn
11 from os.path import join as jn
12
12
13 from unittest import TestCase
13 from unittest import TestCase
14
14
15 from paste.deploy import loadapp
15 from paste.deploy import loadapp
16 from paste.script.appinstall import SetupCommand
16 from paste.script.appinstall import SetupCommand
17 from pylons import config, url
17 from pylons import config, url
18 from routes.util import URLGenerator
18 from routes.util import URLGenerator
19 from webtest import TestApp
19 from webtest import TestApp
20
20
21 from rhodecode.model import meta
21 from rhodecode.model import meta
22 import logging
22 import logging
23
23
24
24
25 log = logging.getLogger(__name__)
25 log = logging.getLogger(__name__)
26
26
27 import pylons.test
27 import pylons.test
28
28
29 __all__ = ['environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
29 __all__ = ['environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
30 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK',
30 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK',
31 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS' ]
31 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS' ]
32
32
33 # Invoke websetup with the current config file
33 # Invoke websetup with the current config file
34 #SetupCommand('setup-app').run([config_file])
34 #SetupCommand('setup-app').run([config_file])
35
35
36 ##RUNNING DESIRED TESTS
36 ##RUNNING DESIRED TESTS
37 # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
37 # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
38 # nosetests --pdb --pdb-failures
38 # nosetests --pdb --pdb-failures
39 environ = {}
39 environ = {}
40
40
41 #SOME GLOBALS FOR TESTS
41 #SOME GLOBALS FOR TESTS
42 from tempfile import _RandomNameSequence
42 from tempfile import _RandomNameSequence
43 TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
43 TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
44 TEST_USER_ADMIN_LOGIN = 'test_admin'
44 TEST_USER_ADMIN_LOGIN = 'test_admin'
45 TEST_USER_ADMIN_PASS = 'test12'
45 TEST_USER_ADMIN_PASS = 'test12'
46 HG_REPO = 'vcs_test_hg'
46 HG_REPO = 'vcs_test_hg'
47 GIT_REPO = 'vcs_test_git'
47 GIT_REPO = 'vcs_test_git'
48
48
49 NEW_HG_REPO = 'vcs_test_hg_new'
49 NEW_HG_REPO = 'vcs_test_hg_new'
50 NEW_GIT_REPO = 'vcs_test_git_new'
50 NEW_GIT_REPO = 'vcs_test_git_new'
51
51
52 HG_FORK = 'vcs_test_hg_fork'
52 HG_FORK = 'vcs_test_hg_fork'
53 GIT_FORK = 'vcs_test_git_fork'
53 GIT_FORK = 'vcs_test_git_fork'
54
54
55 class TestController(TestCase):
55 class TestController(TestCase):
56
56
57 def __init__(self, *args, **kwargs):
57 def __init__(self, *args, **kwargs):
58 wsgiapp = pylons.test.pylonsapp
58 wsgiapp = pylons.test.pylonsapp
59 config = wsgiapp.config
59 config = wsgiapp.config
60
60
61 self.app = TestApp(wsgiapp)
61 self.app = TestApp(wsgiapp)
62 url._push_object(URLGenerator(config['routes.map'], environ))
62 url._push_object(URLGenerator(config['routes.map'], environ))
63 self.sa = meta.Session
63 self.sa = meta.Session
64 self.index_location = config['app_conf']['index_dir']
64 self.index_location = config['app_conf']['index_dir']
65 TestCase.__init__(self, *args, **kwargs)
65 TestCase.__init__(self, *args, **kwargs)
66
66
67 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
67 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
68 password=TEST_USER_ADMIN_PASS):
68 password=TEST_USER_ADMIN_PASS):
69 response = self.app.post(url(controller='login', action='index'),
69 response = self.app.post(url(controller='login', action='index'),
70 {'username':username,
70 {'username':username,
71 'password':password})
71 'password':password})
72
72
73 if 'invalid user name' in response.body:
73 if 'invalid user name' in response.body:
74 self.fail('could not login using %s %s' % (username, password))
74 self.fail('could not login using %s %s' % (username, password))
75
75
76 self.assertEqual(response.status, '302 Found')
76 self.assertEqual(response.status, '302 Found')
77 self.assertEqual(response.session['rhodecode_user'].username, username)
77 self.assertEqual(response.session['rhodecode_user'].username, username)
78 return response.follow()
78 return response.follow()
79
79
80
81
82 def checkSessionFlash(self, response, msg):
80 def checkSessionFlash(self, response, msg):
83 self.assertTrue('flash' in response.session)
81 self.assertTrue('flash' in response.session)
84 self.assertTrue(msg in response.session['flash'][0][1])
82 self.assertTrue(msg in response.session['flash'][0][1])
85
83
@@ -1,208 +1,212 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 from rhodecode.lib.auth import get_crypt_password, check_password
3 from rhodecode.lib.auth import get_crypt_password, check_password
4 from rhodecode.model.db import User, RhodeCodeSettings
4 from rhodecode.model.db import User, RhodeCodeSettings
5 from rhodecode.tests import *
5 from rhodecode.tests import *
6
6
7 class TestAdminSettingsController(TestController):
7 class TestAdminSettingsController(TestController):
8
8
9 def test_index(self):
9 def test_index(self):
10 response = self.app.get(url('admin_settings'))
10 response = self.app.get(url('admin_settings'))
11 # Test response...
11 # Test response...
12
12
13 def test_index_as_xml(self):
13 def test_index_as_xml(self):
14 response = self.app.get(url('formatted_admin_settings', format='xml'))
14 response = self.app.get(url('formatted_admin_settings', format='xml'))
15
15
16 def test_create(self):
16 def test_create(self):
17 response = self.app.post(url('admin_settings'))
17 response = self.app.post(url('admin_settings'))
18
18
19 def test_new(self):
19 def test_new(self):
20 response = self.app.get(url('admin_new_setting'))
20 response = self.app.get(url('admin_new_setting'))
21
21
22 def test_new_as_xml(self):
22 def test_new_as_xml(self):
23 response = self.app.get(url('formatted_admin_new_setting', format='xml'))
23 response = self.app.get(url('formatted_admin_new_setting', format='xml'))
24
24
25 def test_update(self):
25 def test_update(self):
26 response = self.app.put(url('admin_setting', setting_id=1))
26 response = self.app.put(url('admin_setting', setting_id=1))
27
27
28 def test_update_browser_fakeout(self):
28 def test_update_browser_fakeout(self):
29 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='put'))
29 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='put'))
30
30
31 def test_delete(self):
31 def test_delete(self):
32 response = self.app.delete(url('admin_setting', setting_id=1))
32 response = self.app.delete(url('admin_setting', setting_id=1))
33
33
34 def test_delete_browser_fakeout(self):
34 def test_delete_browser_fakeout(self):
35 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='delete'))
35 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='delete'))
36
36
37 def test_show(self):
37 def test_show(self):
38 response = self.app.get(url('admin_setting', setting_id=1))
38 response = self.app.get(url('admin_setting', setting_id=1))
39
39
40 def test_show_as_xml(self):
40 def test_show_as_xml(self):
41 response = self.app.get(url('formatted_admin_setting', setting_id=1, format='xml'))
41 response = self.app.get(url('formatted_admin_setting', setting_id=1, format='xml'))
42
42
43 def test_edit(self):
43 def test_edit(self):
44 response = self.app.get(url('admin_edit_setting', setting_id=1))
44 response = self.app.get(url('admin_edit_setting', setting_id=1))
45
45
46 def test_edit_as_xml(self):
46 def test_edit_as_xml(self):
47 response = self.app.get(url('formatted_admin_edit_setting',
47 response = self.app.get(url('formatted_admin_edit_setting',
48 setting_id=1, format='xml'))
48 setting_id=1, format='xml'))
49
49
50
50
51 def test_ga_code_active(self):
51 def test_ga_code_active(self):
52 self.log_user()
52 self.log_user()
53 old_title = 'RhodeCode'
53 old_title = 'RhodeCode'
54 old_realm = 'RhodeCode authentication'
54 old_realm = 'RhodeCode authentication'
55 new_ga_code = 'ga-test-123456789'
55 new_ga_code = 'ga-test-123456789'
56 response = self.app.post(url('admin_setting', setting_id='global'),
56 response = self.app.post(url('admin_setting', setting_id='global'),
57 params=dict(
57 params=dict(
58 _method='put',
58 _method='put',
59 rhodecode_title=old_title,
59 rhodecode_title=old_title,
60 rhodecode_realm=old_realm,
60 rhodecode_realm=old_realm,
61 rhodecode_ga_code=new_ga_code
61 rhodecode_ga_code=new_ga_code
62 ))
62 ))
63
63
64 self.checkSessionFlash(response, 'Updated application settings')
64 self.checkSessionFlash(response, 'Updated application settings')
65
65
66 self.assertEqual(RhodeCodeSettings
66 self.assertEqual(RhodeCodeSettings
67 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
67 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
68
68
69 response = response.follow()
69 response = response.follow()
70 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
70 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
71 in response.body)
71 in response.body)
72
72
73 def test_ga_code_inactive(self):
73 def test_ga_code_inactive(self):
74 self.log_user()
74 self.log_user()
75 old_title = 'RhodeCode'
75 old_title = 'RhodeCode'
76 old_realm = 'RhodeCode authentication'
76 old_realm = 'RhodeCode authentication'
77 new_ga_code = ''
77 new_ga_code = ''
78 response = self.app.post(url('admin_setting', setting_id='global'),
78 response = self.app.post(url('admin_setting', setting_id='global'),
79 params=dict(
79 params=dict(
80 _method='put',
80 _method='put',
81 rhodecode_title=old_title,
81 rhodecode_title=old_title,
82 rhodecode_realm=old_realm,
82 rhodecode_realm=old_realm,
83 rhodecode_ga_code=new_ga_code
83 rhodecode_ga_code=new_ga_code
84 ))
84 ))
85
85
86 self.assertTrue('Updated application settings' in
86 self.assertTrue('Updated application settings' in
87 response.session['flash'][0][1])
87 response.session['flash'][0][1])
88 self.assertEqual(RhodeCodeSettings
88 self.assertEqual(RhodeCodeSettings
89 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
89 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
90
90
91 response = response.follow()
91 response = response.follow()
92 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
92 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
93 not in response.body)
93 not in response.body)
94
94
95
95
96 def test_title_change(self):
96 def test_title_change(self):
97 self.log_user()
97 self.log_user()
98 old_title = 'RhodeCode'
98 old_title = 'RhodeCode'
99 new_title = old_title + '_changed'
99 new_title = old_title + '_changed'
100 old_realm = 'RhodeCode authentication'
100 old_realm = 'RhodeCode authentication'
101
101
102 for new_title in ['Changed', 'Żółwik', old_title]:
102 for new_title in ['Changed', 'Żółwik', old_title]:
103 response = self.app.post(url('admin_setting', setting_id='global'),
103 response = self.app.post(url('admin_setting', setting_id='global'),
104 params=dict(
104 params=dict(
105 _method='put',
105 _method='put',
106 rhodecode_title=new_title,
106 rhodecode_title=new_title,
107 rhodecode_realm=old_realm,
107 rhodecode_realm=old_realm,
108 rhodecode_ga_code=''
108 rhodecode_ga_code=''
109 ))
109 ))
110
110
111 self.checkSessionFlash(response, 'Updated application settings')
111 self.checkSessionFlash(response, 'Updated application settings')
112 self.assertEqual(RhodeCodeSettings
112 self.assertEqual(RhodeCodeSettings
113 .get_app_settings()['rhodecode_title'],
113 .get_app_settings()['rhodecode_title'],
114 new_title.decode('utf-8'))
114 new_title.decode('utf-8'))
115
115
116 response = response.follow()
116 response = response.follow()
117 self.assertTrue("""<h1><a href="/">%s</a></h1>""" % new_title
117 self.assertTrue("""<h1><a href="/">%s</a></h1>""" % new_title
118 in response.body)
118 in response.body)
119
119
120
120
121 def test_my_account(self):
121 def test_my_account(self):
122 self.log_user()
122 self.log_user()
123 response = self.app.get(url('admin_settings_my_account'))
123 response = self.app.get(url('admin_settings_my_account'))
124
124
125 self.assertTrue('value="test_admin' in response.body)
125 self.assertTrue('value="test_admin' in response.body)
126
126
127 def test_my_account_update(self):
127 def test_my_account_update(self):
128 self.log_user()
128 self.log_user()
129
129
130 new_email = 'new@mail.pl'
130 new_email = 'new@mail.pl'
131 new_name = 'NewName'
131 new_name = 'NewName'
132 new_lastname = 'NewLastname'
132 new_lastname = 'NewLastname'
133 new_password = 'test123'
133 new_password = 'test123'
134
134
135
135
136 response = self.app.post(url('admin_settings_my_account_update'),
136 response = self.app.post(url('admin_settings_my_account_update'),
137 params=dict(_method='put',
137 params=dict(_method='put',
138 username='test_admin',
138 username='test_admin',
139 new_password=new_password,
139 new_password=new_password,
140 password_confirmation = new_password,
140 password='',
141 password='',
141 name=new_name,
142 name=new_name,
142 lastname=new_lastname,
143 lastname=new_lastname,
143 email=new_email,))
144 email=new_email,))
144 response.follow()
145 response.follow()
145
146
146 assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
147 assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
147 user = self.sa.query(User).filter(User.username == 'test_admin').one()
148 user = self.sa.query(User).filter(User.username == 'test_admin').one()
148 assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email)
149 assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email)
149 assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name)
150 assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name)
150 assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname)
151 assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname)
151 assert check_password(new_password, user.password) is True, 'password field mismatch %s vs %s' % (user.password, new_password)
152 assert check_password(new_password, user.password) is True, 'password field mismatch %s vs %s' % (user.password, new_password)
152
153
153 #bring back the admin settings
154 #bring back the admin settings
154 old_email = 'test_admin@mail.com'
155 old_email = 'test_admin@mail.com'
155 old_name = 'RhodeCode'
156 old_name = 'RhodeCode'
156 old_lastname = 'Admin'
157 old_lastname = 'Admin'
157 old_password = 'test12'
158 old_password = 'test12'
158
159
159 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
160 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
160 _method='put',
161 _method='put',
161 username='test_admin',
162 username='test_admin',
162 new_password=old_password,
163 new_password=old_password,
164 password_confirmation = old_password,
163 password='',
165 password='',
164 name=old_name,
166 name=old_name,
165 lastname=old_lastname,
167 lastname=old_lastname,
166 email=old_email,))
168 email=old_email,))
167
169
168 response.follow()
170 response.follow()
169 self.checkSessionFlash(response,
171 self.checkSessionFlash(response,
170 'Your account was updated successfully')
172 'Your account was updated successfully')
171
173
172 user = self.sa.query(User).filter(User.username == 'test_admin').one()
174 user = self.sa.query(User).filter(User.username == 'test_admin').one()
173 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
175 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
174
176
175 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
177 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
176 assert user.name == old_name, 'updated field mismatch %s vs %s' % (user.name, old_name)
178 assert user.name == old_name, 'updated field mismatch %s vs %s' % (user.name, old_name)
177 assert user.lastname == old_lastname, 'updated field mismatch %s vs %s' % (user.lastname, old_lastname)
179 assert user.lastname == old_lastname, 'updated field mismatch %s vs %s' % (user.lastname, old_lastname)
178 assert check_password(old_password, user.password) is True , 'password updated field mismatch %s vs %s' % (user.password, old_password)
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 def test_my_account_update_err_email_exists(self):
183 def test_my_account_update_err_email_exists(self):
182 self.log_user()
184 self.log_user()
183
185
184 new_email = 'test_regular@mail.com'#already exisitn email
186 new_email = 'test_regular@mail.com'#already exisitn email
185 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
187 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
186 _method='put',
188 _method='put',
187 username='test_admin',
189 username='test_admin',
188 new_password='test12',
190 new_password='test12',
191 password_confirmation = 'test122',
189 name='NewName',
192 name='NewName',
190 lastname='NewLastname',
193 lastname='NewLastname',
191 email=new_email,))
194 email=new_email,))
192
195
193 assert 'This e-mail address is already taken' in response.body, 'Missing error message about existing email'
196 assert 'This e-mail address is already taken' in response.body, 'Missing error message about existing email'
194
197
195
198
196 def test_my_account_update_err(self):
199 def test_my_account_update_err(self):
197 self.log_user('test_regular2', 'test12')
200 self.log_user('test_regular2', 'test12')
198
201
199 new_email = 'newmail.pl'
202 new_email = 'newmail.pl'
200 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
203 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
201 _method='put',
204 _method='put',
202 username='test_admin',
205 username='test_admin',
203 new_password='test12',
206 new_password='test12',
207 password_confirmation = 'test122',
204 name='NewName',
208 name='NewName',
205 lastname='NewLastname',
209 lastname='NewLastname',
206 email=new_email,))
210 email=new_email,))
207 assert 'An email address must contain a single @' in response.body, 'Missing error message about wrong email'
211 assert 'An email address must contain a single @' in response.body, 'Missing error message about wrong email'
208 assert 'This username already exists' in response.body, 'Missing error message about existing user'
212 assert 'This username already exists' in response.body, 'Missing error message about existing user'
@@ -1,119 +1,122 b''
1 from rhodecode.tests import *
1 from rhodecode.tests import *
2 from rhodecode.model.db import User
2 from rhodecode.model.db import User
3 from rhodecode.lib.auth import check_password
3 from rhodecode.lib.auth import check_password
4 from sqlalchemy.orm.exc import NoResultFound
4 from sqlalchemy.orm.exc import NoResultFound
5
5
6 class TestAdminUsersController(TestController):
6 class TestAdminUsersController(TestController):
7
7
8 def test_index(self):
8 def test_index(self):
9 response = self.app.get(url('users'))
9 response = self.app.get(url('users'))
10 # Test response...
10 # Test response...
11
11
12 def test_index_as_xml(self):
12 def test_index_as_xml(self):
13 response = self.app.get(url('formatted_users', format='xml'))
13 response = self.app.get(url('formatted_users', format='xml'))
14
14
15 def test_create(self):
15 def test_create(self):
16 self.log_user()
16 self.log_user()
17 username = 'newtestuser'
17 username = 'newtestuser'
18 password = 'test12'
18 password = 'test12'
19 password_confirmation = password
19 name = 'name'
20 name = 'name'
20 lastname = 'lastname'
21 lastname = 'lastname'
21 email = 'mail@mail.com'
22 email = 'mail@mail.com'
22
23
23 response = self.app.post(url('users'), {'username':username,
24 response = self.app.post(url('users'), {'username':username,
24 'password':password,
25 'password':password,
26 'password_confirmation':password_confirmation,
25 'name':name,
27 'name':name,
26 'active':True,
28 'active':True,
27 'lastname':lastname,
29 'lastname':lastname,
28 'email':email})
30 'email':email})
29
31
30
32
31 assert '''created user %s''' % (username) in response.session['flash'][0], 'No flash message about new user'
33 assert '''created user %s''' % (username) in response.session['flash'][0], 'No flash message about new user'
32
34
33 new_user = self.sa.query(User).filter(User.username == username).one()
35 new_user = self.sa.query(User).filter(User.username == username).one()
34
36
35
37
36 assert new_user.username == username, 'wrong info about username'
38 assert new_user.username == username, 'wrong info about username'
37 assert check_password(password, new_user.password) == True , 'wrong info about password'
39 assert check_password(password, new_user.password) == True , 'wrong info about password'
38 assert new_user.name == name, 'wrong info about name'
40 assert new_user.name == name, 'wrong info about name'
39 assert new_user.lastname == lastname, 'wrong info about lastname'
41 assert new_user.lastname == lastname, 'wrong info about lastname'
40 assert new_user.email == email, 'wrong info about email'
42 assert new_user.email == email, 'wrong info about email'
41
43
42
44
43 response.follow()
45 response.follow()
44 response = response.follow()
46 response = response.follow()
45 assert """edit">newtestuser</a>""" in response.body
47 assert """edit">newtestuser</a>""" in response.body
46
48
47 def test_create_err(self):
49 def test_create_err(self):
48 self.log_user()
50 self.log_user()
49 username = 'new_user'
51 username = 'new_user'
50 password = ''
52 password = ''
51 name = 'name'
53 name = 'name'
52 lastname = 'lastname'
54 lastname = 'lastname'
53 email = 'errmail.com'
55 email = 'errmail.com'
54
56
55 response = self.app.post(url('users'), {'username':username,
57 response = self.app.post(url('users'), {'username':username,
56 'password':password,
58 'password':password,
57 'name':name,
59 'name':name,
58 'active':False,
60 'active':False,
59 'lastname':lastname,
61 'lastname':lastname,
60 'email':email})
62 'email':email})
61
63
62 assert """<span class="error-message">Invalid username</span>""" in response.body
64 assert """<span class="error-message">Invalid username</span>""" in response.body
63 assert """<span class="error-message">Please enter a value</span>""" in response.body
65 assert """<span class="error-message">Please enter a value</span>""" in response.body
64 assert """<span class="error-message">An email address must contain a single @</span>""" in response.body
66 assert """<span class="error-message">An email address must contain a single @</span>""" in response.body
65
67
66 def get_user():
68 def get_user():
67 self.sa.query(User).filter(User.username == username).one()
69 self.sa.query(User).filter(User.username == username).one()
68
70
69 self.assertRaises(NoResultFound, get_user), 'found user in database'
71 self.assertRaises(NoResultFound, get_user), 'found user in database'
70
72
71 def test_new(self):
73 def test_new(self):
72 response = self.app.get(url('new_user'))
74 response = self.app.get(url('new_user'))
73
75
74 def test_new_as_xml(self):
76 def test_new_as_xml(self):
75 response = self.app.get(url('formatted_new_user', format='xml'))
77 response = self.app.get(url('formatted_new_user', format='xml'))
76
78
77 def test_update(self):
79 def test_update(self):
78 response = self.app.put(url('user', id=1))
80 response = self.app.put(url('user', id=1))
79
81
80 def test_update_browser_fakeout(self):
82 def test_update_browser_fakeout(self):
81 response = self.app.post(url('user', id=1), params=dict(_method='put'))
83 response = self.app.post(url('user', id=1), params=dict(_method='put'))
82
84
83 def test_delete(self):
85 def test_delete(self):
84 self.log_user()
86 self.log_user()
85 username = 'newtestuserdeleteme'
87 username = 'newtestuserdeleteme'
86 password = 'test12'
88 password = 'test12'
87 name = 'name'
89 name = 'name'
88 lastname = 'lastname'
90 lastname = 'lastname'
89 email = 'todeletemail@mail.com'
91 email = 'todeletemail@mail.com'
90
92
91 response = self.app.post(url('users'), {'username':username,
93 response = self.app.post(url('users'), {'username':username,
92 'password':password,
94 'password':password,
95 'password_confirmation':password,
93 'name':name,
96 'name':name,
94 'active':True,
97 'active':True,
95 'lastname':lastname,
98 'lastname':lastname,
96 'email':email})
99 'email':email})
97
100
98 response = response.follow()
101 response = response.follow()
99
102
100 new_user = self.sa.query(User).filter(User.username == username).one()
103 new_user = self.sa.query(User).filter(User.username == username).one()
101 response = self.app.delete(url('user', id=new_user.user_id))
104 response = self.app.delete(url('user', id=new_user.user_id))
102
105
103 assert """successfully deleted user""" in response.session['flash'][0], 'No info about user deletion'
106 assert """successfully deleted user""" in response.session['flash'][0], 'No info about user deletion'
104
107
105
108
106 def test_delete_browser_fakeout(self):
109 def test_delete_browser_fakeout(self):
107 response = self.app.post(url('user', id=1), params=dict(_method='delete'))
110 response = self.app.post(url('user', id=1), params=dict(_method='delete'))
108
111
109 def test_show(self):
112 def test_show(self):
110 response = self.app.get(url('user', id=1))
113 response = self.app.get(url('user', id=1))
111
114
112 def test_show_as_xml(self):
115 def test_show_as_xml(self):
113 response = self.app.get(url('formatted_user', id=1, format='xml'))
116 response = self.app.get(url('formatted_user', id=1, format='xml'))
114
117
115 def test_edit(self):
118 def test_edit(self):
116 response = self.app.get(url('edit_user', id=1))
119 response = self.app.get(url('edit_user', id=1))
117
120
118 def test_edit_as_xml(self):
121 def test_edit_as_xml(self):
119 response = self.app.get(url('formatted_edit_user', id=1, format='xml'))
122 response = self.app.get(url('formatted_edit_user', id=1, format='xml'))
@@ -1,412 +1,401 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.tests.test_hg_operations
3 rhodecode.tests.test_hg_operations
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Test suite for making push/pull operations
6 Test suite for making push/pull operations
7
7
8 :created_on: Dec 30, 2010
8 :created_on: Dec 30, 2010
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :license: GPLv3, see COPYING for more details.
10 :license: GPLv3, see COPYING for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
24
25 import os
25 import os
26 import time
26 import time
27 import sys
27 import sys
28 import shutil
28 import shutil
29 import logging
29 import logging
30
30
31 from os.path import join as jn
31 from os.path import join as jn
32 from os.path import dirname as dn
32 from os.path import dirname as dn
33
33
34 from tempfile import _RandomNameSequence
34 from tempfile import _RandomNameSequence
35 from subprocess import Popen, PIPE
35 from subprocess import Popen, PIPE
36
36
37 from paste.deploy import appconfig
37 from paste.deploy import appconfig
38 from pylons import config
38 from pylons import config
39 from sqlalchemy import engine_from_config
39 from sqlalchemy import engine_from_config
40
40
41 from rhodecode.lib.utils import add_cache
41 from rhodecode.lib.utils import add_cache
42 from rhodecode.model import init_model
42 from rhodecode.model import init_model
43 from rhodecode.model import meta
43 from rhodecode.model import meta
44 from rhodecode.model.db import User, Repository, UserLog
44 from rhodecode.model.db import User, Repository, UserLog
45 from rhodecode.lib.auth import get_crypt_password
45 from rhodecode.lib.auth import get_crypt_password
46
46
47 from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO
47 from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO
48 from rhodecode.config.environment import load_environment
48 from rhodecode.config.environment import load_environment
49
49
50 rel_path = dn(dn(dn(os.path.abspath(__file__))))
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 load_environment(conf.global_conf, conf.local_conf)
53 load_environment(conf.global_conf, conf.local_conf)
53
54
54 add_cache(conf)
55 add_cache(conf)
55
56
56 USER = 'test_admin'
57 USER = 'test_admin'
57 PASS = 'test12'
58 PASS = 'test12'
58 HOST = '127.0.0.1:5000'
59 HOST = '127.0.0.1:5000'
59 DEBUG = True if sys.argv[1:] else False
60 DEBUG = False
60 print 'DEBUG:', DEBUG
61 print 'DEBUG:', DEBUG
61 log = logging.getLogger(__name__)
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 class Command(object):
68 class Command(object):
65
69
66 def __init__(self, cwd):
70 def __init__(self, cwd):
67 self.cwd = cwd
71 self.cwd = cwd
68
72
69 def execute(self, cmd, *args):
73 def execute(self, cmd, *args):
70 """Runs command on the system with given ``args``.
74 """Runs command on the system with given ``args``.
71 """
75 """
72
76
73 command = cmd + ' ' + ' '.join(args)
77 command = cmd + ' ' + ' '.join(args)
74 log.debug('Executing %s' % command)
78 log.debug('Executing %s' % command)
75 if DEBUG:
79 if DEBUG:
76 print command
80 print command
77 p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
81 p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
78 stdout, stderr = p.communicate()
82 stdout, stderr = p.communicate()
79 if DEBUG:
83 if DEBUG:
80 print stdout, stderr
84 print stdout, stderr
81 return stdout, stderr
85 return stdout, stderr
82
86
83
87
84 def test_wrapp(func):
88 def test_wrapp(func):
85
89
86 def __wrapp(*args, **kwargs):
90 def __wrapp(*args, **kwargs):
87 print '>>>%s' % func.__name__
91 print '>>>%s' % func.__name__
88 try:
92 try:
89 res = func(*args, **kwargs)
93 res = func(*args, **kwargs)
90 except Exception, e:
94 except Exception, e:
91 print ('###############\n-'
95 print ('###############\n-'
92 '--%s failed %s--\n'
96 '--%s failed %s--\n'
93 '###############\n' % (func.__name__, e))
97 '###############\n' % (func.__name__, e))
94 sys.exit()
98 sys.exit()
95 print '++OK++'
99 print '++OK++'
96 return res
100 return res
97 return __wrapp
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 def create_test_user(force=True):
104 def create_test_user(force=True):
107 print '\tcreating test user'
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 if force and user is not None:
109 if force and user is not None:
113 print '\tremoving current user'
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 sa.delete(repo)
112 sa.delete(repo)
116 sa.delete(user)
113 sa.delete(user)
117 sa.commit()
114 sa.commit()
118
115
119 if user is None or force:
116 if user is None or force:
120 print '\tcreating new one'
117 print '\tcreating new one'
121 new_usr = User()
118 new_usr = User()
122 new_usr.username = USER
119 new_usr.username = USER
123 new_usr.password = get_crypt_password(PASS)
120 new_usr.password = get_crypt_password(PASS)
124 new_usr.email = 'mail@mail.com'
121 new_usr.email = 'mail@mail.com'
125 new_usr.name = 'test'
122 new_usr.name = 'test'
126 new_usr.lastname = 'lasttestname'
123 new_usr.lastname = 'lasttestname'
127 new_usr.active = True
124 new_usr.active = True
128 new_usr.admin = True
125 new_usr.admin = True
129 sa.add(new_usr)
126 sa.add(new_usr)
130 sa.commit()
127 sa.commit()
131
128
132 print '\tdone'
129 print '\tdone'
133
130
134
131
135 def create_test_repo(force=True):
132 def create_test_repo(force=True):
136 from rhodecode.model.repo import RepoModel
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 if user is None:
136 if user is None:
141 raise Exception('user not found')
137 raise Exception('user not found')
142
138
143
139
144 repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar()
140 repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar()
145
141
146 if repo is None:
142 if repo is None:
147 print '\trepo not found creating'
143 print '\trepo not found creating'
148
144
149 form_data = {'repo_name':HG_REPO,
145 form_data = {'repo_name':HG_REPO,
150 'repo_type':'hg',
146 'repo_type':'hg',
151 'private':False,
147 'private':False,
152 'clone_uri':'' }
148 'clone_uri':'' }
153 rm = RepoModel(sa)
149 rm = RepoModel(sa)
154 rm.base_path = '/home/hg'
150 rm.base_path = '/home/hg'
155 rm.create(form_data, user)
151 rm.create(form_data, user)
156
152
157
153
158 def set_anonymous_access(enable=True):
154 def set_anonymous_access(enable=True):
159 sa = get_session()
155 user = User.get_by_username('default')
160 user = sa.query(User).filter(User.username == 'default').one()
161 sa.expire(user)
162 user.active = enable
156 user.active = enable
163 sa.add(user)
157 sa.add(user)
164 sa.commit()
158 sa.commit()
165 sa.remove()
166 import time;time.sleep(3)
167 print '\tanonymous access is now:', enable
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 def get_anonymous_access():
163 def get_anonymous_access():
171 sa = get_session()
164 user = User.get_by_username('default')
172 obj1 = sa.query(User).filter(User.username == 'default').one()
165 return user.active
173 sa.expire(obj1)
174 return obj1.active
175
166
176
167
177 #==============================================================================
168 #==============================================================================
178 # TESTS
169 # TESTS
179 #==============================================================================
170 #==============================================================================
180 @test_wrapp
171 @test_wrapp
181 def test_clone_with_credentials(no_errors=False):
172 def test_clone_with_credentials(no_errors=False):
182 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
173 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
183
174
184 try:
175 try:
185 shutil.rmtree(path, ignore_errors=True)
176 shutil.rmtree(path, ignore_errors=True)
186 os.makedirs(path)
177 os.makedirs(path)
187 #print 'made dirs %s' % jn(path)
178 #print 'made dirs %s' % jn(path)
188 except OSError:
179 except OSError:
189 raise
180 raise
190
181
191 print '\tchecking if anonymous access is enabled'
182 print '\tchecking if anonymous access is enabled'
192 anonymous_access = get_anonymous_access()
183 anonymous_access = get_anonymous_access()
193 if anonymous_access:
184 if anonymous_access:
194 print '\tenabled, disabling it '
185 print '\tenabled, disabling it '
195 set_anonymous_access(enable=False)
186 set_anonymous_access(enable=False)
196 time.sleep(1)
187 time.sleep(1)
197
188
198 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
189 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
199 {'user':USER,
190 {'user':USER,
200 'pass':PASS,
191 'pass':PASS,
201 'host':HOST,
192 'host':HOST,
202 'cloned_repo':HG_REPO,
193 'cloned_repo':HG_REPO,
203 'dest':path}
194 'dest':path}
204
195
205 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
196 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
206
197
207 if no_errors is False:
198 if no_errors is False:
208 assert """adding file changes""" in stdout, 'no messages about cloning'
199 assert """adding file changes""" in stdout, 'no messages about cloning'
209 assert """abort""" not in stderr , 'got error from clone'
200 assert """abort""" not in stderr , 'got error from clone'
210
201
211
202
212 @test_wrapp
203 @test_wrapp
213 def test_clone_anonymous():
204 def test_clone_anonymous():
214 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
205 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
215
206
216 try:
207 try:
217 shutil.rmtree(path, ignore_errors=True)
208 shutil.rmtree(path, ignore_errors=True)
218 os.makedirs(path)
209 os.makedirs(path)
219 #print 'made dirs %s' % jn(path)
210 #print 'made dirs %s' % jn(path)
220 except OSError:
211 except OSError:
221 raise
212 raise
222
213
223
214
224 print '\tchecking if anonymous access is enabled'
215 print '\tchecking if anonymous access is enabled'
225 anonymous_access = get_anonymous_access()
216 anonymous_access = get_anonymous_access()
226 if not anonymous_access:
217 if not anonymous_access:
227 print '\tnot enabled, enabling it '
218 print '\tnot enabled, enabling it '
228 set_anonymous_access(enable=True)
219 set_anonymous_access(enable=True)
229 time.sleep(1)
220 time.sleep(1)
230
221
231 clone_url = 'http://%(host)s/%(cloned_repo)s %(dest)s' % \
222 clone_url = 'http://%(host)s/%(cloned_repo)s %(dest)s' % \
232 {'user':USER,
223 {'user':USER,
233 'pass':PASS,
224 'pass':PASS,
234 'host':HOST,
225 'host':HOST,
235 'cloned_repo':HG_REPO,
226 'cloned_repo':HG_REPO,
236 'dest':path}
227 'dest':path}
237
228
238 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
229 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
239
230
240 assert """adding file changes""" in stdout, 'no messages about cloning'
231 assert """adding file changes""" in stdout, 'no messages about cloning'
241 assert """abort""" not in stderr , 'got error from clone'
232 assert """abort""" not in stderr , 'got error from clone'
242
233
243 #disable if it was enabled
234 #disable if it was enabled
244 if not anonymous_access:
235 if not anonymous_access:
245 print '\tdisabling anonymous access'
236 print '\tdisabling anonymous access'
246 set_anonymous_access(enable=False)
237 set_anonymous_access(enable=False)
247
238
248 @test_wrapp
239 @test_wrapp
249 def test_clone_wrong_credentials():
240 def test_clone_wrong_credentials():
250 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
241 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
251
242
252 try:
243 try:
253 shutil.rmtree(path, ignore_errors=True)
244 shutil.rmtree(path, ignore_errors=True)
254 os.makedirs(path)
245 os.makedirs(path)
255 #print 'made dirs %s' % jn(path)
246 #print 'made dirs %s' % jn(path)
256 except OSError:
247 except OSError:
257 raise
248 raise
258
249
259 print '\tchecking if anonymous access is enabled'
250 print '\tchecking if anonymous access is enabled'
260 anonymous_access = get_anonymous_access()
251 anonymous_access = get_anonymous_access()
261 if anonymous_access:
252 if anonymous_access:
262 print '\tenabled, disabling it '
253 print '\tenabled, disabling it '
263 set_anonymous_access(enable=False)
254 set_anonymous_access(enable=False)
264
255
265 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
256 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
266 {'user':USER + 'error',
257 {'user':USER + 'error',
267 'pass':PASS,
258 'pass':PASS,
268 'host':HOST,
259 'host':HOST,
269 'cloned_repo':HG_REPO,
260 'cloned_repo':HG_REPO,
270 'dest':path}
261 'dest':path}
271
262
272 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
263 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
273
264
274 if not """abort: authorization failed""" in stderr:
265 if not """abort: authorization failed""" in stderr:
275 raise Exception('Failure')
266 raise Exception('Failure')
276
267
277 @test_wrapp
268 @test_wrapp
278 def test_pull():
269 def test_pull():
279 pass
270 pass
280
271
281 @test_wrapp
272 @test_wrapp
282 def test_push_modify_file(f_name='setup.py'):
273 def test_push_modify_file(f_name='setup.py'):
283 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
274 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
284 modified_file = jn(TESTS_TMP_PATH, HG_REPO, f_name)
275 modified_file = jn(TESTS_TMP_PATH, HG_REPO, f_name)
285 for i in xrange(5):
276 for i in xrange(5):
286 cmd = """echo 'added_line%s' >> %s""" % (i, modified_file)
277 cmd = """echo 'added_line%s' >> %s""" % (i, modified_file)
287 Command(cwd).execute(cmd)
278 Command(cwd).execute(cmd)
288
279
289 cmd = """hg ci -m 'changed file %s' %s """ % (i, modified_file)
280 cmd = """hg ci -m 'changed file %s' %s """ % (i, modified_file)
290 Command(cwd).execute(cmd)
281 Command(cwd).execute(cmd)
291
282
292 Command(cwd).execute('hg push %s' % jn(TESTS_TMP_PATH, HG_REPO))
283 Command(cwd).execute('hg push %s' % jn(TESTS_TMP_PATH, HG_REPO))
293
284
294 @test_wrapp
285 @test_wrapp
295 def test_push_new_file(commits=15, with_clone=True):
286 def test_push_new_file(commits=15, with_clone=True):
296
287
297 if with_clone:
288 if with_clone:
298 test_clone_with_credentials(no_errors=True)
289 test_clone_with_credentials(no_errors=True)
299
290
300 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
291 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
301 added_file = jn(path, '%ssetupążźć.py' % _RandomNameSequence().next())
292 added_file = jn(path, '%ssetupążźć.py' % _RandomNameSequence().next())
302
293
303 Command(cwd).execute('touch %s' % added_file)
294 Command(cwd).execute('touch %s' % added_file)
304
295
305 Command(cwd).execute('hg add %s' % added_file)
296 Command(cwd).execute('hg add %s' % added_file)
306
297
307 for i in xrange(commits):
298 for i in xrange(commits):
308 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
299 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
309 Command(cwd).execute(cmd)
300 Command(cwd).execute(cmd)
310
301
311 cmd = """hg ci -m 'commited new %s' -u '%s' %s """ % (i,
302 cmd = """hg ci -m 'commited new %s' -u '%s' %s """ % (i,
312 'Marcin Kuźminski <marcin@python-blog.com>',
303 'Marcin Kuźminski <marcin@python-blog.com>',
313 added_file)
304 added_file)
314 Command(cwd).execute(cmd)
305 Command(cwd).execute(cmd)
315
306
316 push_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
307 push_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
317 {'user':USER,
308 {'user':USER,
318 'pass':PASS,
309 'pass':PASS,
319 'host':HOST,
310 'host':HOST,
320 'cloned_repo':HG_REPO,
311 'cloned_repo':HG_REPO,
321 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
312 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
322
313
323 Command(cwd).execute('hg push --verbose --debug %s' % push_url)
314 Command(cwd).execute('hg push --verbose --debug %s' % push_url)
324
315
325 @test_wrapp
316 @test_wrapp
326 def test_push_wrong_credentials():
317 def test_push_wrong_credentials():
327 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
318 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
328 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
319 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
329 {'user':USER + 'xxx',
320 {'user':USER + 'xxx',
330 'pass':PASS,
321 'pass':PASS,
331 'host':HOST,
322 'host':HOST,
332 'cloned_repo':HG_REPO,
323 'cloned_repo':HG_REPO,
333 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
324 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
334
325
335 modified_file = jn(TESTS_TMP_PATH, HG_REPO, 'setup.py')
326 modified_file = jn(TESTS_TMP_PATH, HG_REPO, 'setup.py')
336 for i in xrange(5):
327 for i in xrange(5):
337 cmd = """echo 'added_line%s' >> %s""" % (i, modified_file)
328 cmd = """echo 'added_line%s' >> %s""" % (i, modified_file)
338 Command(cwd).execute(cmd)
329 Command(cwd).execute(cmd)
339
330
340 cmd = """hg ci -m 'commited %s' %s """ % (i, modified_file)
331 cmd = """hg ci -m 'commited %s' %s """ % (i, modified_file)
341 Command(cwd).execute(cmd)
332 Command(cwd).execute(cmd)
342
333
343 Command(cwd).execute('hg push %s' % clone_url)
334 Command(cwd).execute('hg push %s' % clone_url)
344
335
345 @test_wrapp
336 @test_wrapp
346 def test_push_wrong_path():
337 def test_push_wrong_path():
347 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
338 cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
348 added_file = jn(path, 'somefile.py')
339 added_file = jn(path, 'somefile.py')
349
340
350 try:
341 try:
351 shutil.rmtree(path, ignore_errors=True)
342 shutil.rmtree(path, ignore_errors=True)
352 os.makedirs(path)
343 os.makedirs(path)
353 print '\tmade dirs %s' % jn(path)
344 print '\tmade dirs %s' % jn(path)
354 except OSError:
345 except OSError:
355 raise
346 raise
356
347
357 Command(cwd).execute("""echo '' > %s""" % added_file)
348 Command(cwd).execute("""echo '' > %s""" % added_file)
358 Command(cwd).execute("""hg init %s""" % path)
349 Command(cwd).execute("""hg init %s""" % path)
359 Command(cwd).execute("""hg add %s""" % added_file)
350 Command(cwd).execute("""hg add %s""" % added_file)
360
351
361 for i in xrange(2):
352 for i in xrange(2):
362 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
353 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
363 Command(cwd).execute(cmd)
354 Command(cwd).execute(cmd)
364
355
365 cmd = """hg ci -m 'commited new %s' %s """ % (i, added_file)
356 cmd = """hg ci -m 'commited new %s' %s """ % (i, added_file)
366 Command(cwd).execute(cmd)
357 Command(cwd).execute(cmd)
367
358
368 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
359 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
369 {'user':USER,
360 {'user':USER,
370 'pass':PASS,
361 'pass':PASS,
371 'host':HOST,
362 'host':HOST,
372 'cloned_repo':HG_REPO + '_error',
363 'cloned_repo':HG_REPO + '_error',
373 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
364 'dest':jn(TESTS_TMP_PATH, HG_REPO)}
374
365
375 stdout, stderr = Command(cwd).execute('hg push %s' % clone_url)
366 stdout, stderr = Command(cwd).execute('hg push %s' % clone_url)
376 if not """abort: HTTP Error 403: Forbidden""" in stderr:
367 if not """abort: HTTP Error 403: Forbidden""" in stderr:
377 raise Exception('Failure')
368 raise Exception('Failure')
378
369
379 @test_wrapp
370 @test_wrapp
380 def get_logs():
371 def get_logs():
381 sa = get_session()
372 return UserLog.query().all()
382 return len(sa.query(UserLog).all())
383
373
384 @test_wrapp
374 @test_wrapp
385 def test_logs(initial):
375 def test_logs(initial):
386 sa = get_session()
376 logs = UserLog.query().all()
387 logs = sa.query(UserLog).all()
377 operations = 4
388 operations = 7
378 if len(initial) + operations != len(logs):
389 if initial + operations != len(logs):
379 raise Exception("missing number of logs initial:%s vs current:%s" % \
390 raise Exception("missing number of logs %s vs %s" % (initial, len(logs)))
380 (len(initial), len(logs)))
391
381
392
382
393 if __name__ == '__main__':
383 if __name__ == '__main__':
394 create_test_user(force=False)
384 create_test_user(force=False)
395 create_test_repo()
385 create_test_repo()
396
386
397 initial_logs = get_logs()
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 test_clone_with_credentials()
391 test_clone_with_credentials()
401 test_clone_wrong_credentials()
392 test_clone_wrong_credentials()
402
393
403
404 test_push_new_file(commits=2, with_clone=True)
394 test_push_new_file(commits=2, with_clone=True)
405
395
406 test_clone_anonymous()
396 test_clone_anonymous()
407 test_push_wrong_path()
397 test_push_wrong_path()
408
398
409
410 test_push_wrong_credentials()
399 test_push_wrong_credentials()
411
400
412 test_logs(initial_logs)
401 test_logs(initial_logs)
General Comments 0
You need to be logged in to leave comments. Login now