Basic SVN management web UI works.
[pillar-svnman.git] / svnman / routes.py
1 import functools
2 import logging
3
4 from flask import Blueprint, render_template, jsonify, request
5 import werkzeug.exceptions as wz_exceptions
6
7 from pillar.api.utils.authorization import require_login
8 from pillar.auth import current_user
9 from pillar.web.projects.routes import project_view
10 from pillar.web.utils import attach_project_pictures
11 from pillar.web.system_util import pillar_api
12 import pillarsdk
13
14 from svnman import current_svnman
15
16 blueprint = Blueprint('svnman', __name__)
17 log = logging.getLogger(__name__)
18
19
20 def require_project_put(projections: dict = None):
21     """Endpoint decorator, translates project_url into an actual project and checks PUT access."""
22
23     if callable(projections):
24         raise TypeError('Use with @require_project_put() <-- note the parentheses')
25
26     def decorator(wrapped):
27         @functools.wraps(wrapped)
28         @project_view()
29         def wrapper(project: pillarsdk.Project, *args, **kwargs):
30             if 'PUT' not in project.allowed_methods:
31                 log.warning('User %s has no PUT access to project %s (id=%s) but wants to '
32                             'manage a Subversion repository; denying access to %s',
33                             current_user.user_id, project.url, project['_id'], request.url)
34                 raise wz_exceptions.Forbidden()
35
36             return wrapped(project, *args, **kwargs)
37
38         return wrapper
39
40     return decorator
41
42
43 def wrap_svnman_exceptions(wrapped):
44     @functools.wraps(wrapped)
45     def decorator(*args, **kwargs):
46         from . import exceptions
47
48         try:
49             return wrapped(*args, **kwargs)
50         except (OSError, IOError):
51             log.exception('%s(%s, %s): unable to reach SVNman API', wrapped, args, kwargs)
52             resp = jsonify(_message='unable to reach SVNman API server')
53             resp.status_code = 500
54             return resp
55         except exceptions.RemoteError as ex:
56             log.error('%s(%s, %s): API sent us an error: %s', ex, wrapped, args, kwargs)
57             resp = jsonify(_message=str(ex))
58             resp.status_code = 500
59             return resp
60
61     return decorator
62
63
64 @blueprint.route('/')
65 def index():
66     api = pillar_api()
67
68     # FIXME Sybren: add permission check.
69     # TODO: add projections.
70     projects = current_svnman.svnman_projects()
71
72     for project in projects['_items']:
73         attach_project_pictures(project, api)
74
75     return render_template('svnman/index.html',
76                            projects=projects)
77
78
79 def project_settings(project: pillarsdk.Project, **template_args: dict):
80     """Renders the project settings page for Subversion projects."""
81
82     # Based on the project state, we can render a different template.
83     if not current_svnman.is_svnman_project(project):
84         return render_template('svnman/project_settings/offer_create_repo.html',
85                                project=project, **template_args)
86
87     return render_template('svnman/project_settings/settings.html',
88                            project=project,
89                            **template_args)
90
91
92 def error_service_not_available():
93     if request.is_xhr:
94         resp = jsonify({'_message': 'Subversion service not available to your account'})
95         resp.status_code = 403
96         return resp
97
98     return render_template('svnman/errors/service_not_available.html')
99
100
101 @blueprint.route('/<project_url>/create-repo', methods=['POST'])
102 @require_login(require_cap='svn-use', error_view=error_service_not_available)
103 @require_project_put()
104 @wrap_svnman_exceptions
105 def create_repo(project: pillarsdk.Project):
106     log.info('going to create repository for project url=%r on behalf of user %s (%s)',
107              project.url, current_user.user_id, current_user.email)
108
109     current_svnman.create_repo(project, f'{current_user.full_name} <{current_user.email}>')
110     return '', 204
111
112
113 @blueprint.route('/<project_url>/delete-repo/<repo_id>', methods=['POST'])
114 @require_login(require_cap='svn-use', error_view=error_service_not_available)
115 @require_project_put()
116 @wrap_svnman_exceptions
117 def delete_repo(project: pillarsdk.Project, repo_id: str):
118     log.info('going to delete repository %s for project url=%r on behalf of user %s (%s)',
119              repo_id, project.url, current_user.user_id, current_user.email)
120
121     current_svnman.delete_repo(project, repo_id)
122     return '', 204
123
124
125 @blueprint.route('/<project_url>/grant-access/<repo_id>', methods=['POST'])
126 @require_login(require_cap='svn-use', error_view=error_service_not_available)
127 @require_project_put()
128 @wrap_svnman_exceptions
129 def grant_access(project: pillarsdk.Project, repo_id: str):
130     user_id = request.form['user_id']
131     password = request.form.get('password', '')
132
133     log.info('going to grant access to user %s on repository %s for project url=%r '
134              'on behalf of user %s (%s)',
135              user_id, repo_id, project.url, current_user.user_id, current_user.email)
136
137     current_svnman.modify_access(project, repo_id, grant_user_id=user_id, grant_passwd=password)
138     return '', 204
139
140
141 @blueprint.route('/<project_url>/revoke-access/<repo_id>', methods=['POST'])
142 @require_login(require_cap='svn-use', error_view=error_service_not_available)
143 @require_project_put()
144 @wrap_svnman_exceptions
145 def revoke_access(project: pillarsdk.Project, repo_id: str):
146     user_id = request.form['user_id']
147
148     log.info('going to revoke access from user %s on repository %s for project url=%r '
149              'on behalf of user %s (%s)',
150              user_id, repo_id, project.url, current_user.user_id, current_user.email)
151
152     current_svnman.modify_access(project, repo_id, revoke_user_id=user_id)
153     return '', 204