diff --git a/taiga/auth/api.py b/taiga/auth/api.py index 4987ee8b..11a0e41d 100644 --- a/taiga/auth/api.py +++ b/taiga/auth/api.py @@ -28,6 +28,7 @@ from rest_framework import serializers from taiga.base.decorators import list_route from taiga.base import exceptions as exc +from taiga.base.connectors import github from taiga.users.services import get_and_validate_user from .serializers import PublicRegisterSerializer @@ -37,6 +38,7 @@ from .serializers import PrivateRegisterForNewUserSerializer from .services import private_register_for_existing_user from .services import private_register_for_new_user from .services import public_register +from .services import github_register from .services import make_auth_response_data @@ -130,11 +132,34 @@ class AuthViewSet(viewsets.ViewSet): return self._private_register(request) raise exc.BadRequest(_("invalid register type")) - # Login view: /api/v1/auth - def create(self, request, **kwargs): + def _login(self, request): username = request.DATA.get('username', None) password = request.DATA.get('password', None) user = get_and_validate_user(username=username, password=password) data = make_auth_response_data(user) return Response(data, status=status.HTTP_200_OK) + + def _github_login(self, request): + code = request.DATA.get('code', None) + token = request.DATA.get('token', None) + + email, user_info = github.me(code) + + user = github_register(username=user_info.username, + email=email, + full_name=user_info.full_name, + github_id=user_info.id, + bio=user_info.bio, + token=token) + data = make_auth_response_data(user) + return Response(data, status=status.HTTP_200_OK) + + # Login view: /api/v1/auth + def create(self, request, **kwargs): + type = request.DATA.get("type", None) + if type == "normal": + return self._login(request) + elif type == "github": + return self._github_login(request) + raise exc.BadRequest(_("invalid login type")) diff --git a/taiga/auth/services.py b/taiga/auth/services.py index f7dda5d7..cebc24e6 100644 --- a/taiga/auth/services.py +++ b/taiga/auth/services.py @@ -63,14 +63,18 @@ def send_private_register_email(user, **kwargs) -> bool: return bool(email.send()) -def is_user_already_registred(*, username:str, email:str) -> bool: +def is_user_already_registred(*, username:str, email:str, github_id:int=None) -> bool: """ Checks if a specified user is already registred. """ user_model = get_model("users", "User") - qs = user_model.objects.filter(Q(username=username) | - Q(email=email)) + + or_expr = Q(username=username) | Q(email=email) + if github_id: + or_expr = or_expr | Q(email=email) + + qs = user_model.objects.filter(or_expr) return qs.exists() @@ -90,7 +94,7 @@ def get_membership_by_token(token:str): @tx.atomic -def public_register(username:str, password:str, email:str, first_name:str, last_name:str): +def public_register(username:str, password:str, email:str, full_name:str): """ Given a parsed parameters, try register a new user knowing that it follows a public register flow. @@ -107,8 +111,7 @@ def public_register(username:str, password:str, email:str, first_name:str, last_ user_model = get_model("users", "User") user = user_model(username=username, email=email, - first_name=first_name, - last_name=last_name) + full_name=full_name) user.set_password(password) user.save() @@ -138,21 +141,18 @@ def private_register_for_existing_user(token:str, username:str, password:str): @tx.atomic def private_register_for_new_user(token:str, username:str, email:str, - first_name:str, last_name:str, password:str): + full_name:str, password:str): """ Given a inviation token, try register new user matching the invitation token. """ - - user_model = get_model("users", "User") - if is_user_already_registred(username=username, email=email): raise exc.WrongArguments(_("Username or Email is already in use.")) + user_model = get_model("users", "User") user = user_model(username=username, email=email, - first_name=first_name, - last_name=last_name) + full_name=full_name) user.set_password(password) try: @@ -167,6 +167,30 @@ def private_register_for_new_user(token:str, username:str, email:str, return user +@tx.atomic +def github_register(username:str, email:str, full_name:str, github_id:int, bio:str, token:str=None): + """ + Register a new user from github. + + This can raise `exc.IntegrityError` exceptions in + case of conflics found. + + :returns: User + """ + user_model = get_model("users", "User") + user, created = user_model.objects.get_or_create(github_id=github_id, + defaults={"username": username, + "email": email, + "full_name": full_name, + "bio": bio}) + if token: + membership = get_membership_by_token(token) + membership.user = user + membership.save(update_fields=["user"]) + + return user + + def make_auth_response_data(user) -> dict: """ Given a domain and user, creates data structure diff --git a/taiga/base/connectors/github.py b/taiga/base/connectors/github.py index 73705dbf..344a44ce 100644 --- a/taiga/base/connectors/github.py +++ b/taiga/base/connectors/github.py @@ -79,8 +79,8 @@ def _get(url:str, headers:dict) -> dict: data = response.json() if response.status_code != 200: - raise exc.GitHubApiErrorApiError({"status_code": response.status_code, - "error": data.get("error", "")}) + raise exc.GitHubApiError({"status_code": response.status_code, + "error": data.get("error", "")}) return data @@ -88,12 +88,12 @@ def _post(url:str, params:dict, headers:dict) -> dict: """ Make a POST call. """ - response = requests.post(url,params=params, headers=headers) + response = requests.post(url, params=params, headers=headers) data = response.json() if response.status_code != 200 or "error" in data: - raise exc.GitHubApiErrorApiError({"status_code": response.status_code, - "error": data.get("error", "")}) + raise exc.GitHubApiError({"status_code": response.status_code, + "error": data.get("error", "")}) return data @@ -136,7 +136,7 @@ def get_user_emails(headers:dict=HEADERS) -> list: """ url = _build_url("user", "emails") data = _get(url, headers=headers) - return [Email(email=e.get("email", None), is_primary=e.get("primary", None)) + return [Email(email=e.get("email", None), is_primary=e.get("primary", False)) for e in data]