allura
修訂 | 41539b93a42eea4574b8020bdaf7f3962eedb53f (tree) |
---|---|
時間 | 2010-05-21 01:35:11 |
作者 | Rick Copeland <rcopeland@geek...> |
Commiter | Rick Copeland |
Plugin refactoring (not Tools)
This commit includes
@@ -31,6 +31,8 @@ beaker.session.type = cookie | ||
31 | 31 | beaker.session.secret = 61ece7db-ba8d-49fe-a923-ab444741708c |
32 | 32 | beaker.session.validate_key = 714bfe3612c42390726f |
33 | 33 | |
34 | +registration.method = local | |
35 | + | |
34 | 36 | auth.method = local |
35 | 37 | auth.ldap.server = ldap://localhost |
36 | 38 | auth.ldap.suffix = ou=people,dc=example,dc=com |
@@ -11,6 +11,7 @@ from pyforge import model as M | ||
11 | 11 | from pyforge.lib.oid_helper import verify_oid, process_oid |
12 | 12 | from pyforge.lib.security import require_authenticated |
13 | 13 | from pyforge.lib import helpers as h |
14 | +from pyforge.lib import plugin | |
14 | 15 | from pyforge.lib.widgets import SubscriptionForm |
15 | 16 | |
16 | 17 | log = logging.getLogger(__name__) |
@@ -107,7 +108,7 @@ class AuthController(object): | ||
107 | 108 | for open_id in open_ids.split(','): |
108 | 109 | oid = M.OpenId.upsert(open_id, display_name+"'s OpenId") |
109 | 110 | user.claim_openid(open_id) |
110 | - M.AuthenticationProvider.get(request).login(user) | |
111 | + plugin.AuthenticationProvider.get(request).login(user) | |
111 | 112 | flash('User "%s" registered' % user.display_name) |
112 | 113 | redirect('/') |
113 | 114 |
@@ -170,12 +171,12 @@ class AuthController(object): | ||
170 | 171 | |
171 | 172 | @expose() |
172 | 173 | def logout(self): |
173 | - M.AuthenticationProvider.get(request).logout() | |
174 | + plugin.AuthenticationProvider.get(request).logout() | |
174 | 175 | redirect('/') |
175 | 176 | |
176 | 177 | @expose() |
177 | 178 | def do_login(self, came_from=None, **kw): |
178 | - user = M.AuthenticationProvider.get(request).login() | |
179 | + user = plugin.AuthenticationProvider.get(request).login() | |
179 | 180 | flash('Welcome back, %s' % user.display_name) |
180 | 181 | if came_from and came_from != request.url: |
181 | 182 | redirect(came_from) |
@@ -15,6 +15,7 @@ import pyforge | ||
15 | 15 | from pyforge.app import SitemapEntry |
16 | 16 | from pyforge.lib.base import BaseController |
17 | 17 | from pyforge.lib import helpers as h |
18 | +from pyforge.lib import plugin | |
18 | 19 | from pyforge.controllers.error import ErrorController |
19 | 20 | from pyforge import model as M |
20 | 21 | from pyforge.lib.widgets import project_list as plw |
@@ -67,7 +68,7 @@ class RootController(BaseController): | ||
67 | 68 | def _setup_request(self): |
68 | 69 | uid = session.get('userid', None) |
69 | 70 | c.project = c.app = None |
70 | - c.user = M.AuthenticationProvider.get(request).authenticate_request() | |
71 | + c.user = plugin.AuthenticationProvider.get(request).authenticate_request() | |
71 | 72 | c.queued_messages = [] |
72 | 73 | |
73 | 74 | @expose('pyforge.templates.project_list') |
@@ -16,6 +16,7 @@ import pyforge | ||
16 | 16 | from pyforge.lib.base import BaseController |
17 | 17 | from pyforge.lib.security import require, require_authenticated, has_project_access, has_artifact_access |
18 | 18 | from pyforge.lib import helpers as h |
19 | +from pyforge.lib import plugin | |
19 | 20 | from pyforge import model as M |
20 | 21 | from .root import RootController |
21 | 22 | from .project import ProjectController |
@@ -90,7 +91,7 @@ class TestController(BaseController, ProjectController): | ||
90 | 91 | def __call__(self, environ, start_response): |
91 | 92 | c.app = None |
92 | 93 | c.project = M.Project.query.get(shortname='test') |
93 | - c.user = M.AuthenticationProvider.get(request).by_username( | |
94 | + c.user = plugin.AuthenticationProvider.get(request).by_username( | |
94 | 95 | environ.get('username', 'test_admin')) |
95 | 96 | return BaseController.__call__(self, environ, start_response) |
96 | 97 |
@@ -59,32 +59,31 @@ class SFXProjectApi(object): | ||
59 | 59 | def _connect(self): |
60 | 60 | return closing(httplib.HTTPConnection(self.project_host or request.host)) |
61 | 61 | |
62 | - def _unix_group_name(self, p): | |
63 | - if p.neighborhood.url_prefix != 'p/': | |
64 | - path = p.neighborhood.url_prefix + p.shortname | |
62 | + def _unix_group_name(self, neighborhood, shortname): | |
63 | + if neighborhood.url_prefix != 'p/': | |
64 | + path = neighborhood.url_prefix + shortname | |
65 | 65 | else: |
66 | - path = p.shortname | |
66 | + path = shortname | |
67 | 67 | parts = path.split('/')[1:] |
68 | 68 | return '.'.join(reversed(parts)) |
69 | 69 | |
70 | - def create(self, p): | |
70 | + def create(self, neighborhood, shortname, short_description='No description'): | |
71 | 71 | with self._connect() as conn: |
72 | - ug_name = self._unix_group_name(p) | |
72 | + ug_name = self._unix_group_name(neighborhood, shortname) | |
73 | 73 | args = dict( |
74 | 74 | user_id=c.user.sfx_userid, |
75 | 75 | unix_group_name=ug_name, |
76 | - group_name=p.shortname, | |
77 | - short_description=p.short_description) | |
76 | + group_name=shortname, | |
77 | + short_description=short_description) | |
78 | 78 | conn.request('POST', self.project_path, json.dumps(args)) |
79 | 79 | response = conn.getresponse() |
80 | 80 | assert response.status == 201, \ |
81 | 81 | 'Bad status from sfx create: %s' % (response.status) |
82 | - response.read() | |
83 | - self.update(p) | |
82 | + return response.read() | |
84 | 83 | |
85 | 84 | def read(self, p): |
86 | 85 | with self._connect() as conn: |
87 | - ug_name = self._unix_group_name(p) | |
86 | + ug_name = self._unix_group_name(p.neighborhood, p.shortname) | |
88 | 87 | conn.request('GET', self.project_path + '/name/' + ug_name + '/json') |
89 | 88 | response = conn.getresponse() |
90 | 89 | assert response.status == 200, \ |
@@ -93,7 +92,7 @@ class SFXProjectApi(object): | ||
93 | 92 | |
94 | 93 | def update(self, p): |
95 | 94 | with self._connect() as conn: |
96 | - ug_name = self._unix_group_name(p) | |
95 | + ug_name = self._unix_group_name(p.neighborhood, p.shortname) | |
97 | 96 | args = dict( |
98 | 97 | user_id=c.user.sfx_userid, |
99 | 98 | group_name=p.shortname, |
@@ -110,7 +109,7 @@ class SFXProjectApi(object): | ||
110 | 109 | |
111 | 110 | def delete(self, p): |
112 | 111 | with self._connect() as conn: |
113 | - ug_name = self._unix_group_name(p) | |
112 | + ug_name = self._unix_group_name(p.neighborhood, p.shortname) | |
114 | 113 | conn.request('DELETE', self.project_path + '/' + ug_name) |
115 | 114 | response = conn.getresponse() |
116 | 115 | assert response.status in (200, 404, 410), \ |
@@ -10,6 +10,8 @@ from pyforge import version | ||
10 | 10 | from pyforge.app import Application |
11 | 11 | from pyforge.lib.decorators import react |
12 | 12 | from pyforge.lib import search |
13 | +from pyforge.lib import helpers as h | |
14 | +from pyforge.lib import plugin | |
13 | 15 | from pyforge import model as M |
14 | 16 | |
15 | 17 | from .lib.sfx_api import SFXProjectApi, SFXUserApi |
@@ -28,7 +30,7 @@ class SFXApp(Application): | ||
28 | 30 | @react('forge.project_created') |
29 | 31 | def project_created(cls, routing_key, doc): |
30 | 32 | api = SFXProjectApi() |
31 | - api.create(pylons.c.project) | |
33 | + api.update(pylons.c.project) | |
32 | 34 | |
33 | 35 | @classmethod |
34 | 36 | @react('forge.project_updated') |
@@ -54,7 +56,7 @@ class SFXApp(Application): | ||
54 | 56 | def uninstall(self, project): |
55 | 57 | pass # pragma no cover |
56 | 58 | |
57 | -class SFXAuthenticationProvider(M.AuthenticationProvider): | |
59 | +class SFXAuthenticationProvider(plugin.AuthenticationProvider): | |
58 | 60 | |
59 | 61 | def __init__(self, request): |
60 | 62 | super(SFXAuthenticationProvider, self).__init__(request) |
@@ -106,6 +108,24 @@ class SFXAuthenticationProvider(M.AuthenticationProvider): | ||
106 | 108 | with fake_pylons_context(self.request): |
107 | 109 | return api.upsert_user(username, extra) |
108 | 110 | |
111 | +class SFXProjectRegistrationProvider(plugin.ProjectRegistrationProvider): | |
112 | + | |
113 | + def __init__(self): | |
114 | + self.api = SFXUserApi() | |
115 | + | |
116 | + def register_project(self, neighborhood, shortname, user_project): | |
117 | + # Reserve project name with SFX | |
118 | + r = self.api.create(neighborhood, shortname) | |
119 | + log.info('SFX Project creation returned: %s', r) | |
120 | + p = super(SFXProjectRegistrationProvider, self).register_project( | |
121 | + neighborhood, shortname, user_project) | |
122 | + return p | |
123 | + | |
124 | + def register_subproject(self, project, name, install_apps): | |
125 | + r = self.api.create(project.neighborhood, project.shortname + '/' + name) | |
126 | + log.info('SFX Subproject creation returned: %s', r) | |
127 | + return super(SFXProjectRegistrationProvider, self).register_subproject( | |
128 | + project, name, install_apps) | |
109 | 129 | |
110 | 130 | @contextmanager |
111 | 131 | def fake_pylons_context(request): |
@@ -0,0 +1,264 @@ | ||
1 | +''' | |
2 | +Pyforge plugins for authentication and project registration | |
3 | +''' | |
4 | +import logging | |
5 | + | |
6 | +from random import randint | |
7 | +from hashlib import sha256 | |
8 | +from base64 import b64encode | |
9 | +from datetime import datetime | |
10 | + | |
11 | +import ldap | |
12 | +import pkg_resources | |
13 | +from tg import config | |
14 | +from pylons import g, c | |
15 | +from webob import exc | |
16 | + | |
17 | +from ming.utils import LazyProperty | |
18 | +from ming.orm import session | |
19 | +from ming.orm import ThreadLocalORMSession | |
20 | + | |
21 | +from pyforge.lib import helpers as h | |
22 | + | |
23 | +log = logging.getLogger(__name__) | |
24 | + | |
25 | +class AuthenticationProvider(object): | |
26 | + | |
27 | + def __init__(self, request): | |
28 | + self.request = request | |
29 | + | |
30 | + @classmethod | |
31 | + def get(cls, request): | |
32 | + method = config.get('auth.method', 'local') | |
33 | + for ep in pkg_resources.iter_entry_points('pyforge.auth', method): | |
34 | + return ep.load()(request) | |
35 | + return None | |
36 | + | |
37 | + @LazyProperty | |
38 | + def session(self): | |
39 | + return self.request.environ['beaker.session'] | |
40 | + | |
41 | + def authenticate_request(self): | |
42 | + from pyforge import model as M | |
43 | + return M.User.query.get(_id=self.session.get('userid', None)) | |
44 | + | |
45 | + def register_user(self, user_doc): | |
46 | + raise NotImplementedError, 'register_user' | |
47 | + | |
48 | + def _login(self): | |
49 | + raise NotImplementedError, '_login' | |
50 | + | |
51 | + def login(self, user=None): | |
52 | + try: | |
53 | + if user is None: user = self._login() | |
54 | + self.session['userid'] = user._id | |
55 | + self.session.save() | |
56 | + return user | |
57 | + except exc.HTTPUnauthorized: | |
58 | + self.logout() | |
59 | + raise | |
60 | + | |
61 | + def logout(self): | |
62 | + self.session['userid'] = None | |
63 | + self.session.save() | |
64 | + | |
65 | + def by_username(self, username): | |
66 | + raise NotImplementedError, 'by_username' | |
67 | + | |
68 | + def set_password(self, user, old_password, new_password): | |
69 | + raise NotImplementedError, 'set_password' | |
70 | + | |
71 | +class LocalAuthenticationProvider(AuthenticationProvider): | |
72 | + | |
73 | + def register_user(self, user_doc): | |
74 | + from pyforge import model as M | |
75 | + return M.User(**user_doc) | |
76 | + | |
77 | + def _login(self): | |
78 | + user = self.by_username(self.request.params['username']) | |
79 | + if not self._validate_password(user, self.request.params['password']): | |
80 | + raise exc.HTTPUnauthorized() | |
81 | + return user | |
82 | + | |
83 | + def _validate_password(self, user, password): | |
84 | + if user is None: return False | |
85 | + if not user.password: return False | |
86 | + salt = str(user.password[6:6+user.SALT_LEN]) | |
87 | + check = self._encode_password(password, salt) | |
88 | + if check != user.password: return False | |
89 | + return True | |
90 | + | |
91 | + def by_username(self, username): | |
92 | + from pyforge import model as M | |
93 | + return M.User.query.get(username=username) | |
94 | + | |
95 | + def set_password(self, user, old_password, new_password): | |
96 | + user.password = self._encode_password(new_password) | |
97 | + | |
98 | + def _encode_password(self, password, salt=None): | |
99 | + from pyforge import model as M | |
100 | + if salt is None: | |
101 | + salt = ''.join(chr(randint(1, 0x7f)) | |
102 | + for i in xrange(M.User.SALT_LEN)) | |
103 | + hashpass = sha256(salt + password.encode('utf-8')).digest() | |
104 | + return 'sha256' + salt + b64encode(hashpass) | |
105 | + | |
106 | +class LdapAuthenticationProvider(AuthenticationProvider): | |
107 | + | |
108 | + def register_user(self, user_doc): | |
109 | + from pyforge import model as M | |
110 | + password = user_doc.pop('password', None) | |
111 | + result = M.User(**user_doc) | |
112 | + dn = 'uid=%s,%s' % (user_doc['username'], config['auth.ldap.suffix']) | |
113 | + try: | |
114 | + con = ldap.initialize(config['auth.ldap.server']) | |
115 | + con.bind_s(config['auth.ldap.admin_dn'], | |
116 | + config['auth.ldap.admin_password']) | |
117 | + ldap_info = dict( | |
118 | + uid=user_doc['username'], | |
119 | + displayName=user_doc['display_name'], | |
120 | + cn=user_doc['display_name'], | |
121 | + userPassword=password, | |
122 | + objectClass=['inetOrgPerson'], | |
123 | + givenName=user_doc['display_name'].split()[0], | |
124 | + sn=user_doc['display_name'].split()[-1]) | |
125 | + ldap_info = dict((k,v) for k,v in ldap_info.iteritems() | |
126 | + if v is not None) | |
127 | + try: | |
128 | + con.add_s(dn, ldap_info.items()) | |
129 | + except ldap.ALREADY_EXISTS: | |
130 | + con.modify_s(dn, [(ldap.MOD_REPLACE, k, v) | |
131 | + for k,v in ldap_info.iteritems()]) | |
132 | + con.unbind_s() | |
133 | + except: | |
134 | + raise | |
135 | + return result | |
136 | + | |
137 | + def by_username(self, username): | |
138 | + from pyforge import model as M | |
139 | + return M.User.query.get(username=username) | |
140 | + | |
141 | + def set_password(self, user, old_password, new_password): | |
142 | + try: | |
143 | + dn = 'uid=%s,%s' % (self.username, config['auth.ldap.suffix']) | |
144 | + con = ldap.initialize(config['auth.ldap.server']) | |
145 | + con.bind_s(dn, old_password) | |
146 | + con.modify_s(dn, [(ldap.MOD_REPLACE, 'userPassword', new_password)]) | |
147 | + con.unbind_s() | |
148 | + except ldap.INVALID_CREDENTIALS: | |
149 | + raise exc.HTTPUnauthorized() | |
150 | + | |
151 | + def _login(self): | |
152 | + from pyforge import model as M | |
153 | + user = M.User.query.get(username=self.request.params['username']) | |
154 | + if user is None: raise exc.HTTPUnauthorized() | |
155 | + try: | |
156 | + dn = 'uid=%s,%s' % (user.username, config['auth.ldap.suffix']) | |
157 | + con = ldap.initialize(config['auth.ldap.server']) | |
158 | + con.bind_s(dn, self.request.params['password']) | |
159 | + con.unbind_s() | |
160 | + except ldap.INVALID_CREDENTIALS: | |
161 | + raise exc.HTTPUnauthorized() | |
162 | + return user | |
163 | + | |
164 | +class ProjectRegistrationProvider(object): | |
165 | + | |
166 | + @classmethod | |
167 | + def get(cls): | |
168 | + method = config.get('registration.method', 'local') | |
169 | + for ep in pkg_resources.iter_entry_points('pyforge.project_registration', method): | |
170 | + return ep.load()() | |
171 | + return None | |
172 | + | |
173 | + def register_project(self, neighborhood, shortname, user, user_project): | |
174 | + '''Register a new project in the neighborhood. The given user will | |
175 | + become the project's superuser. If no user is specified, c.user is used. | |
176 | + ''' | |
177 | + from pyforge import model as M | |
178 | + assert h.re_path_portion.match(shortname.replace('/', '')), \ | |
179 | + 'Invalid project shortname' | |
180 | + p = M.Project.query.get(shortname=shortname) | |
181 | + if p: | |
182 | + assert p.neighborhood == neighborhood, ( | |
183 | + 'Project %s exists in neighborhood %s' % ( | |
184 | + shortname, p.neighborhood.name)) | |
185 | + return p | |
186 | + database = 'project:' + shortname.replace('/', ':').replace(' ', '_') | |
187 | + p = M.Project(neighborhood_id=neighborhood._id, | |
188 | + shortname=shortname, | |
189 | + name=shortname, | |
190 | + short_description='', | |
191 | + description=(shortname + '\n' | |
192 | + + '=' * 80 + '\n\n' | |
193 | + + 'You can edit this description in the admin page'), | |
194 | + database=database, | |
195 | + last_updated = datetime.utcnow(), | |
196 | + is_root=True) | |
197 | + try: | |
198 | + p.configure_project_database() | |
199 | + with h.push_config(c, project=p, user=user): | |
200 | + assert M.ProjectRole.query.find().count() == 0, \ | |
201 | + 'Project roles already exist' | |
202 | + # Install default named roles (#78) | |
203 | + role_owner = M.ProjectRole(name='Admin') | |
204 | + role_developer = M.ProjectRole(name='Developer') | |
205 | + role_member = M.ProjectRole(name='Member') | |
206 | + role_auth = M.ProjectRole(name='*authenticated') | |
207 | + role_anon = M.ProjectRole(name='*anonymous') | |
208 | + # Setup subroles | |
209 | + role_owner.roles = [ role_developer._id ] | |
210 | + role_developer.roles = [ role_member._id ] | |
211 | + p.acl['create'] = [ role_owner._id ] | |
212 | + p.acl['read'] = [ role_owner._id, role_developer._id, role_member._id, | |
213 | + role_anon._id ] | |
214 | + p.acl['update'] = [ role_owner._id ] | |
215 | + p.acl['delete'] = [ role_owner._id ] | |
216 | + p.acl['tool'] = [ role_owner._id ] | |
217 | + p.acl['security'] = [ role_owner._id ] | |
218 | + pr = user.project_role() | |
219 | + pr.roles = [ role_owner._id, role_developer._id, role_member._id ] | |
220 | + # Setup builtin tool applications | |
221 | + if user_project: | |
222 | + p.install_app('profile', 'profile') | |
223 | + else: | |
224 | + p.install_app('home', 'home') | |
225 | + p.install_app('admin', 'admin') | |
226 | + p.install_app('search', 'search') | |
227 | + ThreadLocalORMSession.flush_all() | |
228 | + except: | |
229 | + ThreadLocalORMSession.close_all() | |
230 | + log.exception('Error registering project, attempting to drop %s', | |
231 | + database) | |
232 | + try: | |
233 | + session(p).impl.bind._conn.drop_database(database) | |
234 | + except: | |
235 | + log.exception('Error dropping database %s', database) | |
236 | + pass | |
237 | + raise | |
238 | + g.publish('react', 'forge.project_created') | |
239 | + return p | |
240 | + | |
241 | + def register_subproject(self, project, name, install_apps): | |
242 | + from pyforge import model as M | |
243 | + assert h.re_path_portion.match(name), 'Invalid subproject shortname' | |
244 | + shortname = project.shortname + '/' + name | |
245 | + sp = M.Project( | |
246 | + parent_id=project._id, | |
247 | + neighborhood_id=project.neighborhood_id, | |
248 | + shortname=shortname, | |
249 | + name=name, | |
250 | + database=project.database, | |
251 | + last_updated = datetime.utcnow(), | |
252 | + is_root=False) | |
253 | + with h.push_config(c, project=sp): | |
254 | + M.AppConfig.query.remove(dict(project_id=c.project._id)) | |
255 | + if install_apps: | |
256 | + sp.install_app('home', 'home') | |
257 | + sp.install_app('admin', 'admin') | |
258 | + sp.install_app('search', 'search') | |
259 | + g.publish('react', 'forge.project_created') | |
260 | + return sp | |
261 | + | |
262 | + | |
263 | +class LocalProjectRegistrationProvider(ProjectRegistrationProvider): | |
264 | + pass |
@@ -6,7 +6,6 @@ from .project import Theme, Neighborhood, NeighborhoodFile, Project, ProjectCate | ||
6 | 6 | from .discuss import Discussion, Thread, PostHistory, Post, Attachment |
7 | 7 | from .artifact import Artifact, Message, VersionedArtifact, Snapshot, ArtifactLink, Feed, AwardFile, Award, AwardGrant |
8 | 8 | from .auth import User, ProjectRole, OpenId, EmailAddress, ApiToken |
9 | -from .auth import AuthenticationProvider, LocalAuthenticationProvider, LdapAuthenticationProvider | |
10 | 9 | from .openid_model import OpenIdStore, OpenIdAssociation, OpenIdNonce |
11 | 10 | from .filesystem import File |
12 | 11 | from .tag import TagEvent, Tag, UserTags |
@@ -4,40 +4,25 @@ import urllib | ||
4 | 4 | import hmac |
5 | 5 | import hashlib |
6 | 6 | from datetime import timedelta, datetime |
7 | -from base64 import b64encode | |
8 | -from random import randint | |
9 | 7 | from hashlib import sha256 |
10 | 8 | |
11 | -import ldap | |
12 | 9 | import iso8601 |
13 | 10 | import pymongo |
14 | -import pkg_resources | |
15 | 11 | from pylons import c, g, request |
16 | -from tg import config | |
17 | -from webob import exc | |
18 | 12 | |
19 | 13 | from ming import schema as S |
20 | -from ming.utils import LazyProperty | |
21 | 14 | from ming.orm.ormsession import ThreadLocalORMSession |
22 | 15 | from ming.orm import session, state, MappedClass |
23 | 16 | from ming.orm import FieldProperty, RelationProperty, ForeignIdProperty |
24 | 17 | |
25 | 18 | from pyforge.lib import helpers as h |
19 | +from pyforge.lib import plugin | |
26 | 20 | from .session import ProjectSession |
27 | 21 | from .session import main_doc_session, main_orm_session |
28 | 22 | from .session import project_doc_session, project_orm_session |
29 | 23 | |
30 | 24 | log = logging.getLogger(__name__) |
31 | 25 | |
32 | -SALT_LENGTH=8 | |
33 | - | |
34 | -def encode_password(password, salt=None): | |
35 | - if salt is None: | |
36 | - salt = ''.join(chr(randint(1, 0x7f)) | |
37 | - for i in xrange(SALT_LENGTH)) | |
38 | - hashpass = sha256(salt + password.encode('utf-8')).digest() | |
39 | - return 'sha256' + salt + b64encode(hashpass) | |
40 | - | |
41 | 26 | class ApiToken(MappedClass): |
42 | 27 | class __mongometa__: |
43 | 28 | name='api_token' |
@@ -180,7 +165,7 @@ class User(MappedClass): | ||
180 | 165 | |
181 | 166 | @classmethod |
182 | 167 | def by_username(cls, name): |
183 | - return AuthenticationProvider.get(request).by_username(name) | |
168 | + return plugin.AuthenticationProvider.get(request).by_username(name) | |
184 | 169 | |
185 | 170 | def address_object(self, addr): |
186 | 171 | return EmailAddress.query.get(_id=addr, claimed_by_user_id=self._id) |
@@ -204,7 +189,7 @@ class User(MappedClass): | ||
204 | 189 | @classmethod |
205 | 190 | def register(cls, doc, make_project=True): |
206 | 191 | from pyforge import model as M |
207 | - result = AuthenticationProvider.get(request).register_user(doc) | |
192 | + result = plugin.AuthenticationProvider.get(request).register_user(doc) | |
208 | 193 | if result and make_project: |
209 | 194 | n = M.Neighborhood.query.get(name='Users') |
210 | 195 | n.register_project('u/' + result.username, result, user_project=True) |
@@ -247,7 +232,7 @@ class User(MappedClass): | ||
247 | 232 | return ProjectRole.query.get(user_id=self._id) |
248 | 233 | |
249 | 234 | def set_password(self, new_password): |
250 | - return AuthenticationProvider.get(request).set_password( | |
235 | + return plugin.AuthenticationProvider.get(request).set_password( | |
251 | 236 | self, self.password, new_password) |
252 | 237 | |
253 | 238 | @classmethod |
@@ -301,128 +286,3 @@ class ProjectRole(MappedClass): | ||
301 | 286 | for rr in pr.role_iter(visited): |
302 | 287 | yield rr |
303 | 288 | |
304 | -class AuthenticationProvider(object): | |
305 | - | |
306 | - def __init__(self, request): | |
307 | - self.request = request | |
308 | - | |
309 | - @classmethod | |
310 | - def get(cls, request): | |
311 | - method = config.get('auth.method', 'local') | |
312 | - for ep in pkg_resources.iter_entry_points('pyforge.auth', method): | |
313 | - return ep.load()(request) | |
314 | - return None | |
315 | - | |
316 | - @LazyProperty | |
317 | - def session(self): | |
318 | - return self.request.environ['beaker.session'] | |
319 | - | |
320 | - def authenticate_request(self): | |
321 | - return User.query.get(_id=self.session.get('userid', None)) | |
322 | - | |
323 | - def register_user(self, user_doc): | |
324 | - raise NotImplementedError, 'register_user' | |
325 | - | |
326 | - def _login(self): | |
327 | - raise NotImplementedError, '_login' | |
328 | - | |
329 | - def login(self, user=None): | |
330 | - try: | |
331 | - if user is None: user = self._login() | |
332 | - self.session['userid'] = user._id | |
333 | - self.session.save() | |
334 | - return user | |
335 | - except exc.HTTPUnauthorized: | |
336 | - self.logout() | |
337 | - raise | |
338 | - | |
339 | - def logout(self): | |
340 | - self.session['userid'] = None | |
341 | - self.session.save() | |
342 | - | |
343 | - def by_username(self, username): | |
344 | - raise NotImplementedError, 'by_username' | |
345 | - | |
346 | - def set_password(self, user, old_password, new_password): | |
347 | - raise NotImplementedError, 'set_password' | |
348 | - | |
349 | -class LocalAuthenticationProvider(AuthenticationProvider): | |
350 | - | |
351 | - def register_user(self, user_doc): | |
352 | - return User(**user_doc) | |
353 | - | |
354 | - def _login(self): | |
355 | - user = self.by_username(self.request.params['username']) | |
356 | - if not self._validate_password(user, self.request.params['password']): | |
357 | - raise exc.HTTPUnauthorized() | |
358 | - return user | |
359 | - | |
360 | - def _validate_password(self, user, password): | |
361 | - if user is None: return False | |
362 | - if not user.password: return False | |
363 | - salt = str(user.password[6:6+user.SALT_LEN]) | |
364 | - check = encode_password(password, salt) | |
365 | - if check != user.password: return False | |
366 | - return True | |
367 | - | |
368 | - def by_username(self, username): | |
369 | - return User.query.get(username=username) | |
370 | - | |
371 | - def set_password(self, user, old_password, new_password): | |
372 | - user.password = encode_password(new_password) | |
373 | - | |
374 | -class LdapAuthenticationProvider(AuthenticationProvider): | |
375 | - | |
376 | - def register_user(self, user_doc): | |
377 | - password = user_doc.pop('password', None) | |
378 | - result = User(**user_doc) | |
379 | - dn = 'uid=%s,%s' % (user_doc['username'], config['auth.ldap.suffix']) | |
380 | - try: | |
381 | - con = ldap.initialize(config['auth.ldap.server']) | |
382 | - con.bind_s(config['auth.ldap.admin_dn'], | |
383 | - config['auth.ldap.admin_password']) | |
384 | - ldap_info = dict( | |
385 | - uid=user_doc['username'], | |
386 | - displayName=user_doc['display_name'], | |
387 | - cn=user_doc['display_name'], | |
388 | - userPassword=password, | |
389 | - objectClass=['inetOrgPerson'], | |
390 | - givenName=user_doc['display_name'].split()[0], | |
391 | - sn=user_doc['display_name'].split()[-1]) | |
392 | - ldap_info = dict((k,v) for k,v in ldap_info.iteritems() | |
393 | - if v is not None) | |
394 | - try: | |
395 | - con.add_s(dn, ldap_info.items()) | |
396 | - except ldap.ALREADY_EXISTS: | |
397 | - con.modify_s(dn, [(ldap.MOD_REPLACE, k, v) | |
398 | - for k,v in ldap_info.iteritems()]) | |
399 | - con.unbind_s() | |
400 | - except: | |
401 | - raise | |
402 | - return result | |
403 | - | |
404 | - def by_username(self, username): | |
405 | - return User.query.get(username=username) | |
406 | - | |
407 | - def set_password(self, user, old_password, new_password): | |
408 | - try: | |
409 | - dn = 'uid=%s,%s' % (self.username, config['auth.ldap.suffix']) | |
410 | - con = ldap.initialize(config['auth.ldap.server']) | |
411 | - con.bind_s(dn, old_password) | |
412 | - con.modify_s(dn, [(ldap.MOD_REPLACE, 'userPassword', new_password)]) | |
413 | - con.unbind_s() | |
414 | - except ldap.INVALID_CREDENTIALS: | |
415 | - raise exc.HTTPUnauthorized() | |
416 | - | |
417 | - def _login(self): | |
418 | - user = User.query.get(username=self.request.params['username']) | |
419 | - if user is None: raise exc.HTTPUnauthorized() | |
420 | - try: | |
421 | - dn = 'uid=%s,%s' % (user.username, config['auth.ldap.suffix']) | |
422 | - con = ldap.initialize(config['auth.ldap.server']) | |
423 | - con.bind_s(dn, self.request.params['password']) | |
424 | - con.unbind_s() | |
425 | - except ldap.INVALID_CREDENTIALS: | |
426 | - raise exc.HTTPUnauthorized() | |
427 | - return user | |
428 | - |
@@ -2,6 +2,7 @@ import logging | ||
2 | 2 | import warnings |
3 | 3 | from datetime import datetime, timedelta |
4 | 4 | |
5 | +from tg import config | |
5 | 6 | from pylons import c, g, request |
6 | 7 | import pkg_resources |
7 | 8 | from webob import exc |
@@ -14,6 +15,7 @@ from ming.orm.property import FieldProperty, RelationProperty, ForeignIdProperty | ||
14 | 15 | from ming.orm.ormsession import ThreadLocalORMSession |
15 | 16 | |
16 | 17 | from pyforge.lib import helpers as h |
18 | +from pyforge.lib import plugin | |
17 | 19 | from .session import main_doc_session, main_orm_session |
18 | 20 | from .session import project_doc_session, project_orm_session |
19 | 21 |
@@ -127,70 +129,8 @@ class Neighborhood(MappedClass): | ||
127 | 129 | '''Register a new project in the neighborhood. The given user will |
128 | 130 | become the project's superuser. If no user is specified, c.user is used. |
129 | 131 | ''' |
130 | - assert h.re_path_portion.match(shortname.replace('/', '')), \ | |
131 | - 'Invalid project shortname' | |
132 | - from . import auth | |
133 | - if user is None: user = c.user | |
134 | - p = Project.query.get(shortname=shortname) | |
135 | - if p: | |
136 | - assert p.neighborhood == self, ( | |
137 | - 'Project %s exists in neighborhood %s' % ( | |
138 | - shortname, p.neighborhood.name)) | |
139 | - return p | |
140 | - database = 'project:' + shortname.replace('/', ':').replace(' ', '_') | |
141 | - p = Project(neighborhood_id=self._id, | |
142 | - shortname=shortname, | |
143 | - name=shortname, | |
144 | - short_description='', | |
145 | - description=(shortname + '\n' | |
146 | - + '=' * 80 + '\n\n' | |
147 | - + 'You can edit this description in the admin page'), | |
148 | - database=database, | |
149 | - last_updated = datetime.utcnow(), | |
150 | - is_root=True) | |
151 | - try: | |
152 | - p.configure_project_database() | |
153 | - with h.push_config(c, project=p, user=user): | |
154 | - assert auth.ProjectRole.query.find().count() == 0, \ | |
155 | - 'Project roles already exist' | |
156 | - # Install default named roles (#78) | |
157 | - role_owner = auth.ProjectRole(name='Admin') | |
158 | - role_developer = auth.ProjectRole(name='Developer') | |
159 | - role_member = auth.ProjectRole(name='Member') | |
160 | - role_auth = auth.ProjectRole(name='*authenticated') | |
161 | - role_anon = auth.ProjectRole(name='*anonymous') | |
162 | - # Setup subroles | |
163 | - role_owner.roles = [ role_developer._id ] | |
164 | - role_developer.roles = [ role_member._id ] | |
165 | - p.acl['create'] = [ role_owner._id ] | |
166 | - p.acl['read'] = [ role_owner._id, role_developer._id, role_member._id, | |
167 | - role_anon._id ] | |
168 | - p.acl['update'] = [ role_owner._id ] | |
169 | - p.acl['delete'] = [ role_owner._id ] | |
170 | - p.acl['tool'] = [ role_owner._id ] | |
171 | - p.acl['security'] = [ role_owner._id ] | |
172 | - pr = user.project_role() | |
173 | - pr.roles = [ role_owner._id, role_developer._id, role_member._id ] | |
174 | - # Setup builtin tool applications | |
175 | - if user_project: | |
176 | - p.install_app('profile', 'profile') | |
177 | - else: | |
178 | - p.install_app('home', 'home') | |
179 | - p.install_app('admin', 'admin') | |
180 | - p.install_app('search', 'search') | |
181 | - ThreadLocalORMSession.flush_all() | |
182 | - except: | |
183 | - ThreadLocalORMSession.close_all() | |
184 | - log.exception('Error registering project, attempting to drop %s', | |
185 | - database) | |
186 | - try: | |
187 | - session(p).impl.bind._conn.drop_database(database) | |
188 | - except: | |
189 | - log.exception('Error dropping database %s', database) | |
190 | - pass | |
191 | - raise | |
192 | - g.publish('react', 'forge.project_created') | |
193 | - return p | |
132 | + provider = plugin.ProjectRegistrationProvider.get() | |
133 | + return provider.register_project(self, shortname, user or c.user, user_project) | |
194 | 134 | |
195 | 135 | def bind_controller(self, controller): |
196 | 136 | from pyforge.controllers.project import NeighborhoodController |
@@ -434,24 +374,8 @@ class Project(MappedClass): | ||
434 | 374 | 'options.mount_point':mount_point}).first() |
435 | 375 | |
436 | 376 | def new_subproject(self, name, install_apps=True): |
437 | - assert h.re_path_portion.match(name), 'Invalid subproject shortname' | |
438 | - shortname = self.shortname + '/' + name | |
439 | - sp = Project( | |
440 | - parent_id=self._id, | |
441 | - neighborhood_id=self.neighborhood_id, | |
442 | - shortname=shortname, | |
443 | - name=name, | |
444 | - database=self.database, | |
445 | - last_updated = datetime.utcnow(), | |
446 | - is_root=False) | |
447 | - with h.push_config(c, project=sp): | |
448 | - AppConfig.query.remove(dict(project_id=c.project._id)) | |
449 | - if install_apps: | |
450 | - sp.install_app('home', 'home') | |
451 | - sp.install_app('admin', 'admin') | |
452 | - sp.install_app('search', 'search') | |
453 | - g.publish('react', 'forge.project_created') | |
454 | - return sp | |
377 | + provider = plugin.ProjectRegistrationProvider.get() | |
378 | + return provider.register_subproject(self, name, install_apps) | |
455 | 379 | |
456 | 380 | def delete(self): |
457 | 381 | # Cascade to subprojects |
@@ -52,7 +52,7 @@ class Repository(Artifact): | ||
52 | 52 | return os.path.join(self.fs_path, self.name) |
53 | 53 | |
54 | 54 | def scm_host(self): |
55 | - return self.tool + config.get('scm.host_suffix', '.' + pylons.request.host) | |
55 | + return self.tool + config.get('scm.host', '.' + pylons.request.host) | |
56 | 56 | |
57 | 57 | @property |
58 | 58 | def scm_url_path(self): |
@@ -12,6 +12,7 @@ from ming.orm.ormsession import ThreadLocalORMSession | ||
12 | 12 | import pyforge.model.auth |
13 | 13 | from pyforge.lib.app_globals import Globals |
14 | 14 | from pyforge import model as M |
15 | +from pyforge.lib import plugin | |
15 | 16 | from pyforge.tests import helpers |
16 | 17 | |
17 | 18 | def setUp(): |
@@ -22,8 +23,9 @@ def setUp(): | ||
22 | 23 | @with_setup(setUp) |
23 | 24 | def test_password_encoder(): |
24 | 25 | # Verify salt |
25 | - assert M.auth.encode_password('test_pass') != M.auth.encode_password('test_pass') | |
26 | - assert M.auth.encode_password('test_pass', '0000') == M.auth.encode_password('test_pass', '0000') | |
26 | + ep = plugin.LocalAuthenticationProvider(Request.blank('/'))._encode_password | |
27 | + assert ep('test_pass') != ep('test_pass') | |
28 | + assert ep('test_pass', '0000') == ep('test_pass', '0000') | |
27 | 29 | |
28 | 30 | @with_setup(setUp) |
29 | 31 | def test_email_address(): |
@@ -66,9 +68,10 @@ def test_user(): | ||
66 | 68 | username='nosetest_user')) |
67 | 69 | ThreadLocalORMSession.flush_all() |
68 | 70 | assert u.private_project().shortname == 'u/nosetest_user' |
69 | - assert len(list(u.role_iter())) == 3 | |
71 | + roles = list(u.role_iter()) | |
72 | + assert len(roles) == 3, roles | |
70 | 73 | u.set_password('foo') |
71 | - provider = M.LocalAuthenticationProvider(Request.blank('/')) | |
74 | + provider = plugin.LocalAuthenticationProvider(Request.blank('/')) | |
72 | 75 | assert provider._validate_password(u, 'foo') |
73 | 76 | assert not provider._validate_password(u, 'foobar') |
74 | 77 | u.set_password('foobar') |
@@ -73,10 +73,14 @@ setup( | ||
73 | 73 | sfx = pyforge.ext.sfx:SFXApp |
74 | 74 | |
75 | 75 | [pyforge.auth] |
76 | - local = pyforge.model.auth:LocalAuthenticationProvider | |
77 | - ldap = pyforge.model.auth.LdapAuthenticationProvider | |
76 | + local = pyforge.lib.plugin:LocalAuthenticationProvider | |
77 | + ldap = pyforge.lib.plugin:LdapAuthenticationProvider | |
78 | 78 | sfx = pyforge.ext.sfx:SFXAuthenticationProvider |
79 | 79 | |
80 | + [pyforge.project_registration] | |
81 | + local = pyforge.lib.plugin:LocalProjectRegistrationProvider | |
82 | + sfx = pyforge.ext.sfx:SFXProjectRegistrationProvider | |
83 | + | |
80 | 84 | [flyway.migrations] |
81 | 85 | pyforge = pyforge.migrations |
82 | 86 |