Skip to content

Commit

Permalink
Merge pull request #33 from ridi/feature/public
Browse files Browse the repository at this point in the history
Feature/public
  • Loading branch information
jaquan-paik authored Oct 10, 2019
2 parents e29d349 + c895fde commit b8c6c6c
Show file tree
Hide file tree
Showing 24 changed files with 491 additions and 262 deletions.
7 changes: 7 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
Changelog
=========
1.0.0 (Oct 10st 2019)
------------------
- Change main logic to get public key from OAuth2 server
- to get public key, it sends request to OAuth2 server, and memorize that key until key expires.
- when getting public key, it use [JWKS](https://tools.ietf.org/html/rfc7517) type
- it makes able to adapt OAuth2 server's key changing dynamically and using multi key.

0.0.15 (Aug 1st 2019)
------------------
- Add cryptography in package
Expand Down
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ name = "pypi"
"PyJWT" = "==1.6.1"
"requests" = "==2.20.0"
"cryptography" = "==2.3"
"pycrypto" = "==2.6.1"

[dev-packages]
"flake8" = "*"
Expand Down
87 changes: 47 additions & 40 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions lib/decorators/memorize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import time
from typing import Callable

DEFAULT_MEMORIZE_TIMEOUT = 60


def memorize(timeout: int = DEFAULT_MEMORIZE_TIMEOUT):
def _wrapper(func: Callable):
_cache = {}
_timeouts = {}

def _decorator(*args, clear: bool = False, **kwargs):
if clear:
return func(*args, **kwargs)

key = func.__name__ + str(args) + str(kwargs)
if key not in _cache:
_timeouts[key] = time.time()
_cache[key] = func(*args, **kwargs)

delta = time.time() - _timeouts[key]
if delta > timeout:
_timeouts[key] = time.time()
_cache[key] = func(*args, **kwargs)

return _cache[key]
return _decorator
return _wrapper
49 changes: 49 additions & 0 deletions lib/decorators/retry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import traceback
from functools import wraps
from typing import Callable, Tuple, Type

_DEFAULT_RETRY_COUNT = 5


class RetryFailException(Exception):
pass


def retry(
retry_count: int = _DEFAULT_RETRY_COUNT,
retriable_exceptions: Tuple[Type[BaseException]] = (BaseException,),
print_stacktrace: bool = False,
):
def _decorator(func: Callable):
def _wrapper(*args, **kwargs):
stacktraces = []

for _ in range(0, retry_count):
try:
value = func(*args, **kwargs)
except Exception as e:
if is_retriable_exception(e, retriable_exceptions):
stacktraces.append(traceback.format_exc())
continue

raise e
else:
return value

if print_stacktrace:
print('[RetryFail][StackTrace] %s', stacktraces)

raise RetryFailException

return wraps(func)(_wrapper)

return _decorator


def is_retriable_exception(exception: Type[BaseException], retriable_exceptions: Tuple[Type[BaseException]]) -> bool:
is_class = isinstance(exception, type)

if is_class:
return issubclass(exception, retriable_exceptions)

return isinstance(exception, retriable_exceptions)
5 changes: 5 additions & 0 deletions lib/utils/bytes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def bytes_to_int(by):
result = 0
for b in by:
result = result * 256 + int(b)
return result
28 changes: 9 additions & 19 deletions ridi_django_oauth2/config.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
from typing import Dict

from django.conf import settings

from ridi_oauth2.introspector.dtos import JwtInfo


class _Settings:
JWT_SECRETS = 'RIDI_OAUTH2_JWT_SECRETS'

class _SettingKeyName:
KEY_URL = 'RIDI_OAUTH2_KEY_URL'
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'
Expand All @@ -19,23 +14,18 @@ class _Default:
REFRESH_TOKEN_COOKIE_KEY = "ridi-rt"


# JwtInfo
_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)
_RIDI_ACCESS_TOKEN_COOKIE_KEY = getattr(settings, _Settings.ACCESS_TOKEN_COOKIE_KEY, _Default.ACCESS_TOKEN_COOKIE_KEY)
_RIDI_REFRESH_TOKEN_COOKIE_KEY = getattr(settings, _Settings.REFRESH_TOKEN_COOKIE_KEY, _Default.REFRESH_TOKEN_COOKIE_KEY)
_RIDI_COOKIE_DOMAIN = getattr(settings, _SettingKeyName.COOKIE_DOMAIN, _Default.COOKIE_DOMAIN)
_RIDI_ACCESS_TOKEN_COOKIE_KEY = getattr(settings, _SettingKeyName.ACCESS_TOKEN_COOKIE_KEY, _Default.ACCESS_TOKEN_COOKIE_KEY)
_RIDI_REFRESH_TOKEN_COOKIE_KEY = getattr(settings, _SettingKeyName.REFRESH_TOKEN_COOKIE_KEY, _Default.REFRESH_TOKEN_COOKIE_KEY)

_RIDI_OAUTH2_KEY_URL = getattr(settings, _SettingKeyName.KEY_URL)


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

@staticmethod
def get_cookie_domain() -> str:
Expand Down
7 changes: 6 additions & 1 deletion ridi_django_oauth2/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from django.contrib.auth.models import AnonymousUser
from django.utils.deprecation import MiddlewareMixin

from ridi_django_oauth2.response import HttpUnauthorizedResponse
from ridi_django_oauth2.utils.token import get_token_from_cookie, get_token_info
from ridi_oauth2.introspector.exceptions import PublicKeyException


class AuthenticationMiddleware(MiddlewareMixin):
Expand All @@ -12,7 +14,10 @@ def process_request(self, request):
token = get_token_from_cookie(request=request)
token_info = None
if token.access_token:
token_info = get_token_info(token.access_token.token)
try:
token_info = get_token_info(token.access_token.token)
except PublicKeyException:
return HttpUnauthorizedResponse()

if token_info is not None:
user, _ = get_user_model().objects.get_or_create(u_idx=token_info.u_idx)
Expand Down
7 changes: 3 additions & 4 deletions ridi_django_oauth2/utils/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ def get_token_from_cookie(request: HttpRequest) -> TokenData:


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

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

pass
return token_info


Expand Down
12 changes: 6 additions & 6 deletions ridi_oauth2/client/dtos.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import typing
from typing import Dict


class ClientInfo:
def __init__(self, client_id: str, client_secret: str, scope: str=None, redirect_uri: str=None):
def __init__(self, client_id: str, client_secret: str, scope: str = None, redirect_uri: str = None):
self._client_id = client_id
self._client_secret = client_secret
self._scope = scope
Expand All @@ -26,7 +26,7 @@ def redirect_uri(self) -> str:


class AuthorizationServerInfo:
def __init__(self, authorization_url: str=None, token_url: str=None):
def __init__(self, authorization_url: str = None, token_url: str = None):
self._authorization_url = authorization_url
self._token_url = token_url

Expand All @@ -40,7 +40,7 @@ def token_url(self) -> str:


class Token:
def __init__(self, token: str, expires_in: int=None):
def __init__(self, token: str, expires_in: int = None):
self._token = token
self._expires_in = expires_in

Expand All @@ -54,7 +54,7 @@ def expires_in(self) -> int:


class TokenData:
def __init__(self, access_token: Token, token_type: str=None, scope: str=None, refresh_token: Token=None):
def __init__(self, access_token: Token, token_type: str = None, scope: str = None, refresh_token: Token = None):
self._access_token = access_token
self._token_type = token_type
self._scope = scope
Expand All @@ -78,7 +78,7 @@ def refresh_token(self) -> Token:
return self._refresh_token

@staticmethod
def from_dict(dictionary: typing.Dict):
def from_dict(dictionary: Dict):
access_token = None
if dictionary.get('access_token', None):
access_token = Token(token=dictionary['access_token'], expires_in=dictionary.get('expires_in', None))
Expand Down
Loading

0 comments on commit b8c6c6c

Please sign in to comment.