Skip to content

Commit

Permalink
Merge pull request #26 from ridi/feature/multi-key
Browse files Browse the repository at this point in the history
무중단 키 변경을 위해 다수의 키로 시도 할 수 있도록 변경
  • Loading branch information
antiline authored Apr 22, 2019
2 parents b03f39b + ade7183 commit 3b77768
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 28 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
language: python
python:
- '3.6'
dist: xenial

stages:
- test
Expand Down
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Changelog
=========
0.0.12 (Apr 23th 2019)
------------------
- Support select jwt sign key by kid

0.0.11 (Apr 17th 2018)
------------------
- Change dependency version
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,18 @@ AUTH_USER_MODEL = 'ridi_django_oauth2.RidiUser'


# RIDI Setting
RIDI_OAUTH2_JWT_SECRET = 'this-is-jwt-secret'
RIDI_OAUTH2_JWT_SECRETS = [
{
'kid': '0',
'secret': 'this-is-hs256-key',
'alg': 'HS256',
},
{
'kid': '1',
'secret': 'this-is-rs256-public-key',
'alg': 'RS256',
},
]
RIDI_OAUTH2_CLIENT_ID = 'this-is-client-id'
RIDI_OAUTH2_CLIENT_SECRET = 'this-is-client-secret'

Expand All @@ -41,7 +52,6 @@ REST_FRAMEWORK = {
'ridi_django_oauth2.rest_framework.authentication.OAuth2Authentication',
)
}

```


Expand Down
20 changes: 10 additions & 10 deletions ridi_django_oauth2/config.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
from typing import Dict

from django.conf import settings

from ridi_oauth2.introspector.dtos import JwtInfo


class _Settings:
JWT_SECRET_NAME = 'RIDI_OAUTH2_JWT_SECRET'
JWT_ALGORITHM_NAME = 'RIDI_OAUTH2_JWT_ALGORITHM'
JWT_SECRETS = 'RIDI_OAUTH2_JWT_SECRETS'

COOKIE_DOMAIN = 'RIDI_OAUTH2_COOKIE_DOMAIN'
ACCESS_TOKEN_COOKIE_KEY = 'RIDI_OAUTH2_ACCESS_TOKEN_COOKIE_KEY'
REFRESH_TOKEN_COOKIE_KEY = 'RIDI_OAUTH2_REFRESH_TOKEN_COOKIE_KEY'


class _Default:
JWT_ALGORITHM = 'HS256'

COOKIE_DOMAIN = 'ridibooks.com'
ACCESS_TOKEN_COOKIE_KEY = "ridi-at"
REFRESH_TOKEN_COOKIE_KEY = "ridi-rt"


# JwtInfo
_RIDI_OAUTH2_JWT_SECRET = getattr(settings, _Settings.JWT_SECRET_NAME)
_RIDI_OAUTH2_JWT_ALGORITHM = getattr(settings, _Settings.JWT_ALGORITHM_NAME, _Default.JWT_ALGORITHM)

_JWT_INFO = JwtInfo(secret=_RIDI_OAUTH2_JWT_SECRET, algorithm=_RIDI_OAUTH2_JWT_ALGORITHM)
_RIDI_OAUTH2_JWT_SECRETS = getattr(settings, _Settings.JWT_SECRETS)
_JWT_INFOS = dict([
(_RIDI_OAUTH2_JWT_SECRET['kid'], JwtInfo(_RIDI_OAUTH2_JWT_SECRET['secret'], _RIDI_OAUTH2_JWT_SECRET['alg']))
for _RIDI_OAUTH2_JWT_SECRET in _RIDI_OAUTH2_JWT_SECRETS
])

# Cookie
_RIDI_COOKIE_DOMAIN = getattr(settings, _Settings.COOKIE_DOMAIN, _Default.COOKIE_DOMAIN)
Expand All @@ -34,8 +34,8 @@ class _Default:

class RidiOAuth2Config:
@staticmethod
def get_jwt_info() -> JwtInfo:
return _JWT_INFO
def get_jwt_infos() -> Dict[str, JwtInfo]:
return _JWT_INFOS

@staticmethod
def get_cookie_domain() -> str:
Expand Down
4 changes: 3 additions & 1 deletion ridi_django_oauth2/utils/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ def get_token_from_cookie(request: HttpRequest) -> TokenData:


def get_token_info(token: str) -> typing.Optional[AccessTokenInfo]:
jwt_infos = RidiOAuth2Config.get_jwt_infos()
try:
token_info = JwtIntrospectHelper.introspect(jwt_info=RidiOAuth2Config.get_jwt_info(), access_token=token)
token_info = JwtIntrospectHelper.introspect(jwt_infos, token)

except (KeyError, ExpireTokenException, InvalidJwtSignatureException):
token_info = None

Expand Down
17 changes: 16 additions & 1 deletion ridi_oauth2/introspector/helpers.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
from typing import Dict

import jwt

from ridi_oauth2.introspector.dtos import AccessTokenInfo, JwtInfo
from ridi_oauth2.introspector.exceptions import InvalidJwtSignatureException
from ridi_oauth2.introspector.jwt_introspector import JwtIntrospector


class JwtIntrospectHelper:
@staticmethod
def introspect(jwt_info: JwtInfo, access_token: str) -> AccessTokenInfo:
def introspect(jwt_infos: Dict[str, JwtInfo], access_token: str) -> AccessTokenInfo:
unverified_header = jwt.get_unverified_header(access_token)
kid = unverified_header.get('kid')
if not kid:
raise InvalidJwtSignatureException

jwt_info = jwt_infos.get(kid)
if not jwt_info:
raise InvalidJwtSignatureException

introspector = JwtIntrospector(jwt_info=jwt_info, access_token=access_token)
result = introspector.introspect()

try:
return AccessTokenInfo.from_dict(result)

except KeyError:
raise InvalidJwtSignatureException
8 changes: 7 additions & 1 deletion runcommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@
'MIDDLEWARE_CLASSES': (
'ridi_django_oauth2.middlewares.AuthenticationMiddleware',
),
'RIDI_OAUTH2_JWT_SECRET': 'dummy_jwt_secret',
'RIDI_OAUTH2_JWT_SECRETS': [
{
'kid': '0',
'secret': 'dummy_jwt_secret',
'alg': 'HS256'
},
],
'RIDI_ridi_oauth2_ID': 'dummy_client_id',
'RIDI_ridi_oauth2_SECRET': 'dummy_client_secret',

Expand Down
8 changes: 7 additions & 1 deletion runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@
'MIDDLEWARE_CLASSES': (
'ridi_django_oauth2.middlewares.AuthenticationMiddleware',
),
'RIDI_OAUTH2_JWT_SECRET': 'dummy_jwt_secret',
'RIDI_OAUTH2_JWT_SECRETS': [
{
'kid': '0',
'secret': 'dummy_jwt_secret',
'alg': 'HS256'
},
],
'RIDI_ridi_oauth2_ID': 'dummy_client_id',
'RIDI_ridi_oauth2_SECRET': 'dummy_client_secret',

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import find_packages, setup

version = '0.0.11'
version = '0.0.12'

with open('requirements/base.txt') as f:
install_requires = [line for line in f if line and not line.startswith('-')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,31 @@
class AuthenticationMiddlewareTestCase(TestCase):
def setUp(self):
self.middleware = AuthenticationMiddleware()
self.headers = {
'kid': '0',
}

self.valid_token = jwt.encode(payload={
'sub': 'testuser',
'u_idx': 123123,
'exp': int(time.time()) + 60 * 60,
'client_id': 'asfeih29snv8as213i',
'scope': 'all'
}, key='dummy_jwt_secret').decode()
}, key='dummy_jwt_secret', headers=self.headers).decode()

self.loose_token = jwt.encode(payload={
'sub': 'testuser',
'u_idx': 123123,
'exp': int(time.time()) + 60 * 60,
}, key='dummy_jwt_secret').decode()
}, key='dummy_jwt_secret', headers=self.headers).decode()

self.expire_token = jwt.encode(payload={
'sub': 'testuser',
'u_idx': 123123,
'exp': int(time.time()) - 60 * 60,
'client_id': 'asfeih29snv8as213i',
'scope': 'all'
}, key='dummy_jwt_secret').decode()
}, key='dummy_jwt_secret', headers=self.headers).decode()

def test_login_and_not_expire(self):
request = Mock()
Expand Down
28 changes: 23 additions & 5 deletions tests/tests_ridi_django_oauth2/test_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ def setUp(self):
'scope': 'all'
}

self.headers = {
'kid': '0',
}

self.dummy_view = login_required()(MagicMock(return_value=HttpResponse(content='success')))
self.custom_dummy_view = login_required(response_handler=response_handler)(MagicMock(return_value=HttpResponse(content='success')))

Expand All @@ -51,7 +55,9 @@ def test_not_login_with_custom_response(self):
def test_not_exists_token_info(self):
request = Mock()
request.COOKIES = {
RidiOAuth2Config.get_access_token_cookie_key(): jwt.encode(payload=self.jwt_payload, key='dummy_jwt_secret').decode(),
RidiOAuth2Config.get_access_token_cookie_key(): jwt.encode(
self.jwt_payload, 'dummy_jwt_secret', headers=self.headers
).decode(),
}
self.middleware.process_request(request)

Expand All @@ -66,7 +72,9 @@ def test_not_exists_token_info(self):
def test_login(self):
request = Mock()
request.COOKIES = {
RidiOAuth2Config.get_access_token_cookie_key(): jwt.encode(payload=self.jwt_payload, key='dummy_jwt_secret').decode(),
RidiOAuth2Config.get_access_token_cookie_key(): jwt.encode(
self.jwt_payload, 'dummy_jwt_secret', headers=self.headers
).decode(),
}
self.middleware.process_request(request)

Expand Down Expand Up @@ -95,6 +103,10 @@ def setUp(self):
'scope': 'user_info'
}

self.headers = {
'kid': '0',
}

self.dummy_view1 = scope_required(required_scopes=['user_info'])(MagicMock(return_value=HttpResponse(content='success1')))
self.dummy_view2 = scope_required(required_scopes=[('user_info', 'purchase')])(
MagicMock(return_value=HttpResponse(content='success2'))
Expand All @@ -106,7 +118,9 @@ def setUp(self):
def test_all_scope(self):
request = Mock()
request.COOKIES = {
RidiOAuth2Config.get_access_token_cookie_key(): jwt.encode(payload=self.jwt_payload, key='dummy_jwt_secret').decode(),
RidiOAuth2Config.get_access_token_cookie_key(): jwt.encode(
self.jwt_payload, 'dummy_jwt_secret', headers=self.headers
).decode(),
}
self.middleware.process_request(request)

Expand All @@ -126,7 +140,9 @@ def test_all_scope(self):
def test_restriction_scope(self):
request = Mock()
request.COOKIES = {
RidiOAuth2Config.get_access_token_cookie_key(): jwt.encode(payload=self.jwt_loose_payload, key='dummy_jwt_secret').decode(),
RidiOAuth2Config.get_access_token_cookie_key(): jwt.encode(
self.jwt_loose_payload, 'dummy_jwt_secret', headers=self.headers
).decode(),
}
self.middleware.process_request(request)

Expand All @@ -145,7 +161,9 @@ def test_restriction_scope(self):
def test_restriction_scope_with_custom_response(self):
request = Mock()
request.COOKIES = {
RidiOAuth2Config.get_access_token_cookie_key(): jwt.encode(payload=self.jwt_loose_payload, key='dummy_jwt_secret').decode(),
RidiOAuth2Config.get_access_token_cookie_key(): jwt.encode(
self.jwt_loose_payload, 'dummy_jwt_secret', headers=self.headers
).decode(),
}
self.middleware.process_request(request)

Expand Down
5 changes: 4 additions & 1 deletion tests/tests_ridi_django_oauth2/test_token_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ def test_token_info(self):
'client_id': 'asfeih29snv8as213i',
'scope': 'all'
}
valid_token = jwt.encode(payload=payload, key='dummy_jwt_secret').decode()
headers = {
'kid': '0',
}
valid_token = jwt.encode(payload=payload, key='dummy_jwt_secret', headers=headers).decode()

token_info = get_token_info(token=valid_token)

Expand Down
7 changes: 5 additions & 2 deletions tests/tests_ridi_oauth2/test_introspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@ class JwtIntrospectorTestCase(unittest.TestCase):
def setUp(self):
self.secret = generate_random_str(chars=string.ascii_letters + string.digits + string.punctuation)
self.alg = 'HS256'
self.headers = {
'kid': '0',
}

self.claim = {
"sub": "testuser",
"exp": int(time.time()) + 60 * 60,
}
self.token = jwt.encode(self.claim, self.secret, algorithm=self.alg)
self.token = jwt.encode(self.claim, self.secret, self.alg, self.headers)

self.invalid_claim = {
"sub": "testuser",
"exp": int(time.time()) - 60 * 60,
}
self.invalid_token = jwt.encode(self.invalid_claim, self.secret, algorithm=self.alg)
self.invalid_token = jwt.encode(self.invalid_claim, self.secret, self.alg, self.headers)

def test_introspect(self):
jwt_info = JwtInfo(secret=self.secret, algorithm=self.alg)
Expand Down

0 comments on commit 3b77768

Please sign in to comment.