##// END OF EJS Templates
implemented cache for repeated queries in simplehg mercurial requests
marcink -
r343:64849630 default
parent child Browse files
Show More
@@ -1,133 +1,135 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # pylons_app - Pylons environment configuration #
3 # pylons_app - 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 ############################################
10 ############################################
11 ## Uncomment and replace with the address ##
11 ## Uncomment and replace with the address ##
12 ## which should receive any error reports ##
12 ## which should receive any error reports ##
13 ############################################
13 ############################################
14 #email_to = admin@localhost
14 #email_to = admin@localhost
15 #smtp_server = mail.server.com
15 #smtp_server = mail.server.com
16 #error_email_from = paste_error@localhost
16 #error_email_from = paste_error@localhost
17 #smtp_username =
17 #smtp_username =
18 #smtp_password =
18 #smtp_password =
19 #error_message = 'mercurial crash !'
19 #error_message = 'mercurial crash !'
20
20
21 [server:main]
21 [server:main]
22 ##nr of threads to spawn
22 ##nr of threads to spawn
23 threadpool_workers = 5
23 threadpool_workers = 5
24
24
25 ##max request before
25 ##max request before
26 threadpool_max_requests = 2
26 threadpool_max_requests = 2
27
27
28 ##option to use threads of process
28 ##option to use threads of process
29 use_threadpool = true
29 use_threadpool = true
30
30
31 use = egg:Paste#http
31 use = egg:Paste#http
32 host = 127.0.0.1
32 host = 127.0.0.1
33 port = 5000
33 port = 5000
34
34
35 [app:main]
35 [app:main]
36 use = egg:pylons_app
36 use = egg:pylons_app
37 full_stack = true
37 full_stack = true
38 static_files = true
38 static_files = true
39 lang=en
39 lang=en
40 cache_dir = %(here)s/data
40 cache_dir = %(here)s/data
41
41
42 ####################################
42 ####################################
43 ### BEAKER CACHE ####
43 ### BEAKER CACHE ####
44 ####################################
44 ####################################
45 beaker.cache.data_dir=/%(here)s/data/cache/data
45 beaker.cache.data_dir=/%(here)s/data/cache/data
46 beaker.cache.lock_dir=/%(here)s/data/cache/lock
46 beaker.cache.lock_dir=/%(here)s/data/cache/lock
47 beaker.cache.regions=short_term,long_term
47 beaker.cache.regions=super_short_term,short_term,long_term
48 beaker.cache.long_term.type=memory
48 beaker.cache.long_term.type=memory
49 beaker.cache.long_term.expire=36000
49 beaker.cache.long_term.expire=36000
50 beaker.cache.short_term.type=memory
50 beaker.cache.short_term.type=memory
51 beaker.cache.short_term.expire=60
51 beaker.cache.short_term.expire=60
52 beaker.cache.super_short_term.type=memory
53 beaker.cache.super_short_term.expire=10
52
54
53 ################################################################################
55 ################################################################################
54 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
56 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
55 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
57 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
56 ## execute malicious code after an exception is raised. ##
58 ## execute malicious code after an exception is raised. ##
57 ################################################################################
59 ################################################################################
58 #set debug = false
60 #set debug = false
59
61
60 ##################################
62 ##################################
61 ### LOGVIEW CONFIG ###
63 ### LOGVIEW CONFIG ###
62 ##################################
64 ##################################
63 logview.sqlalchemy = #faa
65 logview.sqlalchemy = #faa
64 logview.pylons.templating = #bfb
66 logview.pylons.templating = #bfb
65 logview.pylons.util = #eee
67 logview.pylons.util = #eee
66
68
67 #########################################################
69 #########################################################
68 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
70 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
69 #########################################################
71 #########################################################
70 sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db
72 sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db
71 #sqlalchemy.db1.echo = False
73 #sqlalchemy.db1.echo = False
72 #sqlalchemy.db1.pool_recycle = 3600
74 #sqlalchemy.db1.pool_recycle = 3600
73 sqlalchemy.convert_unicode = true
75 sqlalchemy.convert_unicode = true
74
76
75 ################################
77 ################################
76 ### LOGGING CONFIGURATION ####
78 ### LOGGING CONFIGURATION ####
77 ################################
79 ################################
78 [loggers]
80 [loggers]
79 keys = root, routes, pylons_app, sqlalchemy
81 keys = root, routes, pylons_app, sqlalchemy
80
82
81 [handlers]
83 [handlers]
82 keys = console
84 keys = console
83
85
84 [formatters]
86 [formatters]
85 keys = generic,color_formatter
87 keys = generic,color_formatter
86
88
87 #############
89 #############
88 ## LOGGERS ##
90 ## LOGGERS ##
89 #############
91 #############
90 [logger_root]
92 [logger_root]
91 level = NOTSET
93 level = NOTSET
92 handlers = console
94 handlers = console
93
95
94 [logger_routes]
96 [logger_routes]
95 level = DEBUG
97 level = DEBUG
96 handlers = console
98 handlers = console
97 qualname = routes.middleware
99 qualname = routes.middleware
98 # "level = DEBUG" logs the route matched and routing variables.
100 # "level = DEBUG" logs the route matched and routing variables.
99
101
100 [logger_pylons_app]
102 [logger_pylons_app]
101 level = DEBUG
103 level = DEBUG
102 handlers = console
104 handlers = console
103 qualname = pylons_app
105 qualname = pylons_app
104 propagate = 0
106 propagate = 0
105
107
106 [logger_sqlalchemy]
108 [logger_sqlalchemy]
107 level = ERROR
109 level = ERROR
108 handlers = console
110 handlers = console
109 qualname = sqlalchemy.engine
111 qualname = sqlalchemy.engine
110 propagate = 0
112 propagate = 0
111
113
112 ##############
114 ##############
113 ## HANDLERS ##
115 ## HANDLERS ##
114 ##############
116 ##############
115
117
116 [handler_console]
118 [handler_console]
117 class = StreamHandler
119 class = StreamHandler
118 args = (sys.stderr,)
120 args = (sys.stderr,)
119 level = NOTSET
121 level = NOTSET
120 formatter = color_formatter
122 formatter = color_formatter
121
123
122 ################
124 ################
123 ## FORMATTERS ##
125 ## FORMATTERS ##
124 ################
126 ################
125
127
126 [formatter_generic]
128 [formatter_generic]
127 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
129 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
128 datefmt = %Y-%m-%d %H:%M:%S
130 datefmt = %Y-%m-%d %H:%M:%S
129
131
130 [formatter_color_formatter]
132 [formatter_color_formatter]
131 class=pylons_app.lib.colored_formatter.ColorFormatter
133 class=pylons_app.lib.colored_formatter.ColorFormatter
132 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
134 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
133 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
135 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,134 +1,135 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # pylons_app - Pylons environment configuration #
3 # pylons_app - 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 ############################################
10 ############################################
11 ## Uncomment and replace with the address ##
11 ## Uncomment and replace with the address ##
12 ## which should receive any error reports ##
12 ## which should receive any error reports ##
13 ############################################
13 ############################################
14 #email_to = admin@localhost
14 #email_to = admin@localhost
15 #smtp_server = mail.server.com
15 #smtp_server = mail.server.com
16 #error_email_from = paste_error@localhost
16 #error_email_from = paste_error@localhost
17 #smtp_username =
17 #smtp_username =
18 #smtp_password =
18 #smtp_password =
19 #error_message = 'mercurial crash !'
19 #error_message = 'mercurial crash !'
20
20
21 [server:main]
21 [server:main]
22 ##nr of threads to spawn
22 ##nr of threads to spawn
23 threadpool_workers = 5
23 threadpool_workers = 5
24
24
25 ##max request before
25 ##max request before
26 threadpool_max_requests = 2
26 threadpool_max_requests = 2
27
27
28 ##option to use threads of process
28 ##option to use threads of process
29 use_threadpool = true
29 use_threadpool = true
30
30
31 use = egg:Paste#http
31 use = egg:Paste#http
32 host = 127.0.0.1
32 host = 127.0.0.1
33 port = 8001
33 port = 8001
34
34
35 [app:main]
35 [app:main]
36 use = egg:pylons_app
36 use = egg:pylons_app
37 full_stack = true
37 full_stack = true
38 static_files = false
38 static_files = false
39 lang=en
39 lang=en
40 cache_dir = %(here)s/data
40 cache_dir = %(here)s/data
41
41
42
43 ####################################
42 ####################################
44 ### BEAKER CACHE ####
43 ### BEAKER CACHE ####
45 ####################################
44 ####################################
46 beaker.cache.data_dir=/%(here)s/data/cache/data
45 beaker.cache.data_dir=/%(here)s/data/cache/data
47 beaker.cache.lock_dir=/%(here)s/data/cache/lock
46 beaker.cache.lock_dir=/%(here)s/data/cache/lock
48 beaker.cache.regions=short_term,long_term
47 beaker.cache.regions=super_short_term,short_term,long_term
49 beaker.cache.long_term.type=memory
48 beaker.cache.long_term.type=memory
50 beaker.cache.long_term.expire=36000
49 beaker.cache.long_term.expire=36000
51 beaker.cache.short_term.type=memory
50 beaker.cache.short_term.type=memory
52 beaker.cache.short_term.expire=60
51 beaker.cache.short_term.expire=60
52 beaker.cache.super_short_term.type=memory
53 beaker.cache.super_short_term.expire=10
53
54
54 ################################################################################
55 ################################################################################
55 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
56 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
56 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
57 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
57 ## execute malicious code after an exception is raised. ##
58 ## execute malicious code after an exception is raised. ##
58 ################################################################################
59 ################################################################################
59 set debug = false
60 set debug = false
60
61
61 ##################################
62 ##################################
62 ### LOGVIEW CONFIG ###
63 ### LOGVIEW CONFIG ###
63 ##################################
64 ##################################
64 logview.sqlalchemy = #faa
65 logview.sqlalchemy = #faa
65 logview.pylons.templating = #bfb
66 logview.pylons.templating = #bfb
66 logview.pylons.util = #eee
67 logview.pylons.util = #eee
67
68
68 #########################################################
69 #########################################################
69 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
70 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
70 #########################################################
71 #########################################################
71 sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db
72 sqlalchemy.db1.url = sqlite:///%(here)s/hg_app.db
72 #sqlalchemy.db1.echo = False
73 #sqlalchemy.db1.echo = False
73 #sqlalchemy.db1.pool_recycle = 3600
74 #sqlalchemy.db1.pool_recycle = 3600
74 sqlalchemy.convert_unicode = true
75 sqlalchemy.convert_unicode = true
75
76
76 ################################
77 ################################
77 ### LOGGING CONFIGURATION ####
78 ### LOGGING CONFIGURATION ####
78 ################################
79 ################################
79 [loggers]
80 [loggers]
80 keys = root, routes, pylons_app, sqlalchemy
81 keys = root, routes, pylons_app, sqlalchemy
81
82
82 [handlers]
83 [handlers]
83 keys = console
84 keys = console
84
85
85 [formatters]
86 [formatters]
86 keys = generic,color_formatter
87 keys = generic,color_formatter
87
88
88 #############
89 #############
89 ## LOGGERS ##
90 ## LOGGERS ##
90 #############
91 #############
91 [logger_root]
92 [logger_root]
92 level = INFO
93 level = INFO
93 handlers = console
94 handlers = console
94
95
95 [logger_routes]
96 [logger_routes]
96 level = INFO
97 level = INFO
97 handlers = console
98 handlers = console
98 qualname = routes.middleware
99 qualname = routes.middleware
99 # "level = DEBUG" logs the route matched and routing variables.
100 # "level = DEBUG" logs the route matched and routing variables.
100
101
101 [logger_pylons_app]
102 [logger_pylons_app]
102 level = DEBUG
103 level = DEBUG
103 handlers = console
104 handlers = console
104 qualname = pylons_app
105 qualname = pylons_app
105 propagate = 0
106 propagate = 0
106
107
107 [logger_sqlalchemy]
108 [logger_sqlalchemy]
108 level = ERROR
109 level = ERROR
109 handlers = console
110 handlers = console
110 qualname = sqlalchemy.engine
111 qualname = sqlalchemy.engine
111 propagate = 0
112 propagate = 0
112
113
113 ##############
114 ##############
114 ## HANDLERS ##
115 ## HANDLERS ##
115 ##############
116 ##############
116
117
117 [handler_console]
118 [handler_console]
118 class = StreamHandler
119 class = StreamHandler
119 args = (sys.stderr,)
120 args = (sys.stderr,)
120 level = NOTSET
121 level = NOTSET
121 formatter = color_formatter
122 formatter = color_formatter
122
123
123 ################
124 ################
124 ## FORMATTERS ##
125 ## FORMATTERS ##
125 ################
126 ################
126
127
127 [formatter_generic]
128 [formatter_generic]
128 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
129 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
129 datefmt = %Y-%m-%d %H:%M:%S
130 datefmt = %Y-%m-%d %H:%M:%S
130
131
131 [formatter_color_formatter]
132 [formatter_color_formatter]
132 class=pylons_app.lib.colored_formatter.ColorFormatter
133 class=pylons_app.lib.colored_formatter.ColorFormatter
133 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
134 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
134 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
135 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,399 +1,405 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # authentication and permission libraries
3 # authentication and permission libraries
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 4, 2010
21 Created on April 4, 2010
22
22
23 @author: marcink
23 @author: marcink
24 """
24 """
25
25 from beaker.cache import cache_region
26 from functools import wraps
26 from functools import wraps
27 from pylons import session, url, request
27 from pylons import config, session, url, request
28 from pylons.controllers.util import abort, redirect
28 from pylons.controllers.util import abort, redirect
29 from pylons_app.lib.utils import get_repo_slug
29 from pylons_app.model import meta
30 from pylons_app.model import meta
30 from pylons_app.model.db import User, Repo2Perm, Repository, Permission
31 from pylons_app.model.db import User, Repo2Perm, Repository, Permission
31 from pylons_app.lib.utils import get_repo_slug
32 from sqlalchemy.exc import OperationalError
32 from sqlalchemy.exc import OperationalError
33 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
33 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
34 import crypt
34 import crypt
35 import logging
35 import logging
36 from pylons import config
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39 def get_crypt_password(password):
39 def get_crypt_password(password):
40 """
40 """
41 Cryptographic function used for password hashing
41 Cryptographic function used for password hashing
42 @param password: password to hash
42 @param password: password to hash
43 """
43 """
44 return crypt.crypt(password, '6a')
44 return crypt.crypt(password, '6a')
45
45
46
47 @cache_region('super_short_term', 'cached_user')
48 def get_user_cached(username):
49 sa = meta.Session
50 user = sa.query(User).filter(User.username == username).one()
51 return user
52
46 def authfunc(environ, username, password):
53 def authfunc(environ, username, password):
47 sa = meta.Session
48 password_crypt = get_crypt_password(password)
54 password_crypt = get_crypt_password(password)
49 try:
55 try:
50 user = sa.query(User).filter(User.username == username).one()
56 user = get_user_cached(username)
51 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
57 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
52 log.error(e)
58 log.error(e)
53 user = None
59 user = None
54
60
55 if user:
61 if user:
56 if user.active:
62 if user.active:
57 if user.username == username and user.password == password_crypt:
63 if user.username == username and user.password == password_crypt:
58 log.info('user %s authenticated correctly', username)
64 log.info('user %s authenticated correctly', username)
59 return True
65 return True
60 else:
66 else:
61 log.error('user %s is disabled', username)
67 log.error('user %s is disabled', username)
62
68
63 return False
69 return False
64
70
65 class AuthUser(object):
71 class AuthUser(object):
66 """
72 """
67 A simple object that handles a mercurial username for authentication
73 A simple object that handles a mercurial username for authentication
68 """
74 """
69 def __init__(self):
75 def __init__(self):
70 self.username = 'None'
76 self.username = 'None'
71 self.user_id = None
77 self.user_id = None
72 self.is_authenticated = False
78 self.is_authenticated = False
73 self.is_admin = False
79 self.is_admin = False
74 self.permissions = {}
80 self.permissions = {}
75
81
76
82
77 def set_available_permissions(config):
83 def set_available_permissions(config):
78 """
84 """
79 This function will propagate pylons globals with all available defined
85 This function will propagate pylons globals with all available defined
80 permission given in db. We don't wannt to check each time from db for new
86 permission given in db. We don't wannt to check each time from db for new
81 permissions since adding a new permission also requires application restart
87 permissions since adding a new permission also requires application restart
82 ie. to decorate new views with the newly created permission
88 ie. to decorate new views with the newly created permission
83 @param config:
89 @param config:
84 """
90 """
85 log.info('getting information about all available permissions')
91 log.info('getting information about all available permissions')
86 sa = meta.Session
92 sa = meta.Session
87 all_perms = sa.query(Permission).all()
93 all_perms = sa.query(Permission).all()
88 config['available_permissions'] = [x.permission_name for x in all_perms]
94 config['available_permissions'] = [x.permission_name for x in all_perms]
89
95
90 def set_base_path(config):
96 def set_base_path(config):
91 config['base_path'] = config['pylons.app_globals'].base_path
97 config['base_path'] = config['pylons.app_globals'].base_path
92
98
93 def fill_perms(user):
99 def fill_perms(user):
94 sa = meta.Session
100 sa = meta.Session
95 user.permissions['repositories'] = {}
101 user.permissions['repositories'] = {}
96
102
97 #first fetch default permissions
103 #first fetch default permissions
98 default_perms = sa.query(Repo2Perm, Repository, Permission)\
104 default_perms = sa.query(Repo2Perm, Repository, Permission)\
99 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
105 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
100 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
106 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
101 .filter(Repo2Perm.user_id == sa.query(User).filter(User.username ==
107 .filter(Repo2Perm.user_id == sa.query(User).filter(User.username ==
102 'default').one().user_id).all()
108 'default').one().user_id).all()
103
109
104 if user.is_admin:
110 if user.is_admin:
105 user.permissions['global'] = set(['hg.admin'])
111 user.permissions['global'] = set(['hg.admin'])
106 #admin have all rights full
112 #admin have all rights full
107 for perm in default_perms:
113 for perm in default_perms:
108 p = 'repository.admin'
114 p = 'repository.admin'
109 user.permissions['repositories'][perm.Repo2Perm.repository] = p
115 user.permissions['repositories'][perm.Repo2Perm.repository] = p
110
116
111 else:
117 else:
112 user.permissions['global'] = set()
118 user.permissions['global'] = set()
113 for perm in default_perms:
119 for perm in default_perms:
114 if perm.Repository.private:
120 if perm.Repository.private:
115 #disable defaults for private repos,
121 #disable defaults for private repos,
116 p = 'repository.none'
122 p = 'repository.none'
117 elif perm.Repository.user_id == user.user_id:
123 elif perm.Repository.user_id == user.user_id:
118 #set admin if owner
124 #set admin if owner
119 p = 'repository.admin'
125 p = 'repository.admin'
120 else:
126 else:
121 p = perm.Permission.permission_name
127 p = perm.Permission.permission_name
122
128
123 user.permissions['repositories'][perm.Repo2Perm.repository] = p
129 user.permissions['repositories'][perm.Repo2Perm.repository] = p
124
130
125
131
126 user_perms = sa.query(Repo2Perm, Permission, Repository)\
132 user_perms = sa.query(Repo2Perm, Permission, Repository)\
127 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
133 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
128 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
134 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
129 .filter(Repo2Perm.user_id == user.user_id).all()
135 .filter(Repo2Perm.user_id == user.user_id).all()
130 #overwrite userpermissions with defaults
136 #overwrite userpermissions with defaults
131 for perm in user_perms:
137 for perm in user_perms:
132 #set write if owner
138 #set write if owner
133 if perm.Repository.user_id == user.user_id:
139 if perm.Repository.user_id == user.user_id:
134 p = 'repository.write'
140 p = 'repository.write'
135 else:
141 else:
136 p = perm.Permission.permission_name
142 p = perm.Permission.permission_name
137 user.permissions['repositories'][perm.Repo2Perm.repository] = p
143 user.permissions['repositories'][perm.Repo2Perm.repository] = p
138 return user
144 return user
139
145
140 def get_user(session):
146 def get_user(session):
141 """
147 """
142 Gets user from session, and wraps permissions into user
148 Gets user from session, and wraps permissions into user
143 @param session:
149 @param session:
144 """
150 """
145 user = session.get('hg_app_user', AuthUser())
151 user = session.get('hg_app_user', AuthUser())
146
152
147 if user.is_authenticated:
153 if user.is_authenticated:
148 user = fill_perms(user)
154 user = fill_perms(user)
149
155
150 session['hg_app_user'] = user
156 session['hg_app_user'] = user
151 session.save()
157 session.save()
152 return user
158 return user
153
159
154 #===============================================================================
160 #===============================================================================
155 # CHECK DECORATORS
161 # CHECK DECORATORS
156 #===============================================================================
162 #===============================================================================
157 class LoginRequired(object):
163 class LoginRequired(object):
158 """
164 """
159 Must be logged in to execute this function else redirect to login page
165 Must be logged in to execute this function else redirect to login page
160 """
166 """
161
167
162 def __call__(self, func):
168 def __call__(self, func):
163 @wraps(func)
169 @wraps(func)
164 def _wrapper(*fargs, **fkwargs):
170 def _wrapper(*fargs, **fkwargs):
165 user = session.get('hg_app_user', AuthUser())
171 user = session.get('hg_app_user', AuthUser())
166 log.debug('Checking login required for user:%s', user.username)
172 log.debug('Checking login required for user:%s', user.username)
167 if user.is_authenticated:
173 if user.is_authenticated:
168 log.debug('user %s is authenticated', user.username)
174 log.debug('user %s is authenticated', user.username)
169 func(*fargs)
175 func(*fargs)
170 else:
176 else:
171 log.warn('user %s not authenticated', user.username)
177 log.warn('user %s not authenticated', user.username)
172 log.debug('redirecting to login page')
178 log.debug('redirecting to login page')
173 return redirect(url('login_home'))
179 return redirect(url('login_home'))
174
180
175 return _wrapper
181 return _wrapper
176
182
177 class PermsDecorator(object):
183 class PermsDecorator(object):
178 """
184 """
179 Base class for decorators
185 Base class for decorators
180 """
186 """
181
187
182 def __init__(self, *required_perms):
188 def __init__(self, *required_perms):
183 available_perms = config['available_permissions']
189 available_perms = config['available_permissions']
184 for perm in required_perms:
190 for perm in required_perms:
185 if perm not in available_perms:
191 if perm not in available_perms:
186 raise Exception("'%s' permission is not defined" % perm)
192 raise Exception("'%s' permission is not defined" % perm)
187 self.required_perms = set(required_perms)
193 self.required_perms = set(required_perms)
188 self.user_perms = None
194 self.user_perms = None
189
195
190 def __call__(self, func):
196 def __call__(self, func):
191 @wraps(func)
197 @wraps(func)
192 def _wrapper(*fargs, **fkwargs):
198 def _wrapper(*fargs, **fkwargs):
193 self.user_perms = session.get('hg_app_user', AuthUser()).permissions
199 self.user_perms = session.get('hg_app_user', AuthUser()).permissions
194 log.debug('checking %s permissions %s for %s',
200 log.debug('checking %s permissions %s for %s',
195 self.__class__.__name__, self.required_perms, func.__name__)
201 self.__class__.__name__, self.required_perms, func.__name__)
196
202
197 if self.check_permissions():
203 if self.check_permissions():
198 log.debug('Permission granted for %s', func.__name__)
204 log.debug('Permission granted for %s', func.__name__)
199 return func(*fargs)
205 return func(*fargs)
200
206
201 else:
207 else:
202 log.warning('Permission denied for %s', func.__name__)
208 log.warning('Permission denied for %s', func.__name__)
203 #redirect with forbidden ret code
209 #redirect with forbidden ret code
204 return abort(403)
210 return abort(403)
205 return _wrapper
211 return _wrapper
206
212
207
213
208 def check_permissions(self):
214 def check_permissions(self):
209 """
215 """
210 Dummy function for overriding
216 Dummy function for overriding
211 """
217 """
212 raise Exception('You have to write this function in child class')
218 raise Exception('You have to write this function in child class')
213
219
214 class HasPermissionAllDecorator(PermsDecorator):
220 class HasPermissionAllDecorator(PermsDecorator):
215 """
221 """
216 Checks for access permission for all given predicates. All of them have to
222 Checks for access permission for all given predicates. All of them have to
217 be meet in order to fulfill the request
223 be meet in order to fulfill the request
218 """
224 """
219
225
220 def check_permissions(self):
226 def check_permissions(self):
221 if self.required_perms.issubset(self.user_perms.get('global')):
227 if self.required_perms.issubset(self.user_perms.get('global')):
222 return True
228 return True
223 return False
229 return False
224
230
225
231
226 class HasPermissionAnyDecorator(PermsDecorator):
232 class HasPermissionAnyDecorator(PermsDecorator):
227 """
233 """
228 Checks for access permission for any of given predicates. In order to
234 Checks for access permission for any of given predicates. In order to
229 fulfill the request any of predicates must be meet
235 fulfill the request any of predicates must be meet
230 """
236 """
231
237
232 def check_permissions(self):
238 def check_permissions(self):
233 if self.required_perms.intersection(self.user_perms.get('global')):
239 if self.required_perms.intersection(self.user_perms.get('global')):
234 return True
240 return True
235 return False
241 return False
236
242
237 class HasRepoPermissionAllDecorator(PermsDecorator):
243 class HasRepoPermissionAllDecorator(PermsDecorator):
238 """
244 """
239 Checks for access permission for all given predicates for specific
245 Checks for access permission for all given predicates for specific
240 repository. All of them have to be meet in order to fulfill the request
246 repository. All of them have to be meet in order to fulfill the request
241 """
247 """
242
248
243 def check_permissions(self):
249 def check_permissions(self):
244 repo_name = get_repo_slug(request)
250 repo_name = get_repo_slug(request)
245 try:
251 try:
246 user_perms = set([self.user_perms['repositories'][repo_name]])
252 user_perms = set([self.user_perms['repositories'][repo_name]])
247 except KeyError:
253 except KeyError:
248 return False
254 return False
249 if self.required_perms.issubset(user_perms):
255 if self.required_perms.issubset(user_perms):
250 return True
256 return True
251 return False
257 return False
252
258
253
259
254 class HasRepoPermissionAnyDecorator(PermsDecorator):
260 class HasRepoPermissionAnyDecorator(PermsDecorator):
255 """
261 """
256 Checks for access permission for any of given predicates for specific
262 Checks for access permission for any of given predicates for specific
257 repository. In order to fulfill the request any of predicates must be meet
263 repository. In order to fulfill the request any of predicates must be meet
258 """
264 """
259
265
260 def check_permissions(self):
266 def check_permissions(self):
261 repo_name = get_repo_slug(request)
267 repo_name = get_repo_slug(request)
262
268
263 try:
269 try:
264 user_perms = set([self.user_perms['repositories'][repo_name]])
270 user_perms = set([self.user_perms['repositories'][repo_name]])
265 except KeyError:
271 except KeyError:
266 return False
272 return False
267 if self.required_perms.intersection(user_perms):
273 if self.required_perms.intersection(user_perms):
268 return True
274 return True
269 return False
275 return False
270 #===============================================================================
276 #===============================================================================
271 # CHECK FUNCTIONS
277 # CHECK FUNCTIONS
272 #===============================================================================
278 #===============================================================================
273
279
274 class PermsFunction(object):
280 class PermsFunction(object):
275 """
281 """
276 Base function for other check functions
282 Base function for other check functions
277 """
283 """
278
284
279 def __init__(self, *perms):
285 def __init__(self, *perms):
280 available_perms = config['available_permissions']
286 available_perms = config['available_permissions']
281
287
282 for perm in perms:
288 for perm in perms:
283 if perm not in available_perms:
289 if perm not in available_perms:
284 raise Exception("'%s' permission in not defined" % perm)
290 raise Exception("'%s' permission in not defined" % perm)
285 self.required_perms = set(perms)
291 self.required_perms = set(perms)
286 self.user_perms = None
292 self.user_perms = None
287 self.granted_for = ''
293 self.granted_for = ''
288 self.repo_name = None
294 self.repo_name = None
289
295
290 def __call__(self, check_Location=''):
296 def __call__(self, check_Location=''):
291 user = session.get('hg_app_user', False)
297 user = session.get('hg_app_user', False)
292 if not user:
298 if not user:
293 return False
299 return False
294 self.user_perms = user.permissions
300 self.user_perms = user.permissions
295 self.granted_for = user.username
301 self.granted_for = user.username
296 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
302 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
297
303
298 if self.check_permissions():
304 if self.check_permissions():
299 log.debug('Permission granted for %s @%s', self.granted_for,
305 log.debug('Permission granted for %s @%s', self.granted_for,
300 check_Location)
306 check_Location)
301 return True
307 return True
302
308
303 else:
309 else:
304 log.warning('Permission denied for %s @%s', self.granted_for,
310 log.warning('Permission denied for %s @%s', self.granted_for,
305 check_Location)
311 check_Location)
306 return False
312 return False
307
313
308 def check_permissions(self):
314 def check_permissions(self):
309 """
315 """
310 Dummy function for overriding
316 Dummy function for overriding
311 """
317 """
312 raise Exception('You have to write this function in child class')
318 raise Exception('You have to write this function in child class')
313
319
314 class HasPermissionAll(PermsFunction):
320 class HasPermissionAll(PermsFunction):
315 def check_permissions(self):
321 def check_permissions(self):
316 if self.required_perms.issubset(self.user_perms.get('global')):
322 if self.required_perms.issubset(self.user_perms.get('global')):
317 return True
323 return True
318 return False
324 return False
319
325
320 class HasPermissionAny(PermsFunction):
326 class HasPermissionAny(PermsFunction):
321 def check_permissions(self):
327 def check_permissions(self):
322 if self.required_perms.intersection(self.user_perms.get('global')):
328 if self.required_perms.intersection(self.user_perms.get('global')):
323 return True
329 return True
324 return False
330 return False
325
331
326 class HasRepoPermissionAll(PermsFunction):
332 class HasRepoPermissionAll(PermsFunction):
327
333
328 def __call__(self, repo_name=None, check_Location=''):
334 def __call__(self, repo_name=None, check_Location=''):
329 self.repo_name = repo_name
335 self.repo_name = repo_name
330 return super(HasRepoPermissionAll, self).__call__(check_Location)
336 return super(HasRepoPermissionAll, self).__call__(check_Location)
331
337
332 def check_permissions(self):
338 def check_permissions(self):
333 if not self.repo_name:
339 if not self.repo_name:
334 self.repo_name = get_repo_slug(request)
340 self.repo_name = get_repo_slug(request)
335
341
336 try:
342 try:
337 self.user_perms = set([self.user_perms['repositories']\
343 self.user_perms = set([self.user_perms['repositories']\
338 [self.repo_name]])
344 [self.repo_name]])
339 except KeyError:
345 except KeyError:
340 return False
346 return False
341 self.granted_for = self.repo_name
347 self.granted_for = self.repo_name
342 if self.required_perms.issubset(self.user_perms):
348 if self.required_perms.issubset(self.user_perms):
343 return True
349 return True
344 return False
350 return False
345
351
346 class HasRepoPermissionAny(PermsFunction):
352 class HasRepoPermissionAny(PermsFunction):
347
353
348
354
349 def __call__(self, repo_name=None, check_Location=''):
355 def __call__(self, repo_name=None, check_Location=''):
350 self.repo_name = repo_name
356 self.repo_name = repo_name
351 return super(HasRepoPermissionAny, self).__call__(check_Location)
357 return super(HasRepoPermissionAny, self).__call__(check_Location)
352
358
353 def check_permissions(self):
359 def check_permissions(self):
354 if not self.repo_name:
360 if not self.repo_name:
355 self.repo_name = get_repo_slug(request)
361 self.repo_name = get_repo_slug(request)
356
362
357 try:
363 try:
358 self.user_perms = set([self.user_perms['repositories']\
364 self.user_perms = set([self.user_perms['repositories']\
359 [self.repo_name]])
365 [self.repo_name]])
360 except KeyError:
366 except KeyError:
361 return False
367 return False
362 self.granted_for = self.repo_name
368 self.granted_for = self.repo_name
363 if self.required_perms.intersection(self.user_perms):
369 if self.required_perms.intersection(self.user_perms):
364 return True
370 return True
365 return False
371 return False
366
372
367 #===============================================================================
373 #===============================================================================
368 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
374 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
369 #===============================================================================
375 #===============================================================================
370
376
371 class HasPermissionAnyMiddleware(object):
377 class HasPermissionAnyMiddleware(object):
372 def __init__(self, *perms):
378 def __init__(self, *perms):
373 self.required_perms = set(perms)
379 self.required_perms = set(perms)
374
380
375 def __call__(self, user, repo_name):
381 def __call__(self, user, repo_name):
376 usr = AuthUser()
382 usr = AuthUser()
377 usr.user_id = user.user_id
383 usr.user_id = user.user_id
378 usr.username = user.username
384 usr.username = user.username
379 usr.is_admin = user.admin
385 usr.is_admin = user.admin
380
386
381 try:
387 try:
382 self.user_perms = set([fill_perms(usr)\
388 self.user_perms = set([fill_perms(usr)\
383 .permissions['repositories'][repo_name]])
389 .permissions['repositories'][repo_name]])
384 except:
390 except:
385 self.user_perms = set()
391 self.user_perms = set()
386 self.granted_for = ''
392 self.granted_for = ''
387 self.username = user.username
393 self.username = user.username
388 self.repo_name = repo_name
394 self.repo_name = repo_name
389 return self.check_permissions()
395 return self.check_permissions()
390
396
391 def check_permissions(self):
397 def check_permissions(self):
392 log.debug('checking mercurial protocol '
398 log.debug('checking mercurial protocol '
393 'permissions for user:%s repository:%s',
399 'permissions for user:%s repository:%s',
394 self.username, self.repo_name)
400 self.username, self.repo_name)
395 if self.required_perms.intersection(self.user_perms):
401 if self.required_perms.intersection(self.user_perms):
396 log.debug('permission granted')
402 log.debug('permission granted')
397 return True
403 return True
398 log.debug('permission denied')
404 log.debug('permission denied')
399 return False
405 return False
@@ -1,226 +1,231 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # middleware to handle mercurial api calls
3 # middleware to handle mercurial api calls
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20
20
21 """
21 """
22 Created on 2010-04-28
22 Created on 2010-04-28
23
23
24 @author: marcink
24 @author: marcink
25 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
25 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
26 It's implemented with basic auth function
26 It's implemented with basic auth function
27 """
27 """
28 from datetime import datetime
28 from datetime import datetime
29 from itertools import chain
29 from itertools import chain
30 from mercurial.error import RepoError
30 from mercurial.hgweb import hgweb
31 from mercurial.hgweb import hgweb
31 from mercurial.hgweb.request import wsgiapplication
32 from mercurial.hgweb.request import wsgiapplication
32 from mercurial.error import RepoError
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from pylons_app.lib.auth import authfunc, HasPermissionAnyMiddleware
35 from pylons_app.lib.auth import authfunc, HasPermissionAnyMiddleware, \
36 get_user_cached
36 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache, \
37 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache, \
37 check_repo_fast
38 check_repo_fast
38 from pylons_app.model import meta
39 from pylons_app.model import meta
39 from pylons_app.model.db import UserLog, User
40 from pylons_app.model.db import UserLog, User
40 import pylons_app.lib.helpers as h
41 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
41 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
42 import logging
42 import logging
43 import os
43 import os
44 import pylons_app.lib.helpers as h
44 import traceback
45 import traceback
46
45 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
46
48
47 class SimpleHg(object):
49 class SimpleHg(object):
48
50
49 def __init__(self, application, config):
51 def __init__(self, application, config):
50 self.application = application
52 self.application = application
51 self.config = config
53 self.config = config
52 #authenticate this mercurial request using
54 #authenticate this mercurial request using
53 realm = self.config['hg_app_auth_realm']
55 realm = self.config['hg_app_auth_realm']
54 self.authenticate = AuthBasicAuthenticator(realm, authfunc)
56 self.authenticate = AuthBasicAuthenticator(realm, authfunc)
55
57
56 def __call__(self, environ, start_response):
58 def __call__(self, environ, start_response):
57 if not is_mercurial(environ):
59 if not is_mercurial(environ):
58 return self.application(environ, start_response)
60 return self.application(environ, start_response)
59 else:
61
60 #===================================================================
62 #===================================================================
61 # AUTHENTICATE THIS MERCURIAL REQUEST
63 # AUTHENTICATE THIS MERCURIAL REQUEST
62 #===================================================================
64 #===================================================================
63 username = REMOTE_USER(environ)
65 username = REMOTE_USER(environ)
64 if not username:
66 if not username:
65 result = self.authenticate(environ)
67 result = self.authenticate(environ)
66 if isinstance(result, str):
68 if isinstance(result, str):
67 AUTH_TYPE.update(environ, 'basic')
69 AUTH_TYPE.update(environ, 'basic')
68 REMOTE_USER.update(environ, result)
70 REMOTE_USER.update(environ, result)
69 else:
71 else:
70 return result.wsgi_application(environ, start_response)
72 return result.wsgi_application(environ, start_response)
71
73
74 try:
75 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
76 except:
77 log.error(traceback.format_exc())
78 return HTTPInternalServerError()(environ, start_response)
79
80 #===================================================================
81 # CHECK PERMISSIONS FOR THIS REQUEST
82 #===================================================================
83 action = self.__get_action(environ)
84 if action:
85 username = self.__get_environ_user(environ)
72 try:
86 try:
73 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
87 user = self.__get_user(username)
74 except:
88 except:
75 log.error(traceback.format_exc())
89 log.error(traceback.format_exc())
76 return HTTPInternalServerError()(environ, start_response)
90 return HTTPInternalServerError()(environ, start_response)
91 #check permissions for this repository
92 if action == 'pull':
93 if not HasPermissionAnyMiddleware('repository.read',
94 'repository.write',
95 'repository.admin')\
96 (user, repo_name):
97 return HTTPForbidden()(environ, start_response)
98 if action == 'push':
99 if not HasPermissionAnyMiddleware('repository.write',
100 'repository.admin')\
101 (user, repo_name):
102 return HTTPForbidden()(environ, start_response)
77
103
78 #===================================================================
104 #log action
79 # CHECK PERMISSIONS FOR THIS REQUEST
105 proxy_key = 'HTTP_X_REAL_IP'
80 #===================================================================
106 def_key = 'REMOTE_ADDR'
81 action = self.__get_action(environ)
107 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
82 if action:
108 self.__log_user_action(user, action, repo_name, ipaddr)
83 username = self.__get_environ_user(environ)
109
84 try:
110 #===================================================================
85 sa = meta.Session
111 # MERCURIAL REQUEST HANDLING
86 user = sa.query(User)\
112 #===================================================================
87 .filter(User.username == username).one()
113 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
88 except:
114 self.baseui = make_ui('db')
89 log.error(traceback.format_exc())
115 self.basepath = self.config['base_path']
90 return HTTPInternalServerError()(environ, start_response)
116 self.repo_path = os.path.join(self.basepath, repo_name)
91 #check permissions for this repository
92 if action == 'pull':
93 if not HasPermissionAnyMiddleware('repository.read',
94 'repository.write',
95 'repository.admin')\
96 (user, repo_name):
97 return HTTPForbidden()(environ, start_response)
98 if action == 'push':
99 if not HasPermissionAnyMiddleware('repository.write',
100 'repository.admin')\
101 (user, repo_name):
102 return HTTPForbidden()(environ, start_response)
103
104 #log action
105 proxy_key = 'HTTP_X_REAL_IP'
106 def_key = 'REMOTE_ADDR'
107 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
108 self.__log_user_action(user, action, repo_name, ipaddr)
109
110 #===================================================================
111 # MERCURIAL REQUEST HANDLING
112 #===================================================================
113 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
114 self.baseui = make_ui('db')
115 self.basepath = self.config['base_path']
116 self.repo_path = os.path.join(self.basepath, repo_name)
117
117
118 #quick check if that dir exists...
118 #quick check if that dir exists...
119 if check_repo_fast(repo_name, self.basepath):
119 if check_repo_fast(repo_name, self.basepath):
120 return HTTPNotFound()(environ, start_response)
121 try:
122 app = wsgiapplication(self.__make_app)
123 except RepoError as e:
124 if str(e).find('not found') != -1:
120 return HTTPNotFound()(environ, start_response)
125 return HTTPNotFound()(environ, start_response)
121 try:
126 except Exception:
122 app = wsgiapplication(self.__make_app)
127 log.error(traceback.format_exc())
123 except RepoError as e:
128 return HTTPInternalServerError()(environ, start_response)
124 if str(e).find('not found') != -1:
129
125 return HTTPNotFound()(environ, start_response)
130 #invalidate cache on push
126 except Exception:
131 if action == 'push':
127 log.error(traceback.format_exc())
132 self.__invalidate_cache(repo_name)
128 return HTTPInternalServerError()(environ, start_response)
133 messages = []
129
134 messages.append('thank you for using hg-app')
130 #invalidate cache on push
135
131 if action == 'push':
136 return self.msg_wrapper(app, environ, start_response, messages)
132 self.__invalidate_cache(repo_name)
137 else:
133 messages = []
138 return app(environ, start_response)
134 messages.append('thank you for using hg-app')
135
136 return self.msg_wrapper(app, environ, start_response, messages)
137 else:
138 return app(environ, start_response)
139
139
140
140
141 def msg_wrapper(self, app, environ, start_response, messages=[]):
141 def msg_wrapper(self, app, environ, start_response, messages=[]):
142 """
142 """
143 Wrapper for custom messages that come out of mercurial respond messages
143 Wrapper for custom messages that come out of mercurial respond messages
144 is a list of messages that the user will see at the end of response
144 is a list of messages that the user will see at the end of response
145 from merurial protocol actions that involves remote answers
145 from merurial protocol actions that involves remote answers
146 @param app:
146 @param app:
147 @param environ:
147 @param environ:
148 @param start_response:
148 @param start_response:
149 """
149 """
150 def custom_messages(msg_list):
150 def custom_messages(msg_list):
151 for msg in msg_list:
151 for msg in msg_list:
152 yield msg + '\n'
152 yield msg + '\n'
153 org_response = app(environ, start_response)
153 org_response = app(environ, start_response)
154 return chain(org_response, custom_messages(messages))
154 return chain(org_response, custom_messages(messages))
155
155
156 def __make_app(self):
156 def __make_app(self):
157 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
157 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
158 return self.__load_web_settings(hgserve)
158 return self.__load_web_settings(hgserve)
159
159
160 def __get_environ_user(self, environ):
160 def __get_environ_user(self, environ):
161 return environ.get('REMOTE_USER')
161 return environ.get('REMOTE_USER')
162
162
163 def __get_user(self, username):
164 return get_user_cached(username)
165
166
167
163 def __get_size(self, repo_path, content_size):
168 def __get_size(self, repo_path, content_size):
164 size = int(content_size)
169 size = int(content_size)
165 for path, dirs, files in os.walk(repo_path):
170 for path, dirs, files in os.walk(repo_path):
166 if path.find('.hg') == -1:
171 if path.find('.hg') == -1:
167 for f in files:
172 for f in files:
168 size += os.path.getsize(os.path.join(path, f))
173 size += os.path.getsize(os.path.join(path, f))
169 return size
174 return size
170 return h.format_byte_size(size)
175 return h.format_byte_size(size)
171
176
172 def __get_action(self, environ):
177 def __get_action(self, environ):
173 """
178 """
174 Maps mercurial request commands into a pull or push command.
179 Maps mercurial request commands into a pull or push command.
175 @param environ:
180 @param environ:
176 """
181 """
177 mapping = {'changegroup': 'pull',
182 mapping = {'changegroup': 'pull',
178 'changegroupsubset': 'pull',
183 'changegroupsubset': 'pull',
179 'stream_out': 'pull',
184 'stream_out': 'pull',
180 'listkeys': 'pull',
185 'listkeys': 'pull',
181 'unbundle': 'push',
186 'unbundle': 'push',
182 'pushkey': 'push', }
187 'pushkey': 'push', }
183
188
184 for qry in environ['QUERY_STRING'].split('&'):
189 for qry in environ['QUERY_STRING'].split('&'):
185 if qry.startswith('cmd'):
190 if qry.startswith('cmd'):
186 cmd = qry.split('=')[-1]
191 cmd = qry.split('=')[-1]
187 if mapping.has_key(cmd):
192 if mapping.has_key(cmd):
188 return mapping[cmd]
193 return mapping[cmd]
189
194
190 def __log_user_action(self, user, action, repo, ipaddr):
195 def __log_user_action(self, user, action, repo, ipaddr):
191 sa = meta.Session
196 sa = meta.Session
192 try:
197 try:
193 user_log = UserLog()
198 user_log = UserLog()
194 user_log.user_id = user.user_id
199 user_log.user_id = user.user_id
195 user_log.action = action
200 user_log.action = action
196 user_log.repository = repo.replace('/', '')
201 user_log.repository = repo.replace('/', '')
197 user_log.action_date = datetime.now()
202 user_log.action_date = datetime.now()
198 user_log.user_ip = ipaddr
203 user_log.user_ip = ipaddr
199 sa.add(user_log)
204 sa.add(user_log)
200 sa.commit()
205 sa.commit()
201 log.info('Adding user %s, action %s on %s',
206 log.info('Adding user %s, action %s on %s',
202 user.username, action, repo)
207 user.username, action, repo)
203 except Exception as e:
208 except Exception as e:
204 sa.rollback()
209 sa.rollback()
205 log.error('could not log user action:%s', str(e))
210 log.error('could not log user action:%s', str(e))
206
211
207 def __invalidate_cache(self, repo_name):
212 def __invalidate_cache(self, repo_name):
208 """we know that some change was made to repositories and we should
213 """we know that some change was made to repositories and we should
209 invalidate the cache to see the changes right away but only for
214 invalidate the cache to see the changes right away but only for
210 push requests"""
215 push requests"""
211 invalidate_cache('cached_repo_list')
216 invalidate_cache('cached_repo_list')
212 invalidate_cache('full_changelog', repo_name)
217 invalidate_cache('full_changelog', repo_name)
213
218
214
219
215 def __load_web_settings(self, hgserve):
220 def __load_web_settings(self, hgserve):
216 #set the global ui for hgserve
221 #set the global ui for hgserve
217 hgserve.repo.ui = self.baseui
222 hgserve.repo.ui = self.baseui
218
223
219 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
224 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
220 repoui = make_ui('file', hgrc, False)
225 repoui = make_ui('file', hgrc, False)
221
226
222 if repoui:
227 if repoui:
223 #set the repository based config
228 #set the repository based config
224 hgserve.repo.ui = repoui
229 hgserve.repo.ui = repoui
225
230
226 return hgserve
231 return hgserve
@@ -1,189 +1,194 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Utilities for hg app
3 # Utilities for hg app
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2
7 # as published by the Free Software Foundation; version 2
8 # of the License or (at your opinion) any later version of the license.
8 # of the License or (at your opinion) any later version of the license.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA 02110-1301, USA.
18 # MA 02110-1301, USA.
19 from beaker.cache import cache_region
19
20
20 """
21 """
21 Created on April 18, 2010
22 Created on April 18, 2010
22 Utilities for hg app
23 Utilities for hg app
23 @author: marcink
24 @author: marcink
24 """
25 """
25
26
26 import os
27 import os
27 import logging
28 import logging
28 from mercurial import ui, config, hg
29 from mercurial import ui, config, hg
29 from mercurial.error import RepoError
30 from mercurial.error import RepoError
30 from pylons_app.model.db import Repository, User, HgAppUi
31 from pylons_app.model.db import Repository, User, HgAppUi
31 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
32
33
33
34
34 def get_repo_slug(request):
35 def get_repo_slug(request):
35 return request.environ['pylons.routes_dict'].get('repo_name')
36 return request.environ['pylons.routes_dict'].get('repo_name')
36
37
37 def is_mercurial(environ):
38 def is_mercurial(environ):
38 """
39 """
39 Returns True if request's target is mercurial server - header
40 Returns True if request's target is mercurial server - header
40 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
41 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
41 """
42 """
42 http_accept = environ.get('HTTP_ACCEPT')
43 http_accept = environ.get('HTTP_ACCEPT')
43 if http_accept and http_accept.startswith('application/mercurial'):
44 if http_accept and http_accept.startswith('application/mercurial'):
44 return True
45 return True
45 return False
46 return False
46
47
47 def check_repo_dir(paths):
48 def check_repo_dir(paths):
48 repos_path = paths[0][1].split('/')
49 repos_path = paths[0][1].split('/')
49 if repos_path[-1] in ['*', '**']:
50 if repos_path[-1] in ['*', '**']:
50 repos_path = repos_path[:-1]
51 repos_path = repos_path[:-1]
51 if repos_path[0] != '/':
52 if repos_path[0] != '/':
52 repos_path[0] = '/'
53 repos_path[0] = '/'
53 if not os.path.isdir(os.path.join(*repos_path)):
54 if not os.path.isdir(os.path.join(*repos_path)):
54 raise Exception('Not a valid repository in %s' % paths[0][1])
55 raise Exception('Not a valid repository in %s' % paths[0][1])
55
56
56 def check_repo_fast(repo_name, base_path):
57 def check_repo_fast(repo_name, base_path):
57 if os.path.isdir(os.path.join(base_path, repo_name)):return False
58 if os.path.isdir(os.path.join(base_path, repo_name)):return False
58 return True
59 return True
59
60
60 def check_repo(repo_name, base_path, verify=True):
61 def check_repo(repo_name, base_path, verify=True):
61
62
62 repo_path = os.path.join(base_path, repo_name)
63 repo_path = os.path.join(base_path, repo_name)
63
64
64 try:
65 try:
65 if not check_repo_fast(repo_name, base_path):
66 if not check_repo_fast(repo_name, base_path):
66 return False
67 return False
67 r = hg.repository(ui.ui(), repo_path)
68 r = hg.repository(ui.ui(), repo_path)
68 if verify:
69 if verify:
69 hg.verify(r)
70 hg.verify(r)
70 #here we hnow that repo exists it was verified
71 #here we hnow that repo exists it was verified
71 log.info('%s repo is already created', repo_name)
72 log.info('%s repo is already created', repo_name)
72 return False
73 return False
73 except RepoError:
74 except RepoError:
74 #it means that there is no valid repo there...
75 #it means that there is no valid repo there...
75 log.info('%s repo is free for creation', repo_name)
76 log.info('%s repo is free for creation', repo_name)
76 return True
77 return True
77
78
79
80 @cache_region('super_short_term', 'cached_hg_ui')
81 def get_hg_ui_cached():
82 from pylons_app.model.meta import Session
83 sa = Session()
84 return sa.query(HgAppUi).all()
85
78 def make_ui(read_from='file', path=None, checkpaths=True):
86 def make_ui(read_from='file', path=None, checkpaths=True):
79 """
87 """
80 A function that will read python rc files or database
88 A function that will read python rc files or database
81 and make an mercurial ui object from read options
89 and make an mercurial ui object from read options
82
90
83 @param path: path to mercurial config file
91 @param path: path to mercurial config file
84 @param checkpaths: check the path
92 @param checkpaths: check the path
85 @param read_from: read from 'file' or 'db'
93 @param read_from: read from 'file' or 'db'
86 """
94 """
87 #propagated from mercurial documentation
95 #propagated from mercurial documentation
88 sections = ['alias', 'auth',
96 sections = ['alias', 'auth',
89 'decode/encode', 'defaults',
97 'decode/encode', 'defaults',
90 'diff', 'email',
98 'diff', 'email',
91 'extensions', 'format',
99 'extensions', 'format',
92 'merge-patterns', 'merge-tools',
100 'merge-patterns', 'merge-tools',
93 'hooks', 'http_proxy',
101 'hooks', 'http_proxy',
94 'smtp', 'patch',
102 'smtp', 'patch',
95 'paths', 'profiling',
103 'paths', 'profiling',
96 'server', 'trusted',
104 'server', 'trusted',
97 'ui', 'web', ]
105 'ui', 'web', ]
98 baseui = ui.ui()
106 baseui = ui.ui()
99
107
100
108
101 if read_from == 'file':
109 if read_from == 'file':
102 if not os.path.isfile(path):
110 if not os.path.isfile(path):
103 log.warning('Unable to read config file %s' % path)
111 log.warning('Unable to read config file %s' % path)
104 return False
112 return False
105
113
106 cfg = config.config()
114 cfg = config.config()
107 cfg.read(path)
115 cfg.read(path)
108 for section in sections:
116 for section in sections:
109 for k, v in cfg.items(section):
117 for k, v in cfg.items(section):
110 baseui.setconfig(section, k, v)
118 baseui.setconfig(section, k, v)
111 if checkpaths:check_repo_dir(cfg.items('paths'))
119 if checkpaths:check_repo_dir(cfg.items('paths'))
112
120
113
121
114 elif read_from == 'db':
122 elif read_from == 'db':
115 from pylons_app.model.meta import Session
123 hg_ui = get_hg_ui_cached()
116 sa = Session()
117
118 hg_ui = sa.query(HgAppUi).all()
119 for ui_ in hg_ui:
124 for ui_ in hg_ui:
120 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
125 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
121
126
122
127
123 return baseui
128 return baseui
124
129
125
130
126 def set_hg_app_config(config):
131 def set_hg_app_config(config):
127 config['hg_app_auth_realm'] = 'realm'
132 config['hg_app_auth_realm'] = 'realm'
128 config['hg_app_name'] = 'app name'
133 config['hg_app_name'] = 'app name'
129
134
130 def invalidate_cache(name, *args):
135 def invalidate_cache(name, *args):
131 """Invalidates given name cache"""
136 """Invalidates given name cache"""
132
137
133 from beaker.cache import region_invalidate
138 from beaker.cache import region_invalidate
134 log.info('INVALIDATING CACHE FOR %s', name)
139 log.info('INVALIDATING CACHE FOR %s', name)
135
140
136 """propagate our arguments to make sure invalidation works. First
141 """propagate our arguments to make sure invalidation works. First
137 argument has to be the name of cached func name give to cache decorator
142 argument has to be the name of cached func name give to cache decorator
138 without that the invalidation would not work"""
143 without that the invalidation would not work"""
139 tmp = [name]
144 tmp = [name]
140 tmp.extend(args)
145 tmp.extend(args)
141 args = tuple(tmp)
146 args = tuple(tmp)
142
147
143 if name == 'cached_repo_list':
148 if name == 'cached_repo_list':
144 from pylons_app.model.hg_model import _get_repos_cached
149 from pylons_app.model.hg_model import _get_repos_cached
145 region_invalidate(_get_repos_cached, None, *args)
150 region_invalidate(_get_repos_cached, None, *args)
146
151
147 if name == 'full_changelog':
152 if name == 'full_changelog':
148 from pylons_app.model.hg_model import _full_changelog_cached
153 from pylons_app.model.hg_model import _full_changelog_cached
149 region_invalidate(_full_changelog_cached, None, *args)
154 region_invalidate(_full_changelog_cached, None, *args)
150
155
151 from vcs.backends.base import BaseChangeset
156 from vcs.backends.base import BaseChangeset
152 from vcs.utils.lazy import LazyProperty
157 from vcs.utils.lazy import LazyProperty
153 class EmptyChangeset(BaseChangeset):
158 class EmptyChangeset(BaseChangeset):
154
159
155 revision = -1
160 revision = -1
156 message = ''
161 message = ''
157
162
158 @LazyProperty
163 @LazyProperty
159 def raw_id(self):
164 def raw_id(self):
160 """
165 """
161 Returns raw string identifing this changeset, useful for web
166 Returns raw string identifing this changeset, useful for web
162 representation.
167 representation.
163 """
168 """
164 return '0' * 12
169 return '0' * 12
165
170
166
171
167 def repo2db_mapper(initial_repo_list):
172 def repo2db_mapper(initial_repo_list):
168 """
173 """
169 maps all found repositories into db
174 maps all found repositories into db
170 """
175 """
171 from pylons_app.model.meta import Session
176 from pylons_app.model.meta import Session
172 from pylons_app.model.repo_model import RepoModel
177 from pylons_app.model.repo_model import RepoModel
173
178
174 sa = Session()
179 sa = Session()
175 user = sa.query(User).filter(User.admin == True).first()
180 user = sa.query(User).filter(User.admin == True).first()
176
181
177 rm = RepoModel()
182 rm = RepoModel()
178
183
179 for name, repo in initial_repo_list.items():
184 for name, repo in initial_repo_list.items():
180 if not sa.query(Repository).get(name):
185 if not sa.query(Repository).get(name):
181 log.info('repository %s not found creating default', name)
186 log.info('repository %s not found creating default', name)
182
187
183 form_data = {
188 form_data = {
184 'repo_name':name,
189 'repo_name':name,
185 'description':repo.description if repo.description != 'unknown' else \
190 'description':repo.description if repo.description != 'unknown' else \
186 'auto description for %s' % name,
191 'auto description for %s' % name,
187 'private':False
192 'private':False
188 }
193 }
189 rm.create(form_data, user, just_db=True)
194 rm.create(form_data, user, just_db=True)
General Comments 0
You need to be logged in to leave comments. Login now