Merge lp:~pfalcon/linaro-license-protection/crowd-auth into lp:~linaro-automation/linaro-license-protection/trunk
- crowd-auth
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~pfalcon/linaro-license-protection/crowd-auth |
Merge into: | lp:~linaro-automation/linaro-license-protection/trunk |
Diff against target: |
653 lines (+209/-131) 13 files modified
README (+23/-11) license_protected_downloads/buildinfo.py (+10/-6) license_protected_downloads/group_auth_crowd.py (+52/-0) license_protected_downloads/group_auth_openid.py (+19/-32) license_protected_downloads/tests/__init__.py (+2/-0) license_protected_downloads/tests/test_buildinfo.py (+12/-3) license_protected_downloads/tests/test_openid_auth.py (+11/-20) license_protected_downloads/tests/test_splicebuildinfos.py (+3/-3) license_protected_downloads/tests/test_views.py (+18/-0) license_protected_downloads/tests/testserver_root/build-info/subdir2/BUILD-INFO.txt (+7/-0) license_protected_downloads/tests/testserver_root/build-info/write-test/BUILD-INFO.txt (+0/-42) license_protected_downloads/views.py (+43/-14) settings.py (+9/-0) |
To merge this branch: | bzr merge lp:~pfalcon/linaro-license-protection/crowd-auth |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Georgy Redkozubov | Approve | ||
Milo Casagrande (community) | Approve | ||
Review via email: mp+166047@code.launchpad.net |
This proposal has been superseded by a proposal from 2013-06-03.
Commit message
Description of the change
These are changes to switch l-l-p to Crowd API for group access authorization. Expectably, I didn't tear off old "OpenID teams" code, but rather went for adding new Crowd authz code, and allow app to be configured for which of them to use, allowing seamless transition and reverting if needed.
Well, to support switching to another authz method at all, and such "pluggable" architecture in particular, it of course first took some refactoring on existing codebase.
Actually, I submit this MRs with just refactoring existing setup, which doesn't change current authz method. That's to get early comments, while I'm working on Crowd authz "plugin" specifically.
Suggested review mode is to look at the commit individually.
Georgy Redkozubov (gesha) wrote : | # |
Looks good.
+1 to Milo's comment on tests, afaics, you've missed a ',' in comparison string on line 202. Should be
202 + "You need to be the member of one of the linaro, batman, catwoman "
Otherwise approved.
Paul Sokolovsky (pfalcon) wrote : | # |
> Not something I'm really against, but I would be more interested in testing only
the status code I get, not the kind of "string" I will receive (to me that looks
already more than an unit test). I would test something like that:
Well, if looking in more detail, what I did is factored out that string construction out of OpenIdAuth class. The was also test_openid_auth which exactly did that string test. Consequently, I moved that string test where its new routine belongs. I didn't add anything, I don't feel comfortable removing anything either - my idea was to preserve original semantics and tests which cover it.
> I personally try to avoid concatenating strings in that way.
Again, routine wasn't written by me ;-)
Paul Sokolovsky (pfalcon) wrote : | # |
> afaics, you've missed a ',' in comparison string on line
Ok, let me fire up bzr blame. Well, Englishman can't be wrong with English usage - it was done by James ;-).
And of course, I'm in principle all for fixing side issues, but: 1) They may need more consideration (in this case, both test "simplification" and grammar), and 2) They should be done separately, to not skew topic branch context.
Re: 2), if we used git, I could just apply quick fix to main branch, and rebase mine, but with bzr, it just better wait in queue (to not introduce twisted merges).
- 195. By Paul Sokolovsky
-
Implement dynamic loading of settings.
GROUP_AUTH_ MODULE properly. __import__() has peculiar behavior, so importlib.
import_ module( ) instead.
Caveat: Python 2.7 only. - 196. By Paul Sokolovsky
-
Add "auth-groups" to list of known fields.
Mark "openid-
launchpad- teams" as deprecated. - 197. By Paul Sokolovsky
-
Simplify logic in BuildInfo.get().
- 198. By Paul Sokolovsky
-
Consistently handle "openid-
launchpad- teams" -> "auth-groups" field rename. Map old field name to new during parsing. Consistently use "auth-groups"
everywhere in the code. - 199. By Paul Sokolovsky
-
Add parsing test for new "auth-groups" field.
- 200. By Paul Sokolovsky
-
Update README (also includes BUILD-INFO.txt spec) for latest changes.
In particular, format version is bumped to 0.5 (we should have 1.0 soon).
- 201. By Paul Sokolovsky
-
Rename openid_auth => group_auth_openid, for all auth mods to sort together.
- 202. By Paul Sokolovsky
-
Log auth atempt against Launchpad teams.
For helping with group migration.
- 203. By Paul Sokolovsky
-
PEP8 fixes.
- 204. By Paul Sokolovsky
-
Remove file which is regenerated from scratch every time.
- 205. By Paul Sokolovsky
-
Add Crowd API group auth plugin.
- 206. By Paul Sokolovsky
-
Typos/grammar.
- 207. By Paul Sokolovsky
-
Improve docstring for _set().
- 208. By Paul Sokolovsky
-
We use Auth-Groups directive in this file, so it's format 0.5 .
- 209. By Paul Sokolovsky
-
Applied isdisjoint() "optimization", with credit to Milo.
- 210. By Paul Sokolovsky
-
write_from_array(): Write 0.5 format files.
- 211. By Paul Sokolovsky
-
Handle errors during group auth properly.
- 212. By Paul Sokolovsky
-
First step toward normal logging setup.
- 213. By Paul Sokolovsky
-
Fix exception logging.
- 214. By Paul Sokolovsky
-
More test fixes for 0.5 format version.
- 215. By Paul Sokolovsky
-
Pyflakes fixes.
Unmerged revisions
Preview Diff
1 | === modified file 'README' | |||
2 | --- README 2013-02-15 09:45:08 +0000 | |||
3 | +++ README 2013-06-03 11:47:27 +0000 | |||
4 | @@ -16,7 +16,9 @@ | |||
5 | 16 | * makes use of the regular directory structure on disk | 16 | * makes use of the regular directory structure on disk |
6 | 17 | * click through licensing with each file potentially having a different | 17 | * click through licensing with each file potentially having a different |
7 | 18 | license/theme | 18 | license/theme |
9 | 19 | * openid restrictions (by Launchpad teams) | 19 | * group-based access authorization restrictions (using group information |
10 | 20 | from external services, currently OpenID with team extensions is | ||
11 | 21 | (as used by Launchpad.net) and Atlassian Crowd are supported). | ||
12 | 20 | * post-processing of all uploads (a script that manages uploads) | 22 | * post-processing of all uploads (a script that manages uploads) |
13 | 21 | * per-IP pass-through (for automatic services like a test framework) | 23 | * per-IP pass-through (for automatic services like a test framework) |
14 | 22 | 24 | ||
15 | @@ -46,12 +48,12 @@ | |||
16 | 46 | syntax). | 48 | syntax). |
17 | 47 | 49 | ||
18 | 48 | BUILD-INFO.txt file will allow one to specify the access restrictions | 50 | BUILD-INFO.txt file will allow one to specify the access restrictions |
20 | 49 | (such as click-through licensing and OpenID-restrictions) and influence | 51 | (such as click-through licensing and required groups) and influence |
21 | 50 | the display (such as license-theme) of a particular build. | 52 | the display (such as license-theme) of a particular build. |
22 | 51 | 53 | ||
26 | 52 | WARNING: if you want a build type to be protected by OpenID, you need to | 54 | WARNING: if you want a build to be protected by OpenID-based groups, you |
27 | 53 | ensure that the appropriate team is added to the list of django groups | 55 | need to ensure that the appropriate team is added to the list of django |
28 | 54 | in the database. Only django admins can do that. | 56 | groups in the database. Only django admins can do that. |
29 | 55 | 57 | ||
30 | 56 | Next step is to ensure that | 58 | Next step is to ensure that |
31 | 57 | 59 | ||
32 | @@ -108,15 +110,15 @@ | |||
33 | 108 | a placeholder and will be ignored. To be added later by build services? | 110 | a placeholder and will be ignored. To be added later by build services? |
34 | 109 | * License-Type: (required) | 111 | * License-Type: (required) |
35 | 110 | open - Open builds. No license page is displayed. | 112 | open - Open builds. No license page is displayed. |
38 | 111 | protected - EULA protected builds. If 'OpenID-Launchpad-Teams' is defined | 113 | protected - EULA protected builds. If 'Auth-Groups' is defined |
39 | 112 | then OpenID protection is used, otherwise simple Accept/Decline license | 114 | then group authorization is used, otherwise simple Accept/Decline license |
40 | 113 | page is displayed before accessing protected files. | 115 | page is displayed before accessing protected files. |
41 | 114 | * Theme: (required only if License-Type is 'protected') | 116 | * Theme: (required only if License-Type is 'protected') |
42 | 115 | Acceptable values are: stericsson, samsung. | 117 | Acceptable values are: stericsson, samsung. |
43 | 116 | Theme name for selecting proper theming on download and license pages. | 118 | Theme name for selecting proper theming on download and license pages. |
47 | 117 | * OpenID-Launchpad-Teams: (optional) | 119 | * Auth-Groups: (optional) |
48 | 118 | LP team names, members of which are allowed to access protected files. No | 120 | Names of groups, members of which are allowed to access protected files. |
49 | 119 | OpenID protection if absent. | 121 | No group-based protection if absent. |
50 | 120 | * Collect-User-Data: (optional) | 122 | * Collect-User-Data: (optional) |
51 | 121 | Acceptable values are: yes, no. | 123 | Acceptable values are: yes, no. |
52 | 122 | Defaults to 'no' if not present. If the field is set to 'yes' then | 124 | Defaults to 'no' if not present. If the field is set to 'yes' then |
53 | @@ -131,10 +133,20 @@ | |||
54 | 131 | - Field names are case insensitive. | 133 | - Field names are case insensitive. |
55 | 132 | - Fields order doesn't matter, except 'Files-Pattern' | 134 | - Fields order doesn't matter, except 'Files-Pattern' |
56 | 133 | 135 | ||
57 | 136 | History of BUILD-INFO.txt format changes | ||
58 | 137 | ........................................ | ||
59 | 138 | |||
60 | 139 | Changes from format version 0.1 to 0.5: | ||
61 | 140 | |||
62 | 141 | 1. Field "OpenID-Launchpad-Teams" is deprecated and renamed to | ||
63 | 142 | "Auth-Groups". Old name is supported for archived builds, however | ||
64 | 143 | it may lead to warnings, and at later time to errors, during | ||
65 | 144 | publishing new builds, so client usage should be upgraded. | ||
66 | 145 | |||
67 | 134 | BUILD-INFO.txt example: | 146 | BUILD-INFO.txt example: |
68 | 135 | ....................... | 147 | ....................... |
69 | 136 | 148 | ||
71 | 137 | Format-Version: 0.1 | 149 | Format-Version: 0.5 |
72 | 138 | 150 | ||
73 | 139 | Files-Pattern: *.img, *.tar.bz2 | 151 | Files-Pattern: *.img, *.tar.bz2 |
74 | 140 | Build-Name: landing-snowball | 152 | Build-Name: landing-snowball |
75 | 141 | 153 | ||
76 | === modified file 'license_protected_downloads/buildinfo.py' | |||
77 | --- license_protected_downloads/buildinfo.py 2013-05-29 13:41:12 +0000 | |||
78 | +++ license_protected_downloads/buildinfo.py 2013-06-03 11:47:27 +0000 | |||
79 | @@ -14,8 +14,11 @@ | |||
80 | 14 | self.file_info_array = [{}] | 14 | self.file_info_array = [{}] |
81 | 15 | self.fields_defined = [ | 15 | self.fields_defined = [ |
82 | 16 | "format-version", "files-pattern", "build-name", "theme", | 16 | "format-version", "files-pattern", "build-name", "theme", |
85 | 17 | "license-type", "openid-launchpad-teams", "collect-user-data", | 17 | "license-type", "auth-groups", "collect-user-data", |
86 | 18 | "license-text"] | 18 | "license-text", |
87 | 19 | # Deprecated | ||
88 | 20 | "openid-launchpad-teams", | ||
89 | 21 | ] | ||
90 | 19 | self.full_file_name = fn | 22 | self.full_file_name = fn |
91 | 20 | self.search_path = self.get_search_path(fn) | 23 | self.search_path = self.get_search_path(fn) |
92 | 21 | self.fname = os.path.basename(fn) | 24 | self.fname = os.path.basename(fn) |
93 | @@ -40,6 +43,7 @@ | |||
94 | 40 | os.path.join(cls.get_search_path(path), "BUILD-INFO.txt")) | 43 | os.path.join(cls.get_search_path(path), "BUILD-INFO.txt")) |
95 | 41 | 44 | ||
96 | 42 | def _set(self, key, value): | 45 | def _set(self, key, value): |
97 | 46 | "key: file pattern, value: list of dicts of field/val pairs" | ||
98 | 43 | if key in self.build_info_array[self.index]: | 47 | if key in self.build_info_array[self.index]: |
99 | 44 | # A repeated key indicates we have found another chunk of | 48 | # A repeated key indicates we have found another chunk of |
100 | 45 | # build-info | 49 | # build-info |
101 | @@ -91,10 +95,7 @@ | |||
102 | 91 | if index > self.max_index: | 95 | if index > self.max_index: |
103 | 92 | return False | 96 | return False |
104 | 93 | block = self.file_info_array[index] | 97 | block = self.file_info_array[index] |
109 | 94 | for key in block: | 98 | return block.get(field, False) |
106 | 95 | if field == key: | ||
107 | 96 | return block[field] | ||
108 | 97 | return False | ||
110 | 98 | 99 | ||
111 | 99 | def parseLine(self, line): | 100 | def parseLine(self, line): |
112 | 100 | values = line.split(":", 1) | 101 | values = line.split(":", 1) |
113 | @@ -108,6 +109,9 @@ | |||
114 | 108 | raise IncorrectDataFormatException( | 109 | raise IncorrectDataFormatException( |
115 | 109 | "Field '%s' not allowed." % field) | 110 | "Field '%s' not allowed." % field) |
116 | 110 | else: | 111 | else: |
117 | 112 | # Rename any deprecated field names to new names | ||
118 | 113 | field_renames = {"openid-launchpad-teams": "auth-groups"} | ||
119 | 114 | field = field_renames.get(field, field) | ||
120 | 111 | return {field: value} | 115 | return {field: value} |
121 | 112 | 116 | ||
122 | 113 | def isValidField(self, field_name): | 117 | def isValidField(self, field_name): |
123 | 114 | 118 | ||
124 | === added file 'license_protected_downloads/group_auth_crowd.py' | |||
125 | --- license_protected_downloads/group_auth_crowd.py 1970-01-01 00:00:00 +0000 | |||
126 | +++ license_protected_downloads/group_auth_crowd.py 2013-06-03 11:47:27 +0000 | |||
127 | @@ -0,0 +1,52 @@ | |||
128 | 1 | import logging | ||
129 | 2 | |||
130 | 3 | from django.conf import settings | ||
131 | 4 | from django.shortcuts import redirect | ||
132 | 5 | |||
133 | 6 | import requests | ||
134 | 7 | |||
135 | 8 | |||
136 | 9 | log = logging.getLogger(__file__) | ||
137 | 10 | |||
138 | 11 | |||
139 | 12 | def upgrade_requests(): | ||
140 | 13 | """Ubuntu 12.04 comes with pretty old requests version. Add convenience | ||
141 | 14 | methods of newer versions straight to it, to avoid client-side | ||
142 | 15 | workarounds.""" | ||
143 | 16 | if "json" not in dir(requests.models.Response): | ||
144 | 17 | def patchy_json(self): | ||
145 | 18 | import json | ||
146 | 19 | return json.loads(self.content) | ||
147 | 20 | requests.models.Response.json = patchy_json | ||
148 | 21 | |||
149 | 22 | # We monkey-patch requests module on first load | ||
150 | 23 | upgrade_requests() | ||
151 | 24 | |||
152 | 25 | |||
153 | 26 | class GroupAuthError(Exception): | ||
154 | 27 | pass | ||
155 | 28 | |||
156 | 29 | |||
157 | 30 | def process_group_auth(request, required_groups): | ||
158 | 31 | if not required_groups: | ||
159 | 32 | return True | ||
160 | 33 | if not request.user.is_authenticated(): | ||
161 | 34 | # Force OpenID login | ||
162 | 35 | return redirect(settings.LOGIN_URL + "?next=" + request.path) | ||
163 | 36 | |||
164 | 37 | log.warn("Authenticating using Crowd API: %s", | ||
165 | 38 | request.user.username) | ||
166 | 39 | |||
167 | 40 | auth = (settings.ATLASSIAN_CROWD_API_USERNAME, | ||
168 | 41 | settings.ATLASSIAN_CROWD_API_PASSWORD) | ||
169 | 42 | params = {"username": request.user.username} | ||
170 | 43 | r = requests.get(settings.ATLASSIAN_CROWD_API_URL | ||
171 | 44 | + "/user/group/nested.json", params=params, auth=auth) | ||
172 | 45 | if r.status_code != 200: | ||
173 | 46 | raise GroupAuthError(r.status_code) | ||
174 | 47 | data = r.json() | ||
175 | 48 | user_groups = set([x["name"] for x in data["groups"]]) | ||
176 | 49 | for g in required_groups: | ||
177 | 50 | if g in user_groups: | ||
178 | 51 | return True | ||
179 | 52 | return False | ||
180 | 0 | 53 | ||
181 | === renamed file 'license_protected_downloads/openid_auth.py' => 'license_protected_downloads/group_auth_openid.py' | |||
182 | --- license_protected_downloads/openid_auth.py 2012-08-23 13:37:29 +0000 | |||
183 | +++ license_protected_downloads/group_auth_openid.py 2013-06-03 11:47:27 +0000 | |||
184 | @@ -1,16 +1,21 @@ | |||
185 | 1 | import logging | ||
186 | 2 | |||
187 | 1 | from django.conf import settings | 3 | from django.conf import settings |
189 | 2 | from django.shortcuts import redirect, render_to_response | 4 | from django.shortcuts import redirect |
190 | 3 | from django.contrib.auth.models import Group | 5 | from django.contrib.auth.models import Group |
198 | 4 | import bzr_version | 6 | |
199 | 5 | 7 | ||
200 | 6 | 8 | log = logging.getLogger(__file__) | |
201 | 7 | class OpenIDAuth: | 9 | |
202 | 8 | 10 | ||
203 | 9 | @classmethod | 11 | def process_group_auth(request, openid_teams): |
204 | 10 | def process_openid_auth(cls, request, openid_teams): | 12 | """Returns True if access granted, False if denied and Response |
205 | 13 | object if not enough authentication information available and | ||
206 | 14 | user should authenticate first (by following that Response). | ||
207 | 15 | """ | ||
208 | 11 | 16 | ||
209 | 12 | if not openid_teams: | 17 | if not openid_teams: |
211 | 13 | return None | 18 | return True |
212 | 14 | 19 | ||
213 | 15 | for openid_team in openid_teams: | 20 | for openid_team in openid_teams: |
214 | 16 | Group.objects.get_or_create(name=openid_team) | 21 | Group.objects.get_or_create(name=openid_team) |
215 | @@ -19,28 +24,10 @@ | |||
216 | 19 | # Force OpenID login | 24 | # Force OpenID login |
217 | 20 | return redirect(settings.LOGIN_URL + "?next=" + request.path) | 25 | return redirect(settings.LOGIN_URL + "?next=" + request.path) |
218 | 21 | 26 | ||
219 | 27 | log.warn("Authenticating using Launchpad OpenID Teams: %s", | ||
220 | 28 | request.user.username) | ||
221 | 22 | for group in request.user.groups.all(): | 29 | for group in request.user.groups.all(): |
222 | 23 | if group.name in openid_teams: | 30 | if group.name in openid_teams: |
246 | 24 | return None | 31 | return True |
247 | 25 | 32 | ||
248 | 26 | # Construct a nice string of openid teams that will allow access to | 33 | return False |
226 | 27 | # the requested file | ||
227 | 28 | if len(openid_teams) > 1: | ||
228 | 29 | teams_string = "one of the " + openid_teams.pop(0) + " " | ||
229 | 30 | if len(openid_teams) > 1: | ||
230 | 31 | teams_string += ", ".join(openid_teams[0:-1]) | ||
231 | 32 | |||
232 | 33 | teams_string += " or " + openid_teams[-1] + " teams" | ||
233 | 34 | else: | ||
234 | 35 | teams_string = "the " + openid_teams[0] + " team" | ||
235 | 36 | |||
236 | 37 | response = render_to_response( | ||
237 | 38 | 'openid_forbidden_template.html', | ||
238 | 39 | {'login': settings.LOGIN_URL + "?next=" + request.path, | ||
239 | 40 | 'authenticated': request.user.is_authenticated(), | ||
240 | 41 | 'openid_teams': teams_string, | ||
241 | 42 | 'revno': bzr_version.get_my_bzr_revno(), | ||
242 | 43 | }) | ||
243 | 44 | |||
244 | 45 | response.status_code = 403 | ||
245 | 46 | return response | ||
249 | 47 | 34 | ||
250 | === modified file 'license_protected_downloads/tests/__init__.py' | |||
251 | --- license_protected_downloads/tests/__init__.py 2013-04-19 12:13:03 +0000 | |||
252 | +++ license_protected_downloads/tests/__init__.py 2013-06-03 11:47:27 +0000 | |||
253 | @@ -11,6 +11,7 @@ | |||
254 | 11 | FileViewTests, | 11 | FileViewTests, |
255 | 12 | HowtoViewTests, | 12 | HowtoViewTests, |
256 | 13 | ViewTests, | 13 | ViewTests, |
257 | 14 | ViewHelpersTests, | ||
258 | 14 | ) | 15 | ) |
259 | 15 | from license_protected_downloads.tests.test_openid_auth import TestOpenIDAuth | 16 | from license_protected_downloads.tests.test_openid_auth import TestOpenIDAuth |
260 | 16 | from license_protected_downloads.tests.test_custom_commands \ | 17 | from license_protected_downloads.tests.test_custom_commands \ |
261 | @@ -32,4 +33,5 @@ | |||
262 | 32 | 'TestPep8': TestPep8, | 33 | 'TestPep8': TestPep8, |
263 | 33 | 'TestPyflakes': TestPyflakes, | 34 | 'TestPyflakes': TestPyflakes, |
264 | 34 | 'ViewTests': ViewTests, | 35 | 'ViewTests': ViewTests, |
265 | 36 | 'ViewHelpersTests': ViewHelpersTests, | ||
266 | 35 | } | 37 | } |
267 | 36 | 38 | ||
268 | === modified file 'license_protected_downloads/tests/test_buildinfo.py' | |||
269 | --- license_protected_downloads/tests/test_buildinfo.py 2013-04-19 12:13:03 +0000 | |||
270 | +++ license_protected_downloads/tests/test_buildinfo.py 2013-06-03 11:47:27 +0000 | |||
271 | @@ -27,7 +27,16 @@ | |||
272 | 27 | self.assertEquals(build_info.getInfoForFile(), | 27 | self.assertEquals(build_info.getInfoForFile(), |
273 | 28 | [{'build-name': 'landing-protected', | 28 | [{'build-name': 'landing-protected', |
274 | 29 | 'license-type': 'protected', | 29 | 'license-type': 'protected', |
276 | 30 | 'openid-launchpad-teams': 'linaro'}]) | 30 | 'auth-groups': 'linaro'}]) |
277 | 31 | |||
278 | 32 | def test_apply_to_dir_auth_groups_field(self): | ||
279 | 33 | dir_path = THIS_DIRECTORY + \ | ||
280 | 34 | '/testserver_root/build-info/subdir2' | ||
281 | 35 | build_info = BuildInfo(dir_path) | ||
282 | 36 | self.assertEquals(build_info.getInfoForFile(), | ||
283 | 37 | [{'build-name': 'landing-protected', | ||
284 | 38 | 'license-type': 'protected', | ||
285 | 39 | 'auth-groups': 'linaro'}]) | ||
286 | 31 | 40 | ||
287 | 32 | def test_apply_to_nonexistent_file(self): | 41 | def test_apply_to_nonexistent_file(self): |
288 | 33 | with self.assertRaises(IOError): | 42 | with self.assertRaises(IOError): |
289 | @@ -47,8 +56,8 @@ | |||
290 | 47 | value = "notempty" | 56 | value = "notempty" |
291 | 48 | build_info = BuildInfo(self.buildinfo_file_path) | 57 | build_info = BuildInfo(self.buildinfo_file_path) |
292 | 49 | for pair in build_info.file_info_array: | 58 | for pair in build_info.file_info_array: |
295 | 50 | if "openid-launchpad-teams" in pair: | 59 | if "auth-groups" in pair: |
296 | 51 | value = pair["openid-launchpad-teams"] | 60 | value = pair["auth-groups"] |
297 | 52 | 61 | ||
298 | 53 | self.assertFalse(value) | 62 | self.assertFalse(value) |
299 | 54 | 63 | ||
300 | 55 | 64 | ||
301 | === modified file 'license_protected_downloads/tests/test_openid_auth.py' | |||
302 | --- license_protected_downloads/tests/test_openid_auth.py 2012-08-23 13:37:29 +0000 | |||
303 | +++ license_protected_downloads/tests/test_openid_auth.py 2013-06-03 11:47:27 +0000 | |||
304 | @@ -2,7 +2,7 @@ | |||
305 | 2 | from django.http import HttpResponse | 2 | from django.http import HttpResponse |
306 | 3 | from mock import Mock, patch | 3 | from mock import Mock, patch |
307 | 4 | 4 | ||
309 | 5 | from license_protected_downloads.openid_auth import OpenIDAuth | 5 | from license_protected_downloads import group_auth_openid as openid_auth |
310 | 6 | 6 | ||
311 | 7 | 7 | ||
312 | 8 | class TestOpenIDAuth(TestCase): | 8 | class TestOpenIDAuth(TestCase): |
313 | @@ -27,14 +27,14 @@ | |||
314 | 27 | def test_check_team_membership_no_teams(self): | 27 | def test_check_team_membership_no_teams(self): |
315 | 28 | mock_request = self.make_mock_request() | 28 | mock_request = self.make_mock_request() |
316 | 29 | openid_teams = [] | 29 | openid_teams = [] |
319 | 30 | self.assertIsNone( | 30 | self.assertTrue( |
320 | 31 | OpenIDAuth.process_openid_auth(mock_request, openid_teams)) | 31 | openid_auth.process_group_auth(mock_request, openid_teams)) |
321 | 32 | 32 | ||
322 | 33 | def test_check_team_membership_no_authentication(self): | 33 | def test_check_team_membership_no_authentication(self): |
323 | 34 | mock_request = self.make_mock_request() | 34 | mock_request = self.make_mock_request() |
324 | 35 | mock_request.user.is_authenticated.return_value = False | 35 | mock_request.user.is_authenticated.return_value = False |
325 | 36 | openid_teams = ["linaro"] | 36 | openid_teams = ["linaro"] |
327 | 37 | response = OpenIDAuth.process_openid_auth(mock_request, openid_teams) | 37 | response = openid_auth.process_group_auth(mock_request, openid_teams) |
328 | 38 | self.assertIsNotNone(response) | 38 | self.assertIsNotNone(response) |
329 | 39 | self.assertTrue(isinstance(response, HttpResponse)) | 39 | self.assertTrue(isinstance(response, HttpResponse)) |
330 | 40 | self.assertEquals(302, response.status_code) | 40 | self.assertEquals(302, response.status_code) |
331 | @@ -45,8 +45,8 @@ | |||
332 | 45 | mock_request.user.groups.all.return_value = [ | 45 | mock_request.user.groups.all.return_value = [ |
333 | 46 | self.make_mock_group("linaro")] | 46 | self.make_mock_group("linaro")] |
334 | 47 | openid_teams = ["linaro"] | 47 | openid_teams = ["linaro"] |
337 | 48 | response = OpenIDAuth.process_openid_auth(mock_request, openid_teams) | 48 | response = openid_auth.process_group_auth(mock_request, openid_teams) |
338 | 49 | self.assertIsNone(response) | 49 | self.assertTrue(response) |
339 | 50 | 50 | ||
340 | 51 | def test_check_no_team_membership_authed(self): | 51 | def test_check_no_team_membership_authed(self): |
341 | 52 | mock_request = self.make_mock_request() | 52 | mock_request = self.make_mock_request() |
342 | @@ -54,12 +54,8 @@ | |||
343 | 54 | mock_request.user.groups.all.return_value = [ | 54 | mock_request.user.groups.all.return_value = [ |
344 | 55 | self.make_mock_group("another-group")] | 55 | self.make_mock_group("another-group")] |
345 | 56 | openid_teams = ["linaro"] | 56 | openid_teams = ["linaro"] |
352 | 57 | response = OpenIDAuth.process_openid_auth(mock_request, openid_teams) | 57 | response = openid_auth.process_group_auth(mock_request, openid_teams) |
353 | 58 | self.assertIsNotNone(response) | 58 | self.assertFalse(response) |
348 | 59 | self.assertTrue(isinstance(response, HttpResponse)) | ||
349 | 60 | self.assertContains(response, | ||
350 | 61 | "You need to be the member of the linaro team in Launchpad.", | ||
351 | 62 | status_code=403) | ||
354 | 63 | 59 | ||
355 | 64 | def test_check_no_team_membership_authed_multi_teams(self): | 60 | def test_check_no_team_membership_authed_multi_teams(self): |
356 | 65 | mock_request = self.make_mock_request() | 61 | mock_request = self.make_mock_request() |
357 | @@ -67,13 +63,8 @@ | |||
358 | 67 | mock_request.user.groups.all.return_value = [ | 63 | mock_request.user.groups.all.return_value = [ |
359 | 68 | self.make_mock_group("another-group")] | 64 | self.make_mock_group("another-group")] |
360 | 69 | openid_teams = ["linaro", "batman", "catwoman", "joker"] | 65 | openid_teams = ["linaro", "batman", "catwoman", "joker"] |
368 | 70 | response = OpenIDAuth.process_openid_auth(mock_request, openid_teams) | 66 | response = openid_auth.process_group_auth(mock_request, openid_teams) |
369 | 71 | self.assertIsNotNone(response) | 67 | self.assertFalse(response) |
363 | 72 | self.assertTrue(isinstance(response, HttpResponse)) | ||
364 | 73 | self.assertContains(response, | ||
365 | 74 | "You need to be the member of one of the linaro batman, catwoman " | ||
366 | 75 | "or joker teams in Launchpad.", | ||
367 | 76 | status_code=403) | ||
370 | 77 | 68 | ||
371 | 78 | @patch("django.contrib.auth.models.Group.objects.get_or_create") | 69 | @patch("django.contrib.auth.models.Group.objects.get_or_create") |
372 | 79 | def test_auto_adding_groups(self, get_or_create_mock): | 70 | def test_auto_adding_groups(self, get_or_create_mock): |
373 | @@ -83,7 +74,7 @@ | |||
374 | 83 | self.make_mock_group("another-group")] | 74 | self.make_mock_group("another-group")] |
375 | 84 | 75 | ||
376 | 85 | openid_teams = ["linaro", "linaro-infrastructure"] | 76 | openid_teams = ["linaro", "linaro-infrastructure"] |
378 | 86 | OpenIDAuth.process_openid_auth(mock_request, openid_teams) | 77 | openid_auth.process_group_auth(mock_request, openid_teams) |
379 | 87 | 78 | ||
380 | 88 | expected = [ | 79 | expected = [ |
381 | 89 | ((), {'name': 'linaro'}), ((), {'name': 'linaro-infrastructure'})] | 80 | ((), {'name': 'linaro'}), ((), {'name': 'linaro-infrastructure'})] |
382 | 90 | 81 | ||
383 | === modified file 'license_protected_downloads/tests/test_splicebuildinfos.py' | |||
384 | --- license_protected_downloads/tests/test_splicebuildinfos.py 2013-04-19 12:13:03 +0000 | |||
385 | +++ license_protected_downloads/tests/test_splicebuildinfos.py 2013-06-03 11:47:27 +0000 | |||
386 | @@ -20,16 +20,16 @@ | |||
387 | 20 | 'test-protected.txt': | 20 | 'test-protected.txt': |
388 | 21 | [{'license-type': 'protected', | 21 | [{'license-type': 'protected', |
389 | 22 | 'build-name': 'landing-protected', | 22 | 'build-name': 'landing-protected', |
391 | 23 | 'openid-launchpad-teams': 'linaro'}], | 23 | 'auth-groups': 'linaro'}], |
392 | 24 | 'test-protected-2.txt': | 24 | 'test-protected-2.txt': |
393 | 25 | [{'license-type': 'protected', | 25 | [{'license-type': 'protected', |
394 | 26 | 'build-name': 'landing-protected', | 26 | 'build-name': 'landing-protected', |
396 | 27 | 'openid-launchpad-teams': 'linaro'}]} | 27 | 'auth-groups': 'linaro'}]} |
397 | 28 | 28 | ||
398 | 29 | result = {'test-protected.txt, test-protected-2.txt': | 29 | result = {'test-protected.txt, test-protected-2.txt': |
399 | 30 | [{'license-type': 'protected', | 30 | [{'license-type': 'protected', |
400 | 31 | 'build-name': 'landing-protected', | 31 | 'build-name': 'landing-protected', |
402 | 32 | 'openid-launchpad-teams': 'linaro'}]} | 32 | 'auth-groups': 'linaro'}]} |
403 | 33 | 33 | ||
404 | 34 | build_info_res = SpliceBuildInfos.merge_duplicates(build_info_dict) | 34 | build_info_res = SpliceBuildInfos.merge_duplicates(build_info_dict) |
405 | 35 | self.assertEquals(build_info_res, result) | 35 | self.assertEquals(build_info_res, result) |
406 | 36 | 36 | ||
407 | === modified file 'license_protected_downloads/tests/test_views.py' | |||
408 | --- license_protected_downloads/tests/test_views.py 2013-04-02 09:58:42 +0000 | |||
409 | +++ license_protected_downloads/tests/test_views.py 2013-06-03 11:47:27 +0000 | |||
410 | @@ -2,6 +2,7 @@ | |||
411 | 2 | 2 | ||
412 | 3 | from django.conf import settings | 3 | from django.conf import settings |
413 | 4 | from django.test import Client, TestCase | 4 | from django.test import Client, TestCase |
414 | 5 | from django.http import HttpResponse | ||
415 | 5 | import hashlib | 6 | import hashlib |
416 | 6 | import os | 7 | import os |
417 | 7 | import tempfile | 8 | import tempfile |
418 | @@ -10,6 +11,8 @@ | |||
419 | 10 | import urlparse | 11 | import urlparse |
420 | 11 | import json | 12 | import json |
421 | 12 | 13 | ||
422 | 14 | from mock import Mock | ||
423 | 15 | |||
424 | 13 | from license_protected_downloads import bzr_version | 16 | from license_protected_downloads import bzr_version |
425 | 14 | from license_protected_downloads.buildinfo import BuildInfo | 17 | from license_protected_downloads.buildinfo import BuildInfo |
426 | 15 | from license_protected_downloads.config import INTERNAL_HOSTS | 18 | from license_protected_downloads.config import INTERNAL_HOSTS |
427 | @@ -19,6 +22,7 @@ | |||
428 | 19 | from license_protected_downloads.views import _process_include_tags | 22 | from license_protected_downloads.views import _process_include_tags |
429 | 20 | from license_protected_downloads.views import _sizeof_fmt | 23 | from license_protected_downloads.views import _sizeof_fmt |
430 | 21 | from license_protected_downloads.views import is_same_parent_dir | 24 | from license_protected_downloads.views import is_same_parent_dir |
431 | 25 | from license_protected_downloads import views | ||
432 | 22 | 26 | ||
433 | 23 | THIS_DIRECTORY = os.path.dirname(os.path.abspath(__file__)) | 27 | THIS_DIRECTORY = os.path.dirname(os.path.abspath(__file__)) |
434 | 24 | TESTSERVER_ROOT = os.path.join(THIS_DIRECTORY, "testserver_root") | 28 | TESTSERVER_ROOT = os.path.join(THIS_DIRECTORY, "testserver_root") |
435 | @@ -903,5 +907,19 @@ | |||
436 | 903 | self.assertEqual(response.status_code, 200) | 907 | self.assertEqual(response.status_code, 200) |
437 | 904 | 908 | ||
438 | 905 | 909 | ||
439 | 910 | class ViewHelpersTests(BaseServeViewTest): | ||
440 | 911 | def test_auth_group_error(self): | ||
441 | 912 | groups = ["linaro", "batman", "catwoman", "joker"] | ||
442 | 913 | request = Mock() | ||
443 | 914 | request.path = "mock_path" | ||
444 | 915 | response = views.group_auth_failed_response(request, groups) | ||
445 | 916 | self.assertIsNotNone(response) | ||
446 | 917 | self.assertTrue(isinstance(response, HttpResponse)) | ||
447 | 918 | self.assertContains(response, | ||
448 | 919 | "You need to be the member of one of the linaro batman, catwoman " | ||
449 | 920 | "or joker teams in Launchpad.", | ||
450 | 921 | status_code=403) | ||
451 | 922 | |||
452 | 923 | |||
453 | 906 | if __name__ == '__main__': | 924 | if __name__ == '__main__': |
454 | 907 | unittest.main() | 925 | unittest.main() |
455 | 908 | 926 | ||
456 | === added directory 'license_protected_downloads/tests/testserver_root/build-info/subdir2' | |||
457 | === added file 'license_protected_downloads/tests/testserver_root/build-info/subdir2/BUILD-INFO.txt' | |||
458 | --- license_protected_downloads/tests/testserver_root/build-info/subdir2/BUILD-INFO.txt 1970-01-01 00:00:00 +0000 | |||
459 | +++ license_protected_downloads/tests/testserver_root/build-info/subdir2/BUILD-INFO.txt 2013-06-03 11:47:27 +0000 | |||
460 | @@ -0,0 +1,7 @@ | |||
461 | 1 | Format-Version: 0.1 | ||
462 | 2 | |||
463 | 3 | |||
464 | 4 | Files-Pattern: * | ||
465 | 5 | Build-Name: landing-protected | ||
466 | 6 | License-Type: protected | ||
467 | 7 | Auth-Groups: linaro | ||
468 | 0 | 8 | ||
469 | === added file 'license_protected_downloads/tests/testserver_root/build-info/subdir2/testfile.txt' | |||
470 | === removed file 'license_protected_downloads/tests/testserver_root/build-info/write-test/BUILD-INFO.txt' | |||
471 | --- license_protected_downloads/tests/testserver_root/build-info/write-test/BUILD-INFO.txt 2013-04-19 12:13:03 +0000 | |||
472 | +++ license_protected_downloads/tests/testserver_root/build-info/write-test/BUILD-INFO.txt 1970-01-01 00:00:00 +0000 | |||
473 | @@ -1,42 +0,0 @@ | |||
474 | 1 | Format-Version: 0.1 | ||
475 | 2 | |||
476 | 3 | Files-Pattern: *openid* | ||
477 | 4 | license-type: protected | ||
478 | 5 | build-name: landing-protected | ||
479 | 6 | openid-launchpad-teams: linaro | ||
480 | 7 | |||
481 | 8 | Files-Pattern: *panda* | ||
482 | 9 | license-type: open | ||
483 | 10 | build-name: landing-panda | ||
484 | 11 | |||
485 | 12 | Files-Pattern: *.txt | ||
486 | 13 | license-type: protected | ||
487 | 14 | build-name: landing-protected | ||
488 | 15 | openid-launchpad-teams: | ||
489 | 16 | theme: stericsson | ||
490 | 17 | collect-user-data: yes | ||
491 | 18 | license-text: <p>IMPORTANT — PLEASE READ THE FOLLOWING AGREEMENT CAREFULLY.</p> | ||
492 | 19 | <p> | ||
493 | 20 | THIS IS A LEGALLY BINDING AGREEMENT | ||
494 | 21 | </p> | ||
495 | 22 | |||
496 | 23 | Files-Pattern: *origen* | ||
497 | 24 | theme: samsung | ||
498 | 25 | license-type: protected | ||
499 | 26 | build-name: landing-origen | ||
500 | 27 | license-text: <p>IMPORTANT — PLEASE READ THE FOLLOWING AGREEMENT CAREFULLY.</p> | ||
501 | 28 | <p> | ||
502 | 29 | THIS IS A LEGALLY BINDING AGREEMENT BETWEEN YOU, an individual or a | ||
503 | 30 | legal entity, (“LICENSEE”) AND HAL 1000. | ||
504 | 31 | </p> | ||
505 | 32 | |||
506 | 33 | Files-Pattern: *snowball* | ||
507 | 34 | theme: stericsson | ||
508 | 35 | license-type: protected | ||
509 | 36 | build-name: landing-snowball | ||
510 | 37 | license-text: <p>IMPORTANT — PLEASE READ THE FOLLOWING AGREEMENT CAREFULLY.</p> | ||
511 | 38 | <p> | ||
512 | 39 | THIS IS A LEGALLY BINDING AGREEMENT BETWEEN YOU, an individual or a | ||
513 | 40 | legal entity, (“LICENSEE”) AND ME, THE TEST SERVER. | ||
514 | 41 | </p> | ||
515 | 42 | |||
516 | 43 | 0 | ||
517 | === modified file 'license_protected_downloads/views.py' | |||
518 | --- license_protected_downloads/views.py 2013-05-09 11:15:33 +0000 | |||
519 | +++ license_protected_downloads/views.py 2013-06-03 11:47:27 +0000 | |||
520 | @@ -23,7 +23,9 @@ | |||
521 | 23 | from buildinfo import BuildInfo, IncorrectDataFormatException | 23 | from buildinfo import BuildInfo, IncorrectDataFormatException |
522 | 24 | from render_text_files import RenderTextFiles | 24 | from render_text_files import RenderTextFiles |
523 | 25 | from models import License | 25 | from models import License |
525 | 26 | from openid_auth import OpenIDAuth | 26 | # Load group auth "plugin" dynamically |
526 | 27 | import importlib | ||
527 | 28 | group_auth = importlib.import_module(settings.GROUP_AUTH_MODULE) | ||
528 | 27 | from BeautifulSoup import BeautifulSoup | 29 | from BeautifulSoup import BeautifulSoup |
529 | 28 | import config | 30 | import config |
530 | 29 | 31 | ||
531 | @@ -242,7 +244,7 @@ | |||
532 | 242 | license_type = build_info.get("license-type") | 244 | license_type = build_info.get("license-type") |
533 | 243 | license_text = build_info.get("license-text") | 245 | license_text = build_info.get("license-text") |
534 | 244 | theme = build_info.get("theme") | 246 | theme = build_info.get("theme") |
536 | 245 | openid_teams = build_info.get("openid-launchpad-teams") | 247 | auth_groups = build_info.get("auth-groups") |
537 | 246 | max_index = build_info.max_index | 248 | max_index = build_info.max_index |
538 | 247 | elif os.path.isfile(open_eula_path): | 249 | elif os.path.isfile(open_eula_path): |
539 | 248 | return "OPEN" | 250 | return "OPEN" |
540 | @@ -256,7 +258,7 @@ | |||
541 | 256 | license_type = "protected" | 258 | license_type = "protected" |
542 | 257 | license_file = os.path.join(settings.PROJECT_ROOT, | 259 | license_file = os.path.join(settings.PROJECT_ROOT, |
543 | 258 | 'templates/licenses/' + theme + '.txt') | 260 | 'templates/licenses/' + theme + '.txt') |
545 | 259 | openid_teams = False | 261 | auth_groups = False |
546 | 260 | with open(license_file, "r") as infile: | 262 | with open(license_file, "r") as infile: |
547 | 261 | license_text = infile.read() | 263 | license_text = infile.read() |
548 | 262 | elif _check_special_eula(path): | 264 | elif _check_special_eula(path): |
549 | @@ -264,7 +266,7 @@ | |||
550 | 264 | license_type = "protected" | 266 | license_type = "protected" |
551 | 265 | license_file = os.path.join(settings.PROJECT_ROOT, | 267 | license_file = os.path.join(settings.PROJECT_ROOT, |
552 | 266 | 'templates/licenses/' + theme + '.txt') | 268 | 'templates/licenses/' + theme + '.txt') |
554 | 267 | openid_teams = False | 269 | auth_groups = False |
555 | 268 | with open(license_file, "r") as infile: | 270 | with open(license_file, "r") as infile: |
556 | 269 | license_text = infile.read() | 271 | license_text = infile.read() |
557 | 270 | elif _check_special_eula(base_path + "/*"): | 272 | elif _check_special_eula(base_path + "/*"): |
558 | @@ -279,7 +281,7 @@ | |||
559 | 279 | return "OPEN" | 281 | return "OPEN" |
560 | 280 | 282 | ||
561 | 281 | # File matches a license, isn't open. | 283 | # File matches a license, isn't open. |
563 | 282 | if openid_teams: | 284 | if auth_groups: |
564 | 283 | return "OPEN" | 285 | return "OPEN" |
565 | 284 | elif license_text: | 286 | elif license_text: |
566 | 285 | for i in range(max_index): | 287 | for i in range(max_index): |
567 | @@ -392,6 +394,30 @@ | |||
568 | 392 | return response | 394 | return response |
569 | 393 | 395 | ||
570 | 394 | 396 | ||
571 | 397 | def group_auth_failed_response(request, auth_groups): | ||
572 | 398 | """Construct a nice response detailing list of auth groups that | ||
573 | 399 | will allow access to the requested file.""" | ||
574 | 400 | if len(auth_groups) > 1: | ||
575 | 401 | groups_string = "one of the " + auth_groups.pop(0) + " " | ||
576 | 402 | if len(auth_groups) > 1: | ||
577 | 403 | groups_string += ", ".join(auth_groups[0:-1]) | ||
578 | 404 | |||
579 | 405 | groups_string += " or " + auth_groups[-1] + " teams" | ||
580 | 406 | else: | ||
581 | 407 | groups_string = "the " + auth_groups[0] + " team" | ||
582 | 408 | |||
583 | 409 | response = render_to_response( | ||
584 | 410 | 'openid_forbidden_template.html', | ||
585 | 411 | {'login': settings.LOGIN_URL + "?next=" + request.path, | ||
586 | 412 | 'authenticated': request.user.is_authenticated(), | ||
587 | 413 | 'openid_teams': groups_string, | ||
588 | 414 | 'revno': bzr_version.get_my_bzr_revno(), | ||
589 | 415 | }) | ||
590 | 416 | |||
591 | 417 | response.status_code = 403 | ||
592 | 418 | return response | ||
593 | 419 | |||
594 | 420 | |||
595 | 395 | def file_server(request, path): | 421 | def file_server(request, path): |
596 | 396 | """Serve up a file / directory listing or license page as required""" | 422 | """Serve up a file / directory listing or license page as required""" |
597 | 397 | path = iri_to_uri(path) | 423 | path = iri_to_uri(path) |
598 | @@ -415,16 +441,19 @@ | |||
599 | 415 | return HttpResponseForbidden( | 441 | return HttpResponseForbidden( |
600 | 416 | "Error parsing BUILD-INFO.txt") | 442 | "Error parsing BUILD-INFO.txt") |
601 | 417 | 443 | ||
606 | 418 | launchpad_teams = build_info.get("openid-launchpad-teams") | 444 | auth_groups = build_info.get("auth-groups") |
607 | 419 | if launchpad_teams: | 445 | if auth_groups: |
608 | 420 | launchpad_teams = launchpad_teams.split(",") | 446 | auth_groups = auth_groups.split(",") |
609 | 421 | launchpad_teams = [team.strip() for team in launchpad_teams] | 447 | auth_groups = [g.strip() for g in auth_groups] |
610 | 422 | # TODO: use logging! | 448 | # TODO: use logging! |
616 | 423 | print "Checking membership in OpenID groups:", launchpad_teams | 449 | print "Checking membership in auth groups:", auth_groups |
617 | 424 | openid_response = OpenIDAuth.process_openid_auth( | 450 | response = group_auth.process_group_auth(request, auth_groups) |
618 | 425 | request, launchpad_teams) | 451 | if response == False: |
619 | 426 | if openid_response: | 452 | return group_auth_failed_response(request, auth_groups) |
620 | 427 | return openid_response | 453 | elif response == True: |
621 | 454 | pass | ||
622 | 455 | else: | ||
623 | 456 | return response | ||
624 | 428 | 457 | ||
625 | 429 | if target_type == "dir": | 458 | if target_type == "dir": |
626 | 430 | # Generate a link to the parent directory (if one exists) | 459 | # Generate a link to the parent directory (if one exists) |
627 | 431 | 460 | ||
628 | === modified file 'settings.py' | |||
629 | --- settings.py 2013-02-26 19:38:30 +0000 | |||
630 | +++ settings.py 2013-06-03 11:47:27 +0000 | |||
631 | @@ -124,6 +124,10 @@ | |||
632 | 124 | LOGIN_URL = '/linaro-openid/login/' | 124 | LOGIN_URL = '/linaro-openid/login/' |
633 | 125 | LOGIN_REDIRECT_URL = '/' | 125 | LOGIN_REDIRECT_URL = '/' |
634 | 126 | 126 | ||
635 | 127 | # Name of "plugin" module to use for group authentication | ||
636 | 128 | GROUP_AUTH_MODULE = 'license_protected_downloads.group_auth_openid' | ||
637 | 129 | |||
638 | 130 | # Config for django_openid_auth.auth.OpenIDBackend | ||
639 | 127 | OPENID_CREATE_USERS = True | 131 | OPENID_CREATE_USERS = True |
640 | 128 | OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/' | 132 | OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/' |
641 | 129 | OPENID_UPDATE_DETAILS_FROM_SREG = True | 133 | OPENID_UPDATE_DETAILS_FROM_SREG = True |
642 | @@ -132,6 +136,11 @@ | |||
643 | 132 | OPENID_USE_AS_ADMIN_LOGIN = True | 136 | OPENID_USE_AS_ADMIN_LOGIN = True |
644 | 133 | OPENID_USE_EMAIL_FOR_USERNAME = True | 137 | OPENID_USE_EMAIL_FOR_USERNAME = True |
645 | 134 | 138 | ||
646 | 139 | ATLASSIAN_CROWD_API_URL = \ | ||
647 | 140 | "https://login.linaro.org:8443/crowd/rest/usermanagement/1" | ||
648 | 141 | ATLASSIAN_CROWD_API_USERNAME = None | ||
649 | 142 | ATLASSIAN_CROWD_API_PASSWORD = None | ||
650 | 143 | |||
651 | 135 | # A sample logging configuration. The only tangible logging | 144 | # A sample logging configuration. The only tangible logging |
652 | 136 | # performed by this configuration is to send an email to | 145 | # performed by this configuration is to send an email to |
653 | 137 | # the site admins on every HTTP 500 error. | 146 | # the site admins on every HTTP 500 error. |
Paul,
thanks for kick-starting this work!
Overall the work looks great, and the refactoring too.
+class ViewHelpersTest s(BaseServeView Test): group_error( self): auth_failed_ response( request, groups) tNone(response) (isinstance( response, HttpResponse)) ains(response,
+ def test_auth_
+ groups = ["linaro", "batman", "catwoman", "joker"]
+ request = Mock()
+ request.path = "mock_path"
+ response = views.group_
+ self.assertIsNo
+ self.assertTrue
+ self.assertCont
+ "You need to be the member of one of the linaro batman, catwoman "
+ "or joker teams in Launchpad.",
+ status_code=403)
Not something I'm really against, but I would be more interested in testing only
the status code I get, not the kind of "string" I will receive (to me that looks
already more than an unit test). I would test something like that:
self.failUnless Equal(response. status_ code, 403)
(not sure that that is working code though)
+def group_auth_ failed_ response( request, auth_groups): auth_groups[ 0:-1])
+ """Construct a nice response detailing list of auth groups that
+ will allow access to the requested file."""
+ if len(auth_groups) > 1:
+ groups_string = "one of the " + auth_groups.pop(0) + " "
+ if len(auth_groups) > 1:
+ groups_string += ", ".join(
+
+ groups_string += " or " + auth_groups[-1] + " teams"
+ else:
+ groups_string = "the " + auth_groups[0] + " team"
I personally try to avoid concatenating strings in that way. Why not something like:
if len(auth_group) > 1:
groups_ string += (" %s" % ", ".join( auth_groups[ 0:-1])) .rstrip( )
groups_string = "one of the %s" % auth_groups.pop(0)
if len(auth_groups) > 1:
groups_string += " or %s teams" % auth_groups[-1]
else:
groups_string = "the %s team" % auth_groups[0]
I prefer it since it is clearer what you are trying to achieve and you can easily spot spurious white spaces.
I'm approving since what I listed are not really blockers, and functionalities are OK, but if you feel to apply them, do while merging.
Another thing I noticed, but not related to this code review: we are missing a license for all the code in linaro- license- protection, and all license header too. We should probably apply one at one point.