##// END OF EJS Templates
auth: add support for "Bearer" auth scheme (API key variant)...
Søren Løvborg -
r6347:9cf90371 default
parent child Browse files
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 by adding a
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. If api_key is None, no api_key
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