• R/O
  • HTTP
  • SSH
  • HTTPS

提交

標籤
無標籤

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

allura


Commit MetaInfo

修訂41539b93a42eea4574b8020bdaf7f3962eedb53f (tree)
時間2010-05-21 01:35:11
作者Rick Copeland <rcopeland@geek...>
CommiterRick Copeland

Log Message

Plugin refactoring (not Tools)

This commit includes

- Creation of ProjectRegistrationProvider plugins
- Refactoring of AuthenticationProvider and ProjectRegistrationProvider into pyforge.lib.plugin
- Updates to SFX naming convention so /p/foo is registered as 'foo', not 'foo.p'

Change Summary

差異

--- a/pyforge/development.ini
+++ b/pyforge/development.ini
@@ -31,6 +31,8 @@ beaker.session.type = cookie
3131 beaker.session.secret = 61ece7db-ba8d-49fe-a923-ab444741708c
3232 beaker.session.validate_key = 714bfe3612c42390726f
3333
34+registration.method = local
35+
3436 auth.method = local
3537 auth.ldap.server = ldap://localhost
3638 auth.ldap.suffix = ou=people,dc=example,dc=com
--- a/pyforge/pyforge/controllers/auth.py
+++ b/pyforge/pyforge/controllers/auth.py
@@ -11,6 +11,7 @@ from pyforge import model as M
1111 from pyforge.lib.oid_helper import verify_oid, process_oid
1212 from pyforge.lib.security import require_authenticated
1313 from pyforge.lib import helpers as h
14+from pyforge.lib import plugin
1415 from pyforge.lib.widgets import SubscriptionForm
1516
1617 log = logging.getLogger(__name__)
@@ -107,7 +108,7 @@ class AuthController(object):
107108 for open_id in open_ids.split(','):
108109 oid = M.OpenId.upsert(open_id, display_name+"'s OpenId")
109110 user.claim_openid(open_id)
110- M.AuthenticationProvider.get(request).login(user)
111+ plugin.AuthenticationProvider.get(request).login(user)
111112 flash('User "%s" registered' % user.display_name)
112113 redirect('/')
113114
@@ -170,12 +171,12 @@ class AuthController(object):
170171
171172 @expose()
172173 def logout(self):
173- M.AuthenticationProvider.get(request).logout()
174+ plugin.AuthenticationProvider.get(request).logout()
174175 redirect('/')
175176
176177 @expose()
177178 def do_login(self, came_from=None, **kw):
178- user = M.AuthenticationProvider.get(request).login()
179+ user = plugin.AuthenticationProvider.get(request).login()
179180 flash('Welcome back, %s' % user.display_name)
180181 if came_from and came_from != request.url:
181182 redirect(came_from)
--- a/pyforge/pyforge/controllers/root.py
+++ b/pyforge/pyforge/controllers/root.py
@@ -15,6 +15,7 @@ import pyforge
1515 from pyforge.app import SitemapEntry
1616 from pyforge.lib.base import BaseController
1717 from pyforge.lib import helpers as h
18+from pyforge.lib import plugin
1819 from pyforge.controllers.error import ErrorController
1920 from pyforge import model as M
2021 from pyforge.lib.widgets import project_list as plw
@@ -67,7 +68,7 @@ class RootController(BaseController):
6768 def _setup_request(self):
6869 uid = session.get('userid', None)
6970 c.project = c.app = None
70- c.user = M.AuthenticationProvider.get(request).authenticate_request()
71+ c.user = plugin.AuthenticationProvider.get(request).authenticate_request()
7172 c.queued_messages = []
7273
7374 @expose('pyforge.templates.project_list')
--- a/pyforge/pyforge/controllers/test.py
+++ b/pyforge/pyforge/controllers/test.py
@@ -16,6 +16,7 @@ import pyforge
1616 from pyforge.lib.base import BaseController
1717 from pyforge.lib.security import require, require_authenticated, has_project_access, has_artifact_access
1818 from pyforge.lib import helpers as h
19+from pyforge.lib import plugin
1920 from pyforge import model as M
2021 from .root import RootController
2122 from .project import ProjectController
@@ -90,7 +91,7 @@ class TestController(BaseController, ProjectController):
9091 def __call__(self, environ, start_response):
9192 c.app = None
9293 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(
9495 environ.get('username', 'test_admin'))
9596 return BaseController.__call__(self, environ, start_response)
9697
--- a/pyforge/pyforge/ext/sfx/lib/sfx_api.py
+++ b/pyforge/pyforge/ext/sfx/lib/sfx_api.py
@@ -59,32 +59,31 @@ class SFXProjectApi(object):
5959 def _connect(self):
6060 return closing(httplib.HTTPConnection(self.project_host or request.host))
6161
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
6565 else:
66- path = p.shortname
66+ path = shortname
6767 parts = path.split('/')[1:]
6868 return '.'.join(reversed(parts))
6969
70- def create(self, p):
70+ def create(self, neighborhood, shortname, short_description='No description'):
7171 with self._connect() as conn:
72- ug_name = self._unix_group_name(p)
72+ ug_name = self._unix_group_name(neighborhood, shortname)
7373 args = dict(
7474 user_id=c.user.sfx_userid,
7575 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)
7878 conn.request('POST', self.project_path, json.dumps(args))
7979 response = conn.getresponse()
8080 assert response.status == 201, \
8181 'Bad status from sfx create: %s' % (response.status)
82- response.read()
83- self.update(p)
82+ return response.read()
8483
8584 def read(self, p):
8685 with self._connect() as conn:
87- ug_name = self._unix_group_name(p)
86+ ug_name = self._unix_group_name(p.neighborhood, p.shortname)
8887 conn.request('GET', self.project_path + '/name/' + ug_name + '/json')
8988 response = conn.getresponse()
9089 assert response.status == 200, \
@@ -93,7 +92,7 @@ class SFXProjectApi(object):
9392
9493 def update(self, p):
9594 with self._connect() as conn:
96- ug_name = self._unix_group_name(p)
95+ ug_name = self._unix_group_name(p.neighborhood, p.shortname)
9796 args = dict(
9897 user_id=c.user.sfx_userid,
9998 group_name=p.shortname,
@@ -110,7 +109,7 @@ class SFXProjectApi(object):
110109
111110 def delete(self, p):
112111 with self._connect() as conn:
113- ug_name = self._unix_group_name(p)
112+ ug_name = self._unix_group_name(p.neighborhood, p.shortname)
114113 conn.request('DELETE', self.project_path + '/' + ug_name)
115114 response = conn.getresponse()
116115 assert response.status in (200, 404, 410), \
--- a/pyforge/pyforge/ext/sfx/sfx_main.py
+++ b/pyforge/pyforge/ext/sfx/sfx_main.py
@@ -10,6 +10,8 @@ from pyforge import version
1010 from pyforge.app import Application
1111 from pyforge.lib.decorators import react
1212 from pyforge.lib import search
13+from pyforge.lib import helpers as h
14+from pyforge.lib import plugin
1315 from pyforge import model as M
1416
1517 from .lib.sfx_api import SFXProjectApi, SFXUserApi
@@ -28,7 +30,7 @@ class SFXApp(Application):
2830 @react('forge.project_created')
2931 def project_created(cls, routing_key, doc):
3032 api = SFXProjectApi()
31- api.create(pylons.c.project)
33+ api.update(pylons.c.project)
3234
3335 @classmethod
3436 @react('forge.project_updated')
@@ -54,7 +56,7 @@ class SFXApp(Application):
5456 def uninstall(self, project):
5557 pass # pragma no cover
5658
57-class SFXAuthenticationProvider(M.AuthenticationProvider):
59+class SFXAuthenticationProvider(plugin.AuthenticationProvider):
5860
5961 def __init__(self, request):
6062 super(SFXAuthenticationProvider, self).__init__(request)
@@ -106,6 +108,24 @@ class SFXAuthenticationProvider(M.AuthenticationProvider):
106108 with fake_pylons_context(self.request):
107109 return api.upsert_user(username, extra)
108110
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)
109129
110130 @contextmanager
111131 def fake_pylons_context(request):
--- /dev/null
+++ b/pyforge/pyforge/lib/plugin.py
@@ -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
--- a/pyforge/pyforge/model/__init__.py
+++ b/pyforge/pyforge/model/__init__.py
@@ -6,7 +6,6 @@ from .project import Theme, Neighborhood, NeighborhoodFile, Project, ProjectCate
66 from .discuss import Discussion, Thread, PostHistory, Post, Attachment
77 from .artifact import Artifact, Message, VersionedArtifact, Snapshot, ArtifactLink, Feed, AwardFile, Award, AwardGrant
88 from .auth import User, ProjectRole, OpenId, EmailAddress, ApiToken
9-from .auth import AuthenticationProvider, LocalAuthenticationProvider, LdapAuthenticationProvider
109 from .openid_model import OpenIdStore, OpenIdAssociation, OpenIdNonce
1110 from .filesystem import File
1211 from .tag import TagEvent, Tag, UserTags
--- a/pyforge/pyforge/model/auth.py
+++ b/pyforge/pyforge/model/auth.py
@@ -4,40 +4,25 @@ import urllib
44 import hmac
55 import hashlib
66 from datetime import timedelta, datetime
7-from base64 import b64encode
8-from random import randint
97 from hashlib import sha256
108
11-import ldap
129 import iso8601
1310 import pymongo
14-import pkg_resources
1511 from pylons import c, g, request
16-from tg import config
17-from webob import exc
1812
1913 from ming import schema as S
20-from ming.utils import LazyProperty
2114 from ming.orm.ormsession import ThreadLocalORMSession
2215 from ming.orm import session, state, MappedClass
2316 from ming.orm import FieldProperty, RelationProperty, ForeignIdProperty
2417
2518 from pyforge.lib import helpers as h
19+from pyforge.lib import plugin
2620 from .session import ProjectSession
2721 from .session import main_doc_session, main_orm_session
2822 from .session import project_doc_session, project_orm_session
2923
3024 log = logging.getLogger(__name__)
3125
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-
4126 class ApiToken(MappedClass):
4227 class __mongometa__:
4328 name='api_token'
@@ -180,7 +165,7 @@ class User(MappedClass):
180165
181166 @classmethod
182167 def by_username(cls, name):
183- return AuthenticationProvider.get(request).by_username(name)
168+ return plugin.AuthenticationProvider.get(request).by_username(name)
184169
185170 def address_object(self, addr):
186171 return EmailAddress.query.get(_id=addr, claimed_by_user_id=self._id)
@@ -204,7 +189,7 @@ class User(MappedClass):
204189 @classmethod
205190 def register(cls, doc, make_project=True):
206191 from pyforge import model as M
207- result = AuthenticationProvider.get(request).register_user(doc)
192+ result = plugin.AuthenticationProvider.get(request).register_user(doc)
208193 if result and make_project:
209194 n = M.Neighborhood.query.get(name='Users')
210195 n.register_project('u/' + result.username, result, user_project=True)
@@ -247,7 +232,7 @@ class User(MappedClass):
247232 return ProjectRole.query.get(user_id=self._id)
248233
249234 def set_password(self, new_password):
250- return AuthenticationProvider.get(request).set_password(
235+ return plugin.AuthenticationProvider.get(request).set_password(
251236 self, self.password, new_password)
252237
253238 @classmethod
@@ -301,128 +286,3 @@ class ProjectRole(MappedClass):
301286 for rr in pr.role_iter(visited):
302287 yield rr
303288
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-
--- a/pyforge/pyforge/model/project.py
+++ b/pyforge/pyforge/model/project.py
@@ -2,6 +2,7 @@ import logging
22 import warnings
33 from datetime import datetime, timedelta
44
5+from tg import config
56 from pylons import c, g, request
67 import pkg_resources
78 from webob import exc
@@ -14,6 +15,7 @@ from ming.orm.property import FieldProperty, RelationProperty, ForeignIdProperty
1415 from ming.orm.ormsession import ThreadLocalORMSession
1516
1617 from pyforge.lib import helpers as h
18+from pyforge.lib import plugin
1719 from .session import main_doc_session, main_orm_session
1820 from .session import project_doc_session, project_orm_session
1921
@@ -127,70 +129,8 @@ class Neighborhood(MappedClass):
127129 '''Register a new project in the neighborhood. The given user will
128130 become the project's superuser. If no user is specified, c.user is used.
129131 '''
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)
194134
195135 def bind_controller(self, controller):
196136 from pyforge.controllers.project import NeighborhoodController
@@ -434,24 +374,8 @@ class Project(MappedClass):
434374 'options.mount_point':mount_point}).first()
435375
436376 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)
455379
456380 def delete(self):
457381 # Cascade to subprojects
--- a/pyforge/pyforge/model/repository.py
+++ b/pyforge/pyforge/model/repository.py
@@ -52,7 +52,7 @@ class Repository(Artifact):
5252 return os.path.join(self.fs_path, self.name)
5353
5454 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)
5656
5757 @property
5858 def scm_url_path(self):
--- a/pyforge/pyforge/tests/model/test_auth.py
+++ b/pyforge/pyforge/tests/model/test_auth.py
@@ -12,6 +12,7 @@ from ming.orm.ormsession import ThreadLocalORMSession
1212 import pyforge.model.auth
1313 from pyforge.lib.app_globals import Globals
1414 from pyforge import model as M
15+from pyforge.lib import plugin
1516 from pyforge.tests import helpers
1617
1718 def setUp():
@@ -22,8 +23,9 @@ def setUp():
2223 @with_setup(setUp)
2324 def test_password_encoder():
2425 # 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')
2729
2830 @with_setup(setUp)
2931 def test_email_address():
@@ -66,9 +68,10 @@ def test_user():
6668 username='nosetest_user'))
6769 ThreadLocalORMSession.flush_all()
6870 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
7073 u.set_password('foo')
71- provider = M.LocalAuthenticationProvider(Request.blank('/'))
74+ provider = plugin.LocalAuthenticationProvider(Request.blank('/'))
7275 assert provider._validate_password(u, 'foo')
7376 assert not provider._validate_password(u, 'foobar')
7477 u.set_password('foobar')
--- a/pyforge/setup.py
+++ b/pyforge/setup.py
@@ -73,10 +73,14 @@ setup(
7373 sfx = pyforge.ext.sfx:SFXApp
7474
7575 [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
7878 sfx = pyforge.ext.sfx:SFXAuthenticationProvider
7979
80+ [pyforge.project_registration]
81+ local = pyforge.lib.plugin:LocalProjectRegistrationProvider
82+ sfx = pyforge.ext.sfx:SFXProjectRegistrationProvider
83+
8084 [flyway.migrations]
8185 pyforge = pyforge.migrations
8286