Skip to content

Commit 7391edf

Browse files
committed
chore(auth): add password login
1 parent b236258 commit 7391edf

5 files changed

Lines changed: 93 additions & 30 deletions

File tree

rootfs/api/apps_extra/social_core/actions.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import json
22
from urllib.parse import quote
3-
43
from social_core.utils import (
54
partial_pipeline_data,
65
sanitize_redirect,
76
setting_url,
87
user_is_active,
98
user_is_authenticated,
109
)
10+
from api.oauth import TokenManager
1111

1212

1313
def do_auth(backend, redirect_name="next"):
@@ -40,8 +40,8 @@ def form2json(form_data):
4040
query = urlparse("?" + form_data).query
4141
params = parse_qs(query)
4242
return {key: params[key][0] for key in params}
43-
from django.core.cache import cache
44-
cache.set("oidc_key_" + data.get("key", ""), form2json(url).get("state"), 60 * 10)
43+
manager = TokenManager()
44+
manager.set_state(data.get("key", ""), form2json(url).get("state"))
4545
return response
4646

4747

@@ -129,11 +129,8 @@ def do_complete(backend, login, user=None, redirect_name="next", *args, **kwargs
129129
if social_auth and social_auth.extra_data:
130130
extra_data = json.loads(social_auth.extra_data) if \
131131
isinstance(social_auth.extra_data, str) else social_auth.extra_data
132-
from django.core.cache import cache
133-
cache.set("oidc_state_" + data.get("state"),
134-
{"token": extra_data.get("id_token", "fail"),
135-
"username": user.username},
136-
60 * 10)
132+
manager = TokenManager()
133+
manager.set_token(data.get("state"), extra_data.get("access_token", "fail"), user.username)
137134
return response
138135

139136

rootfs/api/authentication.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from rest_framework.authentication import TokenAuthentication, \
88
get_authorization_header
99
from rest_framework import exceptions
10-
from api.oauth import OAuthManager
10+
1111

1212
logger = logging.getLogger(__name__)
1313

@@ -44,19 +44,21 @@ def authenticate(self, request):
4444
msg = gettext_lazy(
4545
'Invalid token header. Token string should not contain invalid characters.')
4646
raise exceptions.AuthenticationFailed(msg)
47-
return cache.get_or_set(
48-
token, lambda: self._get_user(token), settings.OAUTH_CACHE_USER_TIME), None
47+
return self.sync_user(token), None
4948
return super(DryccAuthentication, self).authenticate(request)
5049

5150
@staticmethod
52-
def _get_user(key):
53-
from api import serializers
54-
try:
55-
user_info = OAuthManager().get_user_by_token(key)
56-
if not user_info.get('email'):
57-
user_info['email'] = OAuthManager().get_email_by_token(key)
58-
user, _ = serializers.UserSerializer.update_or_create(user_info)
59-
return user
60-
except Exception as e:
61-
logger.info(e)
62-
raise exceptions.AuthenticationFailed(gettext_lazy('Verify token fail.'))
51+
def sync_user(token):
52+
def _sync_user(token):
53+
from api import serializers
54+
from api.oauth import OAuthManager
55+
try:
56+
user_info = OAuthManager().get_user_by_token(token)
57+
if not user_info.get('email'):
58+
user_info['email'] = OAuthManager().get_email_by_token(token)
59+
user, _ = serializers.UserSerializer.update_or_create(user_info)
60+
return user
61+
except Exception as e:
62+
logger.info(e)
63+
raise exceptions.AuthenticationFailed(gettext_lazy('Verify token fail.'))
64+
return cache.get_or_set(token, lambda: _sync_user(token), settings.OAUTH_CACHE_USER_TIME)

rootfs/api/oauth.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,26 @@
22

33
import requests
44
from django.conf import settings
5+
from django.core.cache import cache
56
from authlib.integrations.requests_client import OAuth2Session
67

78

9+
class TokenManager(object):
10+
11+
def __init__(self, timeout=60 * 10):
12+
self.timeout = timeout
13+
14+
def set_state(self, key, state):
15+
cache.set("oidc_key_" + key, state, self.timeout)
16+
17+
def set_token(self, state, token, username):
18+
cache.set("oidc_state_" + state, {"token": token, "username": username}, self.timeout)
19+
20+
def get_token(self, key):
21+
state = cache.get("oidc_key_" + key, "")
22+
return cache.get("oidc_state_" + state, {})
23+
24+
825
class OAuthManager(object):
926

1027
def __init__(self):

rootfs/api/serializers/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
from django.conf import settings
1212
from django.contrib.auth import get_user_model
1313
from django.utils import timezone
14+
from django.utils.translation import gettext_lazy
1415
from rest_framework import serializers
1516

17+
1618
from api import models
1719
from api.exceptions import DryccException
1820
from scheduler.resources.pod import DEFAULT_CONTAINER_PORT
@@ -96,6 +98,21 @@ def to_representation(self, obj):
9698
return obj
9799

98100

101+
class AuthSerializer(serializers.Serializer):
102+
username = serializers.CharField(
103+
label=gettext_lazy("Username"),
104+
required=False,
105+
write_only=True
106+
)
107+
password = serializers.CharField(
108+
label=gettext_lazy("Password"),
109+
style={'input_type': 'password'},
110+
required=False,
111+
trim_whitespace=False,
112+
write_only=True,
113+
)
114+
115+
99116
class UserSerializer(serializers.ModelSerializer):
100117
class Meta:
101118
model = User

rootfs/api/views.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import uuid
66
import logging
77
import json
8+
import requests
89
from django.db.models import Q
910
from django.core.cache import cache
1011
from django.http import Http404, HttpResponse
@@ -20,7 +21,7 @@
2021
from rest_framework.response import Response
2122
from rest_framework.viewsets import GenericViewSet
2223

23-
from api import monitor, models, permissions, serializers, viewsets, authentication
24+
from api import monitor, models, permissions, serializers, viewsets, authentication, oauth
2425
from api.tasks import scale_app, restart_app, mount_app, downstream_model_owner
2526
from api.exceptions import AlreadyExists, ServiceUnavailable, DryccException
2627

@@ -85,22 +86,51 @@ def complete(request, backend, *args, **kwargs):
8586
class AuthLoginView(GenericViewSet):
8687

8788
permission_classes = (AllowAny, )
89+
serializer_class = serializers.AuthSerializer
8890

8991
def login(self, request, *args, **kwargs):
90-
def get_local_host(request):
91-
uri = request.build_absolute_uri()
92-
return uri[0:uri.find(request.path)]
93-
res = redirect(get_local_host(request) + "/v2/login/drycc/?key=" + uuid.uuid4().hex)
94-
return res
92+
key = uuid.uuid4().hex
93+
serializer = self.get_serializer(data=request.data)
94+
serializer.is_valid(raise_exception=True)
95+
username = serializer.validated_data.get('username')
96+
password = serializer.validated_data.get('password')
97+
if username and password:
98+
return self._create_interactive_response(username, password, key)
99+
return self._create_browser_response(key)
100+
101+
def _create_browser_response(self, key):
102+
uri = self.request.build_absolute_uri()
103+
return redirect(f"{uri[0:uri.find(self.request.path)]}/v2/login/drycc/?key={key}")
104+
105+
def _create_interactive_response(self, username, password, key):
106+
response = requests.post(
107+
settings.SOCIAL_AUTH_DRYCC_ACCESS_TOKEN_URL,
108+
data={
109+
'grant_type': 'password',
110+
'client_id': settings.SOCIAL_AUTH_DRYCC_KEY,
111+
'client_secret': settings.SOCIAL_AUTH_DRYCC_SECRET,
112+
'username': username,
113+
'password': password,
114+
},
115+
)
116+
if response.status_code != 200:
117+
raise DryccException(response.content)
118+
state = uuid.uuid4().hex
119+
token = response.json()["access_token"]
120+
authentication.DryccAuthentication.sync_user(token)
121+
manager = oauth.TokenManager()
122+
manager.set_state(key, state)
123+
manager.set_token(state, token, username)
124+
return HttpResponse(json.dumps({"key": key}))
95125

96126

97127
class AuthTokenView(GenericViewSet):
98128

99129
permission_classes = (AllowAny, )
100130

101131
def token(self, request, *args, **kwargs):
102-
state = cache.get("oidc_key_" + self.kwargs['key'], "")
103-
token = cache.get("oidc_state_" + state, {})
132+
manager = oauth.TokenManager()
133+
token = manager.get_token(self.kwargs['key'])
104134
if not token.get('token'):
105135
return HttpResponse(status=404)
106136
return HttpResponse(json.dumps(token))

0 commit comments

Comments
 (0)