Merge pull request #247 from taigaio/refactor/auth-modules
Refactor/auth modulesremotes/origin/enhancement/email-actions
commit
985838cd1a
|
@ -9,3 +9,4 @@ pytest-pythonpath==0.3
|
||||||
coverage==3.7.1
|
coverage==3.7.1
|
||||||
coveralls==0.4.2
|
coveralls==0.4.2
|
||||||
django-slowdown==0.0.1
|
django-slowdown==0.0.1
|
||||||
|
taiga-contrib-github-auth==0.0.2
|
||||||
|
|
|
@ -24,7 +24,10 @@ CELERY_ENABLED = False
|
||||||
MEDIA_ROOT = "/tmp"
|
MEDIA_ROOT = "/tmp"
|
||||||
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
|
||||||
INSTALLED_APPS = INSTALLED_APPS + ["tests"]
|
INSTALLED_APPS = INSTALLED_APPS + [
|
||||||
|
"tests",
|
||||||
|
"taiga_contrib_github_auth",
|
||||||
|
]
|
||||||
|
|
||||||
REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = {
|
REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = {
|
||||||
"anon": None,
|
"anon": None,
|
||||||
|
|
|
@ -27,7 +27,6 @@ from rest_framework import serializers
|
||||||
from taiga.base.api import viewsets
|
from taiga.base.api import viewsets
|
||||||
from taiga.base.decorators import list_route
|
from taiga.base.decorators import list_route
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.connectors import github
|
|
||||||
from taiga.users.services import get_and_validate_user
|
from taiga.users.services import get_and_validate_user
|
||||||
|
|
||||||
from .serializers import PublicRegisterSerializer
|
from .serializers import PublicRegisterSerializer
|
||||||
|
@ -37,8 +36,8 @@ from .serializers import PrivateRegisterForNewUserSerializer
|
||||||
from .services import private_register_for_existing_user
|
from .services import private_register_for_existing_user
|
||||||
from .services import private_register_for_new_user
|
from .services import private_register_for_new_user
|
||||||
from .services import public_register
|
from .services import public_register
|
||||||
from .services import github_register
|
|
||||||
from .services import make_auth_response_data
|
from .services import make_auth_response_data
|
||||||
|
from .services import get_auth_plugins
|
||||||
|
|
||||||
from .permissions import AuthPermission
|
from .permissions import AuthPermission
|
||||||
|
|
||||||
|
@ -135,36 +134,15 @@ class AuthViewSet(viewsets.ViewSet):
|
||||||
return self._private_register(request)
|
return self._private_register(request)
|
||||||
raise exc.BadRequest(_("invalid register type"))
|
raise exc.BadRequest(_("invalid register type"))
|
||||||
|
|
||||||
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
|
# Login view: /api/v1/auth
|
||||||
def create(self, request, **kwargs):
|
def create(self, request, **kwargs):
|
||||||
self.check_permissions(request, 'create', None)
|
self.check_permissions(request, 'create', None)
|
||||||
|
auth_plugins = get_auth_plugins()
|
||||||
|
|
||||||
|
login_type = request.DATA.get("type", None)
|
||||||
|
|
||||||
|
if login_type in auth_plugins:
|
||||||
|
return auth_plugins[login_type]['login_func'](request)
|
||||||
|
|
||||||
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"))
|
raise exc.BadRequest(_("invalid login type"))
|
||||||
|
|
|
@ -29,6 +29,9 @@ from django.db import transaction as tx
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
from djmail.template_mail import MagicMailBuilder, InlineCSSTemplateMail
|
from djmail.template_mail import MagicMailBuilder, InlineCSSTemplateMail
|
||||||
|
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
|
@ -39,6 +42,19 @@ from taiga.base.utils.slug import slugify_uniquely
|
||||||
from .tokens import get_token_for_user
|
from .tokens import get_token_for_user
|
||||||
from .signals import user_registered as user_registered_signal
|
from .signals import user_registered as user_registered_signal
|
||||||
|
|
||||||
|
auth_plugins = {}
|
||||||
|
|
||||||
|
|
||||||
|
def register_auth_plugin(name, login_func):
|
||||||
|
auth_plugins[name] = {
|
||||||
|
"login_func": login_func,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_auth_plugins():
|
||||||
|
return auth_plugins
|
||||||
|
|
||||||
|
|
||||||
def send_register_email(user) -> bool:
|
def send_register_email(user) -> bool:
|
||||||
"""
|
"""
|
||||||
Given a user, send register welcome email
|
Given a user, send register welcome email
|
||||||
|
@ -169,47 +185,6 @@ def private_register_for_new_user(token:str, username:str, email:str,
|
||||||
return user
|
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 = apps.get_model("users", "User")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Github user association exist?
|
|
||||||
user = user_model.objects.get(github_id=github_id)
|
|
||||||
except user_model.DoesNotExist:
|
|
||||||
try:
|
|
||||||
# Is a user with the same email as the github user?
|
|
||||||
user = user_model.objects.get(email=email)
|
|
||||||
user.github_id = github_id
|
|
||||||
user.save(update_fields=["github_id"])
|
|
||||||
except user_model.DoesNotExist:
|
|
||||||
# Create a new user
|
|
||||||
username_unique = slugify_uniquely(username, user_model, slugfield="username")
|
|
||||||
user = user_model.objects.create(email=email,
|
|
||||||
username=username_unique,
|
|
||||||
github_id=github_id,
|
|
||||||
full_name=full_name,
|
|
||||||
bio=bio)
|
|
||||||
|
|
||||||
send_register_email(user)
|
|
||||||
user_registered_signal.send(sender=user.__class__, user=user)
|
|
||||||
|
|
||||||
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:
|
def make_auth_response_data(user) -> dict:
|
||||||
"""
|
"""
|
||||||
Given a domain and user, creates data structure
|
Given a domain and user, creates data structure
|
||||||
|
@ -220,3 +195,15 @@ def make_auth_response_data(user) -> dict:
|
||||||
data = dict(serializer.data)
|
data = dict(serializer.data)
|
||||||
data["auth_token"] = get_token_for_user(user, "authentication")
|
data["auth_token"] = get_token_for_user(user, "authentication")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def normal_login_func(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)
|
||||||
|
|
||||||
|
|
||||||
|
register_auth_plugin("normal", normal_login_func);
|
||||||
|
|
|
@ -21,7 +21,3 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
class ConnectorBaseException(BaseException):
|
class ConnectorBaseException(BaseException):
|
||||||
status_code = 400
|
status_code = 400
|
||||||
default_detail = _("Connection error.")
|
default_detail = _("Connection error.")
|
||||||
|
|
||||||
|
|
||||||
class GitHubApiError(ConnectorBaseException):
|
|
||||||
pass
|
|
||||||
|
|
|
@ -1,166 +0,0 @@
|
||||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
|
||||||
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
|
||||||
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
|
|
||||||
from collections import namedtuple
|
|
||||||
from urllib.parse import urljoin
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from . import exceptions as exc
|
|
||||||
|
|
||||||
|
|
||||||
######################################################
|
|
||||||
## Data
|
|
||||||
######################################################
|
|
||||||
|
|
||||||
CLIENT_ID = getattr(settings, "GITHUB_API_CLIENT_ID", None)
|
|
||||||
CLIENT_SECRET = getattr(settings, "GITHUB_API_CLIENT_SECRET", None)
|
|
||||||
|
|
||||||
URL = getattr(settings, "GITHUB_URL", "https://github.com/")
|
|
||||||
API_URL = getattr(settings, "GITHUB_API_URL", "https://api.github.com/")
|
|
||||||
API_RESOURCES_URLS = {
|
|
||||||
"login": {
|
|
||||||
"authorize": "login/oauth/authorize",
|
|
||||||
"access-token": "login/oauth/access_token"
|
|
||||||
},
|
|
||||||
"user": {
|
|
||||||
"profile": "user",
|
|
||||||
"emails": "user/emails"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
HEADERS = {"Accept": "application/json",}
|
|
||||||
|
|
||||||
AuthInfo = namedtuple("AuthInfo", ["access_token"])
|
|
||||||
User = namedtuple("User", ["id", "username", "full_name", "bio"])
|
|
||||||
Email = namedtuple("Email", ["email", "is_primary"])
|
|
||||||
|
|
||||||
|
|
||||||
######################################################
|
|
||||||
## utils
|
|
||||||
######################################################
|
|
||||||
|
|
||||||
def _build_url(*args, **kwargs) -> str:
|
|
||||||
"""
|
|
||||||
Return a valid url.
|
|
||||||
"""
|
|
||||||
resource_url = API_RESOURCES_URLS
|
|
||||||
for key in args:
|
|
||||||
resource_url = resource_url[key]
|
|
||||||
|
|
||||||
if kwargs:
|
|
||||||
resource_url = resource_url.format(**kwargs)
|
|
||||||
|
|
||||||
return urljoin(API_URL, resource_url)
|
|
||||||
|
|
||||||
|
|
||||||
def _get(url:str, headers:dict) -> dict:
|
|
||||||
"""
|
|
||||||
Make a GET call.
|
|
||||||
"""
|
|
||||||
response = requests.get(url, headers=headers)
|
|
||||||
|
|
||||||
data = response.json()
|
|
||||||
if response.status_code != 200:
|
|
||||||
raise exc.GitHubApiError({"status_code": response.status_code,
|
|
||||||
"error": data.get("error", "")})
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def _post(url:str, params:dict, headers:dict) -> dict:
|
|
||||||
"""
|
|
||||||
Make a POST call.
|
|
||||||
"""
|
|
||||||
response = requests.post(url, params=params, headers=headers)
|
|
||||||
|
|
||||||
data = response.json()
|
|
||||||
if response.status_code != 200 or "error" in data:
|
|
||||||
raise exc.GitHubApiError({"status_code": response.status_code,
|
|
||||||
"error": data.get("error", "")})
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
######################################################
|
|
||||||
## Simple calls
|
|
||||||
######################################################
|
|
||||||
|
|
||||||
def login(access_code:str, client_id:str=CLIENT_ID, client_secret:str=CLIENT_SECRET,
|
|
||||||
headers:dict=HEADERS):
|
|
||||||
"""
|
|
||||||
Get access_token fron an user authorized code, the client id and the client secret key.
|
|
||||||
(See https://developer.github.com/v3/oauth/#web-application-flow).
|
|
||||||
"""
|
|
||||||
if not CLIENT_ID or not CLIENT_SECRET:
|
|
||||||
raise exc.GitHubApiError({"error_message": _("Login with github account is disabled. Contact "
|
|
||||||
"with the sysadmins. Maybe they're snoozing in a "
|
|
||||||
"secret hideout of the data center.")})
|
|
||||||
|
|
||||||
url = urljoin(URL, "login/oauth/access_token")
|
|
||||||
params={"code": access_code,
|
|
||||||
"client_id": client_id,
|
|
||||||
"client_secret": client_secret,
|
|
||||||
"scope": "user:emails"}
|
|
||||||
data = _post(url, params=params, headers=headers)
|
|
||||||
return AuthInfo(access_token=data.get("access_token", None))
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_profile(headers:dict=HEADERS):
|
|
||||||
"""
|
|
||||||
Get authenticated user info.
|
|
||||||
(See https://developer.github.com/v3/users/#get-the-authenticated-user).
|
|
||||||
"""
|
|
||||||
url = _build_url("user", "profile")
|
|
||||||
data = _get(url, headers=headers)
|
|
||||||
return User(id=data.get("id", None),
|
|
||||||
username=data.get("login", None),
|
|
||||||
full_name=(data.get("name", None) or ""),
|
|
||||||
bio=(data.get("bio", None) or ""))
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_emails(headers:dict=HEADERS) -> list:
|
|
||||||
"""
|
|
||||||
Get a list with all emails of the authenticated user.
|
|
||||||
(See https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user).
|
|
||||||
"""
|
|
||||||
url = _build_url("user", "emails")
|
|
||||||
data = _get(url, headers=headers)
|
|
||||||
return [Email(email=e.get("email", None), is_primary=e.get("primary", False))
|
|
||||||
for e in data]
|
|
||||||
|
|
||||||
|
|
||||||
######################################################
|
|
||||||
## Convined calls
|
|
||||||
######################################################
|
|
||||||
|
|
||||||
def me(access_code:str) -> tuple:
|
|
||||||
"""
|
|
||||||
Connect to a github account and get all personal info (profile and the primary email).
|
|
||||||
"""
|
|
||||||
auth_info = login(access_code)
|
|
||||||
|
|
||||||
headers = HEADERS.copy()
|
|
||||||
headers["Authorization"] = "token {}".format(auth_info.access_token)
|
|
||||||
|
|
||||||
user = get_user_profile(headers=headers)
|
|
||||||
emails = get_user_emails(headers=headers)
|
|
||||||
|
|
||||||
primary_email = next(filter(lambda x: x.is_primary, emails))
|
|
||||||
return primary_email.email, user
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.conf import settings
|
||||||
|
import django_pgjson.fields
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_github_id(apps, schema_editor):
|
||||||
|
AuthData = apps.get_model("users", "AuthData")
|
||||||
|
User = apps.get_model("users", "User")
|
||||||
|
for user in User.objects.all():
|
||||||
|
if user.github_id:
|
||||||
|
AuthData.objects.create(user=user, key="github", value=user.github_id, extra={})
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0006_auto_20141030_1132'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AuthData',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)),
|
||||||
|
('key', models.SlugField()),
|
||||||
|
('value', models.CharField(max_length=300)),
|
||||||
|
('extra', django_pgjson.fields.JsonField()),
|
||||||
|
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='authdata',
|
||||||
|
unique_together=set([('key', 'value')]),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_github_id),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='user',
|
||||||
|
name='github_id',
|
||||||
|
),
|
||||||
|
]
|
|
@ -32,6 +32,7 @@ from django.utils import timezone
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
from django.template.defaultfilters import slugify
|
from django.template.defaultfilters import slugify
|
||||||
|
|
||||||
|
from django_pgjson.fields import JsonField
|
||||||
from djorm_pgarray.fields import TextArrayField
|
from djorm_pgarray.fields import TextArrayField
|
||||||
|
|
||||||
from taiga.auth.tokens import get_token_for_user
|
from taiga.auth.tokens import get_token_for_user
|
||||||
|
@ -129,7 +130,6 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||||
|
|
||||||
new_email = models.EmailField(_('new email address'), null=True, blank=True)
|
new_email = models.EmailField(_('new email address'), null=True, blank=True)
|
||||||
|
|
||||||
github_id = models.IntegerField(null=True, blank=True, verbose_name=_("github ID"), db_index=True)
|
|
||||||
is_system = models.BooleanField(null=False, blank=False, default=False)
|
is_system = models.BooleanField(null=False, blank=False, default=False)
|
||||||
|
|
||||||
USERNAME_FIELD = 'username'
|
USERNAME_FIELD = 'username'
|
||||||
|
@ -170,9 +170,9 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||||
self.default_timezone = ""
|
self.default_timezone = ""
|
||||||
self.colorize_tags = True
|
self.colorize_tags = True
|
||||||
self.token = None
|
self.token = None
|
||||||
self.github_id = None
|
|
||||||
self.set_unusable_password()
|
self.set_unusable_password()
|
||||||
self.save()
|
self.save()
|
||||||
|
self.auth_data.all().delete()
|
||||||
|
|
||||||
class Role(models.Model):
|
class Role(models.Model):
|
||||||
name = models.CharField(max_length=200, null=False, blank=False,
|
name = models.CharField(max_length=200, null=False, blank=False,
|
||||||
|
@ -211,6 +211,16 @@ class Role(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class AuthData(models.Model):
|
||||||
|
user = models.ForeignKey('users.User', related_name="auth_data")
|
||||||
|
key = models.SlugField(max_length=50)
|
||||||
|
value = models.CharField(max_length=300)
|
||||||
|
extra = JsonField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ["key", "value"]
|
||||||
|
|
||||||
|
|
||||||
# On Role object is changed, update all membership
|
# On Role object is changed, update all membership
|
||||||
# related to current role.
|
# related to current role.
|
||||||
@receiver(models.signals.post_save, sender=Role,
|
@receiver(models.signals.post_save, sender=Role,
|
||||||
|
|
|
@ -33,9 +33,9 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ("id", "username", "full_name", "full_name_display", "email",
|
fields = ("id", "username", "full_name", "full_name_display", "email",
|
||||||
"github_id", "color", "bio", "default_language",
|
"color", "bio", "default_language",
|
||||||
"default_timezone", "is_active", "photo", "big_photo")
|
"default_timezone", "is_active", "photo", "big_photo")
|
||||||
read_only_fields = ("id", "email", "github_id")
|
read_only_fields = ("id", "email")
|
||||||
|
|
||||||
def validate_username(self, attrs, source):
|
def validate_username(self, attrs, source):
|
||||||
value = attrs[source]
|
value = attrs[source]
|
||||||
|
|
|
@ -24,11 +24,12 @@ from django.core import mail
|
||||||
|
|
||||||
from .. import factories
|
from .. import factories
|
||||||
|
|
||||||
from taiga.base.connectors import github
|
|
||||||
from taiga.front import resolve as resolve_front_url
|
from taiga.front import resolve as resolve_front_url
|
||||||
from taiga.users import models
|
from taiga.users import models
|
||||||
from taiga.auth.tokens import get_token_for_user
|
from taiga.auth.tokens import get_token_for_user
|
||||||
|
|
||||||
|
from taiga_contrib_github_auth import connector as github_connector
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,12 +96,14 @@ def test_response_200_in_registration_with_github_account(client, settings):
|
||||||
form = {"type": "github",
|
form = {"type": "github",
|
||||||
"code": "xxxxxx"}
|
"code": "xxxxxx"}
|
||||||
|
|
||||||
with patch("taiga.base.connectors.github.me") as m_me:
|
auth_data_model = apps.get_model("users", "AuthData")
|
||||||
|
|
||||||
|
with patch("taiga_contrib_github_auth.connector.me") as m_me:
|
||||||
m_me.return_value = ("mmcfly@bttf.com",
|
m_me.return_value = ("mmcfly@bttf.com",
|
||||||
github.User(id=1955,
|
github_connector.User(id=1955,
|
||||||
username="mmcfly",
|
username="mmcfly",
|
||||||
full_name="martin seamus mcfly",
|
full_name="martin seamus mcfly",
|
||||||
bio="time traveler"))
|
bio="time traveler"))
|
||||||
|
|
||||||
response = client.post(reverse("auth-list"), form)
|
response = client.post(reverse("auth-list"), form)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -109,7 +112,7 @@ def test_response_200_in_registration_with_github_account(client, settings):
|
||||||
assert response.data["email"] == "mmcfly@bttf.com"
|
assert response.data["email"] == "mmcfly@bttf.com"
|
||||||
assert response.data["full_name"] == "martin seamus mcfly"
|
assert response.data["full_name"] == "martin seamus mcfly"
|
||||||
assert response.data["bio"] == "time traveler"
|
assert response.data["bio"] == "time traveler"
|
||||||
assert response.data["github_id"] == 1955
|
assert auth_data_model.objects.filter(user__username="mmcfly", key="github", value="1955").count() == 1
|
||||||
|
|
||||||
def test_response_200_in_registration_with_github_account_and_existed_user_by_email(client, settings):
|
def test_response_200_in_registration_with_github_account_and_existed_user_by_email(client, settings):
|
||||||
settings.PUBLIC_REGISTER_ENABLED = False
|
settings.PUBLIC_REGISTER_ENABLED = False
|
||||||
|
@ -117,15 +120,14 @@ def test_response_200_in_registration_with_github_account_and_existed_user_by_em
|
||||||
"code": "xxxxxx"}
|
"code": "xxxxxx"}
|
||||||
user = factories.UserFactory()
|
user = factories.UserFactory()
|
||||||
user.email = "mmcfly@bttf.com"
|
user.email = "mmcfly@bttf.com"
|
||||||
user.github_id = None
|
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
with patch("taiga.base.connectors.github.me") as m_me:
|
with patch("taiga_contrib_github_auth.connector.me") as m_me:
|
||||||
m_me.return_value = ("mmcfly@bttf.com",
|
m_me.return_value = ("mmcfly@bttf.com",
|
||||||
github.User(id=1955,
|
github_connector.User(id=1955,
|
||||||
username="mmcfly",
|
username="mmcfly",
|
||||||
full_name="martin seamus mcfly",
|
full_name="martin seamus mcfly",
|
||||||
bio="time traveler"))
|
bio="time traveler"))
|
||||||
|
|
||||||
response = client.post(reverse("auth-list"), form)
|
response = client.post(reverse("auth-list"), form)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -134,22 +136,24 @@ def test_response_200_in_registration_with_github_account_and_existed_user_by_em
|
||||||
assert response.data["email"] == user.email
|
assert response.data["email"] == user.email
|
||||||
assert response.data["full_name"] == user.full_name
|
assert response.data["full_name"] == user.full_name
|
||||||
assert response.data["bio"] == user.bio
|
assert response.data["bio"] == user.bio
|
||||||
assert response.data["github_id"] == 1955
|
assert user.auth_data.filter(key="github", value="1955").count() == 1
|
||||||
|
|
||||||
def test_response_200_in_registration_with_github_account_and_existed_user_by_github_id(client, settings):
|
def test_response_200_in_registration_with_github_account_and_existed_user_by_github_id(client, settings):
|
||||||
settings.PUBLIC_REGISTER_ENABLED = False
|
settings.PUBLIC_REGISTER_ENABLED = False
|
||||||
form = {"type": "github",
|
form = {"type": "github",
|
||||||
"code": "xxxxxx"}
|
"code": "xxxxxx"}
|
||||||
user = factories.UserFactory()
|
user = factories.UserFactory.create()
|
||||||
user.github_id = 1955
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
with patch("taiga.base.connectors.github.me") as m_me:
|
auth_data_model = apps.get_model("users", "AuthData")
|
||||||
|
auth_data_model.objects.create(user=user, key="github", value="1955", extra={})
|
||||||
|
|
||||||
|
|
||||||
|
with patch("taiga_contrib_github_auth.connector.me") as m_me:
|
||||||
m_me.return_value = ("mmcfly@bttf.com",
|
m_me.return_value = ("mmcfly@bttf.com",
|
||||||
github.User(id=1955,
|
github_connector.User(id=1955,
|
||||||
username="mmcfly",
|
username="mmcfly",
|
||||||
full_name="martin seamus mcfly",
|
full_name="martin seamus mcfly",
|
||||||
bio="time traveler"))
|
bio="time traveler"))
|
||||||
|
|
||||||
response = client.post(reverse("auth-list"), form)
|
response = client.post(reverse("auth-list"), form)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -158,7 +162,6 @@ def test_response_200_in_registration_with_github_account_and_existed_user_by_gi
|
||||||
assert response.data["email"] != "mmcfly@bttf.com"
|
assert response.data["email"] != "mmcfly@bttf.com"
|
||||||
assert response.data["full_name"] != "martin seamus mcfly"
|
assert response.data["full_name"] != "martin seamus mcfly"
|
||||||
assert response.data["bio"] != "time traveler"
|
assert response.data["bio"] != "time traveler"
|
||||||
assert response.data["github_id"] == user.github_id
|
|
||||||
|
|
||||||
def test_response_200_in_registration_with_github_account_and_change_github_username(client, settings):
|
def test_response_200_in_registration_with_github_account_and_change_github_username(client, settings):
|
||||||
settings.PUBLIC_REGISTER_ENABLED = False
|
settings.PUBLIC_REGISTER_ENABLED = False
|
||||||
|
@ -168,12 +171,14 @@ def test_response_200_in_registration_with_github_account_and_change_github_user
|
||||||
user.username = "mmcfly"
|
user.username = "mmcfly"
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
with patch("taiga.base.connectors.github.me") as m_me:
|
auth_data_model = apps.get_model("users", "AuthData")
|
||||||
|
|
||||||
|
with patch("taiga_contrib_github_auth.connector.me") as m_me:
|
||||||
m_me.return_value = ("mmcfly@bttf.com",
|
m_me.return_value = ("mmcfly@bttf.com",
|
||||||
github.User(id=1955,
|
github_connector.User(id=1955,
|
||||||
username="mmcfly",
|
username="mmcfly",
|
||||||
full_name="martin seamus mcfly",
|
full_name="martin seamus mcfly",
|
||||||
bio="time traveler"))
|
bio="time traveler"))
|
||||||
|
|
||||||
response = client.post(reverse("auth-list"), form)
|
response = client.post(reverse("auth-list"), form)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -182,7 +187,7 @@ def test_response_200_in_registration_with_github_account_and_change_github_user
|
||||||
assert response.data["email"] == "mmcfly@bttf.com"
|
assert response.data["email"] == "mmcfly@bttf.com"
|
||||||
assert response.data["full_name"] == "martin seamus mcfly"
|
assert response.data["full_name"] == "martin seamus mcfly"
|
||||||
assert response.data["bio"] == "time traveler"
|
assert response.data["bio"] == "time traveler"
|
||||||
assert response.data["github_id"] == 1955
|
assert auth_data_model.objects.filter(user__username="mmcfly-1", key="github", value="1955").count() == 1
|
||||||
|
|
||||||
def test_response_200_in_registration_with_github_account_in_a_project(client, settings):
|
def test_response_200_in_registration_with_github_account_in_a_project(client, settings):
|
||||||
settings.PUBLIC_REGISTER_ENABLED = False
|
settings.PUBLIC_REGISTER_ENABLED = False
|
||||||
|
@ -192,12 +197,12 @@ def test_response_200_in_registration_with_github_account_in_a_project(client, s
|
||||||
"code": "xxxxxx",
|
"code": "xxxxxx",
|
||||||
"token": membership.token}
|
"token": membership.token}
|
||||||
|
|
||||||
with patch("taiga.base.connectors.github.me") as m_me:
|
with patch("taiga_contrib_github_auth.connector.me") as m_me:
|
||||||
m_me.return_value = ("mmcfly@bttf.com",
|
m_me.return_value = ("mmcfly@bttf.com",
|
||||||
github.User(id=1955,
|
github_connector.User(id=1955,
|
||||||
username="mmcfly",
|
username="mmcfly",
|
||||||
full_name="martin seamus mcfly",
|
full_name="martin seamus mcfly",
|
||||||
bio="time traveler"))
|
bio="time traveler"))
|
||||||
|
|
||||||
response = client.post(reverse("auth-list"), form)
|
response = client.post(reverse("auth-list"), form)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -210,12 +215,12 @@ def test_response_404_in_registration_with_github_in_a_project_with_invalid_toke
|
||||||
"code": "xxxxxx",
|
"code": "xxxxxx",
|
||||||
"token": "123456"}
|
"token": "123456"}
|
||||||
|
|
||||||
with patch("taiga.base.connectors.github.me") as m_me:
|
with patch("taiga_contrib_github_auth.connector.me") as m_me:
|
||||||
m_me.return_value = ("mmcfly@bttf.com",
|
m_me.return_value = ("mmcfly@bttf.com",
|
||||||
github.User(id=1955,
|
github_connector.User(id=1955,
|
||||||
username="mmcfly",
|
username="mmcfly",
|
||||||
full_name="martin seamus mcfly",
|
full_name="martin seamus mcfly",
|
||||||
bio="time traveler"))
|
bio="time traveler"))
|
||||||
|
|
||||||
response = client.post(reverse("auth-list"), form)
|
response = client.post(reverse("auth-list"), form)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
|
@ -18,8 +18,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from unittest.mock import patch, Mock
|
from unittest.mock import patch, Mock
|
||||||
from taiga.base.connectors import github
|
from taiga_contrib_github_auth import connector as github
|
||||||
from taiga.base.connectors import exceptions as exc
|
|
||||||
|
|
||||||
|
|
||||||
def test_url_builder():
|
def test_url_builder():
|
||||||
|
@ -34,8 +33,8 @@ def test_url_builder():
|
||||||
|
|
||||||
|
|
||||||
def test_login_without_settings_params():
|
def test_login_without_settings_params():
|
||||||
with pytest.raises(exc.GitHubApiError) as e, \
|
with pytest.raises(github.GitHubApiError) as e, \
|
||||||
patch("taiga.base.connectors.github.requests") as m_requests:
|
patch("taiga_contrib_github_auth.connector.requests") as m_requests:
|
||||||
m_requests.post.return_value = m_response = Mock()
|
m_requests.post.return_value = m_response = Mock()
|
||||||
m_response.status_code = 200
|
m_response.status_code = 200
|
||||||
m_response.json.return_value = {"access_token": "xxxxxxxx"}
|
m_response.json.return_value = {"access_token": "xxxxxxxx"}
|
||||||
|
@ -46,9 +45,9 @@ def test_login_without_settings_params():
|
||||||
|
|
||||||
|
|
||||||
def test_login_success():
|
def test_login_success():
|
||||||
with patch("taiga.base.connectors.github.requests") as m_requests, \
|
with patch("taiga_contrib_github_auth.connector.requests") as m_requests, \
|
||||||
patch("taiga.base.connectors.github.CLIENT_ID") as CLIENT_ID, \
|
patch("taiga_contrib_github_auth.connector.CLIENT_ID") as CLIENT_ID, \
|
||||||
patch("taiga.base.connectors.github.CLIENT_SECRET") as CLIENT_SECRET:
|
patch("taiga_contrib_github_auth.connector.CLIENT_SECRET") as CLIENT_SECRET:
|
||||||
CLIENT_ID = "*CLIENT_ID*"
|
CLIENT_ID = "*CLIENT_ID*"
|
||||||
CLIENT_SECRET = "*CLIENT_SECRET*"
|
CLIENT_SECRET = "*CLIENT_SECRET*"
|
||||||
m_requests.post.return_value = m_response = Mock()
|
m_requests.post.return_value = m_response = Mock()
|
||||||
|
@ -67,10 +66,10 @@ def test_login_success():
|
||||||
|
|
||||||
|
|
||||||
def test_login_whit_errors():
|
def test_login_whit_errors():
|
||||||
with pytest.raises(exc.GitHubApiError) as e, \
|
with pytest.raises(github.GitHubApiError) as e, \
|
||||||
patch("taiga.base.connectors.github.requests") as m_requests, \
|
patch("taiga_contrib_github_auth.connector.requests") as m_requests, \
|
||||||
patch("taiga.base.connectors.github.CLIENT_ID") as CLIENT_ID, \
|
patch("taiga_contrib_github_auth.connector.CLIENT_ID") as CLIENT_ID, \
|
||||||
patch("taiga.base.connectors.github.CLIENT_SECRET") as CLIENT_SECRET:
|
patch("taiga_contrib_github_auth.connector.CLIENT_SECRET") as CLIENT_SECRET:
|
||||||
CLIENT_ID = "*CLIENT_ID*"
|
CLIENT_ID = "*CLIENT_ID*"
|
||||||
CLIENT_SECRET = "*CLIENT_SECRET*"
|
CLIENT_SECRET = "*CLIENT_SECRET*"
|
||||||
m_requests.post.return_value = m_response = Mock()
|
m_requests.post.return_value = m_response = Mock()
|
||||||
|
@ -84,7 +83,7 @@ def test_login_whit_errors():
|
||||||
|
|
||||||
|
|
||||||
def test_get_user_profile_success():
|
def test_get_user_profile_success():
|
||||||
with patch("taiga.base.connectors.github.requests") as m_requests:
|
with patch("taiga_contrib_github_auth.connector.requests") as m_requests:
|
||||||
m_requests.get.return_value = m_response = Mock()
|
m_requests.get.return_value = m_response = Mock()
|
||||||
m_response.status_code = 200
|
m_response.status_code = 200
|
||||||
m_response.json.return_value = {"id": 1955,
|
m_response.json.return_value = {"id": 1955,
|
||||||
|
@ -103,8 +102,8 @@ def test_get_user_profile_success():
|
||||||
|
|
||||||
|
|
||||||
def test_get_user_profile_whit_errors():
|
def test_get_user_profile_whit_errors():
|
||||||
with pytest.raises(exc.GitHubApiError) as e, \
|
with pytest.raises(github.GitHubApiError) as e, \
|
||||||
patch("taiga.base.connectors.github.requests") as m_requests:
|
patch("taiga_contrib_github_auth.connector.requests") as m_requests:
|
||||||
m_requests.get.return_value = m_response = Mock()
|
m_requests.get.return_value = m_response = Mock()
|
||||||
m_response.status_code = 401
|
m_response.status_code = 401
|
||||||
m_response.json.return_value = {"error": "Invalid credentials"}
|
m_response.json.return_value = {"error": "Invalid credentials"}
|
||||||
|
@ -116,7 +115,7 @@ def test_get_user_profile_whit_errors():
|
||||||
|
|
||||||
|
|
||||||
def test_get_user_emails_success():
|
def test_get_user_emails_success():
|
||||||
with patch("taiga.base.connectors.github.requests") as m_requests:
|
with patch("taiga_contrib_github_auth.connector.requests") as m_requests:
|
||||||
m_requests.get.return_value = m_response = Mock()
|
m_requests.get.return_value = m_response = Mock()
|
||||||
m_response.status_code = 200
|
m_response.status_code = 200
|
||||||
m_response.json.return_value = [{"email": "darth-vader@bttf.com", "primary": False},
|
m_response.json.return_value = [{"email": "darth-vader@bttf.com", "primary": False},
|
||||||
|
@ -134,8 +133,8 @@ def test_get_user_emails_success():
|
||||||
|
|
||||||
|
|
||||||
def test_get_user_emails_whit_errors():
|
def test_get_user_emails_whit_errors():
|
||||||
with pytest.raises(exc.GitHubApiError) as e, \
|
with pytest.raises(github.GitHubApiError) as e, \
|
||||||
patch("taiga.base.connectors.github.requests") as m_requests:
|
patch("taiga_contrib_github_auth.connector.requests") as m_requests:
|
||||||
m_requests.get.return_value = m_response = Mock()
|
m_requests.get.return_value = m_response = Mock()
|
||||||
m_response.status_code = 401
|
m_response.status_code = 401
|
||||||
m_response.json.return_value = {"error": "Invalid credentials"}
|
m_response.json.return_value = {"error": "Invalid credentials"}
|
||||||
|
@ -147,9 +146,9 @@ def test_get_user_emails_whit_errors():
|
||||||
|
|
||||||
|
|
||||||
def test_me():
|
def test_me():
|
||||||
with patch("taiga.base.connectors.github.login") as m_login, \
|
with patch("taiga_contrib_github_auth.connector.login") as m_login, \
|
||||||
patch("taiga.base.connectors.github.get_user_profile") as m_get_user_profile, \
|
patch("taiga_contrib_github_auth.connector.get_user_profile") as m_get_user_profile, \
|
||||||
patch("taiga.base.connectors.github.get_user_emails") as m_get_user_emails:
|
patch("taiga_contrib_github_auth.connector.get_user_emails") as m_get_user_emails:
|
||||||
m_login.return_value = github.AuthInfo(access_token="xxxxxxxx")
|
m_login.return_value = github.AuthInfo(access_token="xxxxxxxx")
|
||||||
m_get_user_profile.return_value = github.User(id=1955,
|
m_get_user_profile.return_value = github.User(id=1955,
|
||||||
username="mmcfly",
|
username="mmcfly",
|
||||||
|
|
Loading…
Reference in New Issue