/
server_info.py
203 lines (165 loc) · 5.85 KB
/
server_info.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
"""Server information and capability registration for the API."""
import logging
from copy import deepcopy
from django.conf import settings
from djblets.siteconfig.models import SiteConfiguration
from reviewboard import get_version_string, get_package_version, is_release
from reviewboard.admin.server import get_server_url
from reviewboard.diffviewer.features import dvcs_feature
logger = logging.getLogger(__name__)
_registered_capabilities = {}
_capabilities_defaults = {
'authentication': {
# Whether to allow clients to authenticate to Review Board
# via a web browser.
'client_web_login': True,
},
'diffs': {
'base_commit_ids': True,
'moved_files': True,
'validation': {
'base_commit_ids': True,
}
},
'extra_data': {
'json_patching': True,
},
'review_requests': {
'commit_ids': True,
'trivial_publish': True,
},
'scmtools': {
'git': {
'empty_files': True,
'symlinks': True,
},
'mercurial': {
'empty_files': True,
},
'perforce': {
'moved_files': True,
'empty_files': True,
},
'svn': {
'empty_files': True,
},
},
'text': {
'markdown': True,
'per_field_text_types': True,
'can_include_raw_values': True,
},
}
_feature_gated_capabilities = {
'review_requests': {
'supports_history': dvcs_feature,
},
}
def get_server_info(request=None):
"""Return server information for use in the API.
This is used for the root resource and for the deprecated server
info resource.
Args:
request (django.http.HttpRequest, optional):
The HTTP request from the client.
Returns:
dict:
A dictionary of information about the server and its capabilities.
"""
return {
'product': {
'name': 'Review Board',
'version': get_version_string(),
'package_version': get_package_version(),
'is_release': is_release(),
},
'site': {
'url': get_server_url(request=request),
'administrators': [
{
'name': name,
'email': email,
}
for name, email in settings.ADMINS
],
'time_zone': settings.TIME_ZONE,
},
'capabilities': get_capabilities(request=request),
}
def get_capabilities(request=None):
"""Return the capabilities made available in the API.
Args:
request (django.http.HttpRequest, optional):
The http request from the client.
Returns:
dict:
The dictionary of capabilities.
"""
capabilities = deepcopy(_capabilities_defaults)
capabilities.update(_registered_capabilities)
for category, cap, enabled in get_feature_gated_capabilities(request):
capabilities.setdefault(category, {})[cap] = enabled
siteconfig = SiteConfiguration.objects.get_current()
capabilities['authentication']['client_web_login'] = \
siteconfig.get('client_web_login', False)
return capabilities
def get_feature_gated_capabilities(request=None):
"""Return the capabilities gated behind enabled features.
Args:
request (django.http.HttpRequest, optional):
The HTTP request from the client.
Yields:
tuple:
A 3-tuple of the following:
* The category of the capability (:py:class:`unicode`).
* The capability name (:py:class:`unicode`).
* Whether or not the capability is enabled (:py:class:`bool`).
"""
for category, caps in _feature_gated_capabilities.items():
for cap, required_feature in caps.items():
if required_feature.is_enabled(request=request):
yield category, cap, True
def register_webapi_capabilities(capabilities_id, caps):
"""Register a set of web API capabilities.
These capabilities will appear in the dictionary of available
capabilities with the ID as their key.
A capabilities_id attribute passed in, and can only be registered once.
A KeyError will be thrown if attempting to register a second time.
Args:
capabilities_id (unicode):
A unique ID representing this collection of capabilities.
This can only be used once until unregistered.
caps (dict):
The dictionary of capabilities to register. Each key msut
be a string, and each value should be a boolean or a
dictionary of string keys to booleans.
Raises:
KeyError:
The capabilities ID has already been used.
"""
if not capabilities_id:
raise ValueError('The capabilities_id attribute must not be None')
if capabilities_id in _registered_capabilities:
raise KeyError('"%s" is already a registered set of capabilities'
% capabilities_id)
if capabilities_id in _capabilities_defaults:
raise KeyError('"%s" is reserved for the default set of capabilities'
% capabilities_id)
_registered_capabilities[capabilities_id] = caps
def unregister_webapi_capabilities(capabilities_id):
"""Unregister a previously registered set of web API capabilities.
Args:
capabilities_id (unicode):
The unique ID representing a registered collection of capabilities.
Raises:
KeyError:
A set of capabilities matching the ID were not found.
"""
try:
del _registered_capabilities[capabilities_id]
except KeyError:
logger.error('Failed to unregister unknown web API capabilities '
'"%s".',
capabilities_id)
raise KeyError('"%s" is not a registered web API capabilities set'
% capabilities_id)