Show More
@@ -1019,8 +1019,14 b' For example, to enable API access to pat' | |||||
1019 | FilesController:raw, |
|
1019 | FilesController:raw, | |
1020 | FilesController:archivefile |
|
1020 | FilesController:archivefile | |
1021 |
|
1021 | |||
1022 |
After this change, a Kallithea view can be accessed without login |
|
1022 | After this change, a Kallithea view can be accessed without login using | |
1023 | GET parameter ``?api_key=<api_key>`` to the URL. |
|
1023 | bearer authentication, by including this header with the request:: | |
|
1024 | ||||
|
1025 | Authentication: Bearer <api_key> | |||
|
1026 | ||||
|
1027 | Alternatively, the API key can be passed in the URL query string using | |||
|
1028 | ``?api_key=<api_key>``, though this is not recommended due to the increased | |||
|
1029 | risk of API key leaks, and support will likely be removed in the future. | |||
1024 |
|
1030 | |||
1025 | Exposing raw diffs is a good way to integrate with |
|
1031 | Exposing raw diffs is a good way to integrate with | |
1026 | third-party services like code review, or build farms that can download archives. |
|
1032 | third-party services like code review, or build farms that can download archives. |
@@ -365,11 +365,15 b' class BaseController(WSGIController):' | |||||
365 | self.scm_model = ScmModel(self.sa) |
|
365 | self.scm_model = ScmModel(self.sa) | |
366 |
|
366 | |||
367 | @staticmethod |
|
367 | @staticmethod | |
368 | def _determine_auth_user(api_key, session_authuser): |
|
368 | def _determine_auth_user(api_key, bearer_token, session_authuser): | |
|
369 | """ | |||
|
370 | Create an `AuthUser` object given the API key/bearer token | |||
|
371 | (if any) and the value of the authuser session cookie. | |||
369 |
|
|
372 | """ | |
370 | Create an `AuthUser` object given the API key (if any) and the |
|
373 | ||
371 | value of the authuser session cookie. |
|
374 | # Authenticate by bearer token | |
372 | """ |
|
375 | if bearer_token is not None: | |
|
376 | api_key = bearer_token | |||
373 |
|
377 | |||
374 | # Authenticate by API key |
|
378 | # Authenticate by API key | |
375 | if api_key is not None: |
|
379 | if api_key is not None: | |
@@ -459,8 +463,20 b' class BaseController(WSGIController):' | |||||
459 | self._basic_security_checks() |
|
463 | self._basic_security_checks() | |
460 |
|
464 | |||
461 | #set globals for auth user |
|
465 | #set globals for auth user | |
|
466 | ||||
|
467 | bearer_token = None | |||
|
468 | try: | |||
|
469 | # Request.authorization may raise ValueError on invalid input | |||
|
470 | type, params = request.authorization | |||
|
471 | except (ValueError, TypeError): | |||
|
472 | pass | |||
|
473 | else: | |||
|
474 | if type.lower() == 'bearer': | |||
|
475 | bearer_token = params | |||
|
476 | ||||
462 | self.authuser = c.authuser = request.user = self._determine_auth_user( |
|
477 | self.authuser = c.authuser = request.user = self._determine_auth_user( | |
463 | request.GET.get('api_key'), |
|
478 | request.GET.get('api_key'), | |
|
479 | bearer_token, | |||
464 | session.get('authuser'), |
|
480 | session.get('authuser'), | |
465 | ) |
|
481 | ) | |
466 |
|
482 |
@@ -131,7 +131,14 b' def detect_mode(line, default):' | |||||
131 | def generate_api_key(): |
|
131 | def generate_api_key(): | |
132 | """ |
|
132 | """ | |
133 | Generates a random (presumably unique) API key. |
|
133 | Generates a random (presumably unique) API key. | |
|
134 | ||||
|
135 | This value is used in URLs and "Bearer" HTTP Authorization headers, | |||
|
136 | which in practice means it should only contain URL-safe characters | |||
|
137 | (RFC 3986): | |||
|
138 | ||||
|
139 | unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" | |||
134 | """ |
|
140 | """ | |
|
141 | # Hexadecimal certainly qualifies as URL-safe. | |||
135 | return binascii.hexlify(os.urandom(20)) |
|
142 | return binascii.hexlify(os.urandom(20)) | |
136 |
|
143 | |||
137 |
|
144 |
@@ -435,22 +435,31 b' class TestLoginController(TestController' | |||||
435 |
|
435 | |||
436 | def _api_key_test(self, api_key, status): |
|
436 | def _api_key_test(self, api_key, status): | |
437 | """Verifies HTTP status code for accessing an auth-requiring page, |
|
437 | """Verifies HTTP status code for accessing an auth-requiring page, | |
438 |
using the given api_key URL parameter |
|
438 | using the given api_key URL parameter as well as using the API key | |
439 | parameter is passed at all. If api_key is True, a real, working API key |
|
439 | with bearer authentication. | |
440 | is used. |
|
440 | ||
|
441 | If api_key is None, no api_key is passed at all. If api_key is True, | |||
|
442 | a real, working API key is used. | |||
441 | """ |
|
443 | """ | |
442 | with fixture.anon_access(False): |
|
444 | with fixture.anon_access(False): | |
443 | if api_key is None: |
|
445 | if api_key is None: | |
444 | params = {} |
|
446 | params = {} | |
|
447 | headers = {} | |||
445 | else: |
|
448 | else: | |
446 | if api_key is True: |
|
449 | if api_key is True: | |
447 | api_key = User.get_first_admin().api_key |
|
450 | api_key = User.get_first_admin().api_key | |
448 | params = {'api_key': api_key} |
|
451 | params = {'api_key': api_key} | |
|
452 | headers = {'Authorization': 'Bearer ' + str(api_key)} | |||
449 |
|
453 | |||
450 | self.app.get(url(controller='changeset', action='changeset_raw', |
|
454 | self.app.get(url(controller='changeset', action='changeset_raw', | |
451 | repo_name=HG_REPO, revision='tip', **params), |
|
455 | repo_name=HG_REPO, revision='tip', **params), | |
452 | status=status) |
|
456 | status=status) | |
453 |
|
457 | |||
|
458 | self.app.get(url(controller='changeset', action='changeset_raw', | |||
|
459 | repo_name=HG_REPO, revision='tip'), | |||
|
460 | headers=headers, | |||
|
461 | status=status) | |||
|
462 | ||||
454 | @parametrize('test_name,api_key,code', [ |
|
463 | @parametrize('test_name,api_key,code', [ | |
455 | ('none', None, 302), |
|
464 | ('none', None, 302), | |
456 | ('empty_string', '', 403), |
|
465 | ('empty_string', '', 403), |
General Comments 0
You need to be logged in to leave comments.
Login now