Merge lp:~cjwatson/turnip/turnipcake-auth-uid into lp:turnip

Proposed by Colin Watson
Status: Superseded
Proposed branch: lp:~cjwatson/turnip/turnipcake-auth-uid
Merge into: lp:turnip
Diff against target: 322 lines (+291/-0) (has conflicts)
6 files modified
README.rst (+4/-0)
setup.py (+40/-0)
turnipcake.ini (+49/-0)
turnipcake/__init__.py (+22/-0)
turnipcake/models.py (+38/-0)
turnipcake/views.py (+138/-0)
Conflict adding file setup.py.  Moved existing file to setup.py.moved.
To merge this branch: bzr merge lp:~cjwatson/turnip/turnipcake-auth-uid
Reviewer Review Type Date Requested Status
Canonical Launchpad Branches Pending
Review via email: mp+248421@code.launchpad.net

This proposal has been superseded by a proposal from 2015-02-03.

Commit message

Return user.id as an additional dict entry from authenticateWithPassword; require user ID rather than name in translatePath.

Description of the change

Return user.id as an additional dict entry from authenticateWithPassword; require user ID rather than name in translatePath.

This is an incompatible API change, and goes together with https://code.launchpad.net/~cjwatson/turnip/auth-uid/+merge/248420.

To post a comment you must log in.

Unmerged revisions

10. By Colin Watson

Return user.id as an additional dict entry from authenticateWithPassword; require user ID rather than name in translatePath.

9. By William Grant

Repos have owners, and only the owner can write to them.

8. By William Grant

Actual user authentication.

7. By William Grant

Add a trivial authenticateWithPassword.

6. By William Grant

Support username-based authorisation.

5. By William Grant

Check repo existence before failing on read-onlyness.

4. By William Grant

Update translatePath to check permission itself.

3. By William Grant

Update turnip.endpoint to the new port layout.

2. By William Grant

Basic SQLite-backed turnip virtinfo service, with a JSON API to create and list repositories.

1. By William Grant

Initial paster cornice template.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'README.rst'
--- README.rst 1970-01-01 00:00:00 +0000
+++ README.rst 2015-02-03 17:12:01 +0000
@@ -0,0 +1,4 @@
1Documentation
2=============
3
4Put a brief description of 'turnipcake'.
05
=== added file 'setup.py'
--- setup.py 1970-01-01 00:00:00 +0000
+++ setup.py 2015-02-03 17:12:01 +0000
@@ -0,0 +1,40 @@
1import os
2from setuptools import setup, find_packages
3
4here = os.path.abspath(os.path.dirname(__file__))
5
6with open(os.path.join(here, 'README.rst')) as f:
7 README = f.read()
8
9
10setup(name='turnipcake',
11 version=0.1,
12 description='turnipcake',
13 long_description=README,
14 classifiers=[
15 "Programming Language :: Python",
16 "Framework :: Pylons",
17 "Topic :: Internet :: WWW/HTTP",
18 "Topic :: Internet :: WWW/HTTP :: WSGI :: Application"
19 ],
20 keywords="web services",
21 author='',
22 author_email='',
23 url='',
24 packages=find_packages(),
25 include_package_data=True,
26 zip_safe=False,
27 install_requires=[
28 'cornice',
29 'pyramid_rpc',
30 'pyramid_tm',
31 'requests',
32 'sqlalchemy',
33 'waitress',
34 'zope.sqlalchemy',
35 ],
36 entry_points="""\
37 [paste.app_factory]
38 main = turnipcake:main
39 """,
40 paster_plugins=['pyramid'])
041
=== renamed file 'setup.py' => 'setup.py.moved'
=== added directory 'turnipcake'
=== added file 'turnipcake.ini'
--- turnipcake.ini 1970-01-01 00:00:00 +0000
+++ turnipcake.ini 2015-02-03 17:12:01 +0000
@@ -0,0 +1,49 @@
1[app:main]
2use = egg:turnipcake
3
4pyramid.reload_templates = true
5pyramid.debug_authorization = false
6pyramid.debug_notfound = false
7pyramid.debug_routematch = false
8pyramid.debug_templates = true
9pyramid.default_locale_name = en
10
11sqlalchemy.url = sqlite:///turnipcake.db
12
13turnip.endpoint = http://localhost:19417/
14
15[server:main]
16use = egg:waitress#main
17host = 0.0.0.0
18port = 6543
19
20# Begin logging configuration
21
22[loggers]
23keys = root, turnipcake
24
25[handlers]
26keys = console
27
28[formatters]
29keys = generic
30
31[logger_root]
32level = INFO
33handlers = console
34
35[logger_turnipcake]
36level = DEBUG
37handlers =
38qualname = turnipcake
39
40[handler_console]
41class = StreamHandler
42args = (sys.stderr,)
43level = NOTSET
44formatter = generic
45
46[formatter_generic]
47format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
48
49# End logging configuration
050
=== added file 'turnipcake/__init__.py'
--- turnipcake/__init__.py 1970-01-01 00:00:00 +0000
+++ turnipcake/__init__.py 2015-02-03 17:12:01 +0000
@@ -0,0 +1,22 @@
1"""Main entry point
2"""
3from pyramid.config import Configurator
4from sqlalchemy import engine_from_config
5
6from .models import (
7 Base,
8 DBSession,
9 )
10
11
12def main(global_config, **settings):
13 engine = engine_from_config(settings, 'sqlalchemy.')
14 DBSession.configure(bind=engine)
15 Base.metadata.bind = engine
16 config = Configurator(settings=settings)
17 config.include("cornice")
18 config.include("pyramid_rpc.xmlrpc")
19 config.include("pyramid_tm")
20 config.add_xmlrpc_endpoint('githosting', '/githosting')
21 config.scan("turnipcake.views")
22 return config.make_wsgi_app()
023
=== added file 'turnipcake/models.py'
--- turnipcake/models.py 1970-01-01 00:00:00 +0000
+++ turnipcake/models.py 2015-02-03 17:12:01 +0000
@@ -0,0 +1,38 @@
1import datetime
2
3from sqlalchemy import (
4 Column,
5 DateTime,
6 ForeignKey,
7 Integer,
8 Text,
9 )
10from sqlalchemy.ext.declarative import declarative_base
11from sqlalchemy.orm import (
12 relationship,
13 scoped_session,
14 sessionmaker,
15 )
16
17from zope.sqlalchemy import ZopeTransactionExtension
18
19DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
20Base = declarative_base()
21
22
23class Repo(Base):
24 __tablename__ = "repo"
25 id = Column(Integer, primary_key=True)
26 name = Column(Text)
27 date_created = Column(DateTime, default=datetime.datetime.utcnow)
28 owner_id = Column(Integer, ForeignKey('user.id'))
29 owner = relationship('User', backref='repos')
30 turnip_id = Column(Text)
31
32
33class User(Base):
34 __tablename__ = "user"
35 id = Column(Integer, primary_key=True)
36 name = Column(Text)
37 date_created = Column(DateTime, default=datetime.datetime.utcnow)
38 password = Column(Text)
039
=== added file 'turnipcake/views.py'
--- turnipcake/views.py 1970-01-01 00:00:00 +0000
+++ turnipcake/views.py 2015-02-03 17:12:01 +0000
@@ -0,0 +1,138 @@
1import uuid
2import xmlrpclib
3
4from cornice.resource import resource
5from cornice.util import extract_json_data
6from pyramid.httpexceptions import (
7 HTTPCreated,
8 HTTPNotFound,
9 )
10from pyramid_rpc.xmlrpc import xmlrpc_method
11import requests
12
13from .models import (
14 DBSession,
15 Repo,
16 User,
17 )
18
19from sqlalchemy.orm.exc import NoResultFound
20
21
22@resource(collection_path='/repos', path='/repos/{name}')
23class RepoAPI(object):
24
25 def __init__(self, request):
26 self.request = request
27
28 def collection_get(self):
29 return {'repos': [repo.name for repo in DBSession.query(Repo)]}
30
31 def collection_post(self):
32 name = extract_json_data(self.request).get('name')
33 if not name:
34 self.request.errors.add('body', 'name', 'name is missing')
35 return
36 try:
37 DBSession.query(Repo).filter(Repo.name == name).one()
38 except NoResultFound:
39 pass
40 else:
41 self.request.errors.add('body', 'name', 'name already exists')
42 return
43 owner_name = extract_json_data(self.request).get('owner')
44 if not owner_name:
45 self.request.errors.add('body', 'owner', 'owner_name is missing')
46 return
47 try:
48 owner = DBSession.query(User).filter(User.name == owner_name).one()
49 except NoResultFound:
50 self.request.errors.add('body', 'owner', 'owner does not exist')
51 return
52 repo = Repo(name=name, owner=owner, turnip_id=str(uuid.uuid4()))
53 DBSession.add(repo)
54 create_resp = requests.post(
55 self.request.registry.settings['turnip.endpoint'] + 'create',
56 data={'path': repo.turnip_id})
57 if create_resp.status_code != 200:
58 raise Exception("turnip failed to create the repository")
59 return HTTPCreated(
60 self.request.route_url('repoapi', name=repo.name))
61
62 def get(self):
63 name = self.request.matchdict['name']
64 try:
65 DBSession.query(Repo).filter(Repo.name == name).one()
66 except NoResultFound:
67 return HTTPNotFound('Repository not found')
68 return {'name': self.request.matchdict['name']}
69
70
71@resource(collection_path='/users', path='/users/{name}')
72class UserAPI(object):
73
74 def __init__(self, request):
75 self.request = request
76
77 def collection_get(self):
78 return {'users': [user.name for user in DBSession.query(User)]}
79
80 def collection_post(self):
81 name = extract_json_data(self.request).get('name')
82 if not name:
83 self.request.errors.add('body', 'name', 'name is missing')
84 return
85 password = extract_json_data(self.request).get('password')
86 if not name:
87 self.request.errors.add('body', 'password', 'password is missing')
88 return
89 try:
90 DBSession.query(User).filter(User.name == name).one()
91 except NoResultFound:
92 pass
93 else:
94 self.request.errors.add('body', 'name', 'name already exists')
95 return
96 user = User(name=name, password=password)
97 DBSession.add(user)
98 return HTTPCreated(
99 self.request.route_url('userapi', name=user.name))
100
101 def get(self):
102 name = self.request.matchdict['name']
103 try:
104 DBSession.query(User).filter(User.name == name).one()
105 except NoResultFound:
106 return HTTPNotFound('User not found')
107 return {'name': self.request.matchdict['name']}
108
109
110@xmlrpc_method(endpoint='githosting')
111def translatePath(request, path, permission, authenticated_uid,
112 can_authenticate):
113 try:
114 repo = DBSession.query(Repo).filter(
115 Repo.name == path.lstrip('/')).one()
116 except NoResultFound:
117 raise xmlrpclib.Fault(1, "Repo does not exist")
118
119 writable = authenticated_uid == repo.owner_id
120
121 if permission != b'read' and not writable:
122 if not can_authenticate or authenticated_uid is not None:
123 raise xmlrpclib.Fault(2, "Repo is read-only")
124 else:
125 raise xmlrpclib.Fault(3, "Authorisation required")
126
127 return {'path': repo.turnip_id, 'writable': writable}
128
129
130@xmlrpc_method(endpoint='githosting')
131def authenticateWithPassword(request, username, password):
132 try:
133 user = DBSession.query(User).filter(User.name == username).one()
134 except NoResultFound:
135 raise xmlrpclib.Fault(3, "Invalid username or password")
136 if password != user.password:
137 raise xmlrpclib.Fault(3, "Invalid username or password")
138 return {'user': username, 'uid': user.id}

Subscribers

People subscribed via source and target branches

to all changes: