Merge pull request #33 from taigaio/auth-move
Auth module refactor.remotes/origin/enhancement/email-actions
commit
a946f47bc2
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
python ./manage.py dumpdata -n --indent=4 users.Role > taiga/base/users/fixtures/initial_role.json
|
python ./manage.py dumpdata -n --indent=4 users.Role > taiga/users/fixtures/initial_role.json
|
||||||
|
|
|
@ -16,3 +16,6 @@ django-jinja>=0.23
|
||||||
jinja2==2.7.1
|
jinja2==2.7.1
|
||||||
pygments>=1.6
|
pygments>=1.6
|
||||||
django-sites==0.4
|
django-sites==0.4
|
||||||
|
|
||||||
|
# Comment it if you are using python >= 3.4
|
||||||
|
enum34==0.9.23
|
||||||
|
|
|
@ -157,7 +157,7 @@ INSTALLED_APPS = [
|
||||||
"django.contrib.admin",
|
"django.contrib.admin",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
|
|
||||||
"taiga.base.users",
|
"taiga.users",
|
||||||
"taiga.base.notifications",
|
"taiga.base.notifications",
|
||||||
"taiga.base.searches",
|
"taiga.base.searches",
|
||||||
"taiga.base",
|
"taiga.base",
|
||||||
|
@ -266,10 +266,10 @@ API_LIMIT_PER_PAGE = 0
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||||
# Mainly used by taiga-front
|
# Mainly used by taiga-front
|
||||||
"taiga.base.auth.Token",
|
"taiga.auth.backends.Token",
|
||||||
|
|
||||||
# Mainly used for api debug.
|
# Mainly used for api debug.
|
||||||
"taiga.base.auth.Session",
|
"taiga.auth.backends.Session",
|
||||||
),
|
),
|
||||||
"FILTER_BACKEND": "taiga.base.filters.FilterBackend",
|
"FILTER_BACKEND": "taiga.base.filters.FilterBackend",
|
||||||
"EXCEPTION_HANDLER": "taiga.base.exceptions.exception_handler",
|
"EXCEPTION_HANDLER": "taiga.base.exceptions.exception_handler",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
from functools import partial
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from taiga.base.decorators import list_route
|
||||||
|
from taiga.base import exceptions as exc
|
||||||
|
from taiga.users.services import get_and_validate_user
|
||||||
|
from taiga.domains.services import is_public_register_enabled_for_domain
|
||||||
|
|
||||||
|
from .serializers import PublicRegisterSerializer
|
||||||
|
from .serializers import PrivateRegisterForExistingUserSerializer
|
||||||
|
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 make_auth_response_data
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_data(data:dict, *, cls):
|
||||||
|
"""
|
||||||
|
Generic function for parse user data using
|
||||||
|
specified serializer on `cls` keyword parameter.
|
||||||
|
|
||||||
|
Raises: RequestValidationError exception if
|
||||||
|
some errors found when data is validated.
|
||||||
|
|
||||||
|
Returns the parsed data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
serializer = cls(data=data)
|
||||||
|
if not serializer.is_valid():
|
||||||
|
raise exc.RequestValidationError(serializer.errors)
|
||||||
|
return serializer.data
|
||||||
|
|
||||||
|
# Parse public register data
|
||||||
|
parse_public_register_data = partial(_parse_data, cls=PublicRegisterSerializer)
|
||||||
|
|
||||||
|
# Parse private register data for existing user
|
||||||
|
parse_private_register_for_existing_user_data = \
|
||||||
|
partial(_parse_data, cls=PrivateRegisterForExistingUserSerializer)
|
||||||
|
|
||||||
|
# Parse private register data for new user
|
||||||
|
parse_private_register_for_new_user_data = \
|
||||||
|
partial(_parse_data, cls=PrivateRegisterForNewUserSerializer)
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterTypeEnum(Enum):
|
||||||
|
new_user = 1
|
||||||
|
existing_user = 2
|
||||||
|
|
||||||
|
|
||||||
|
def parse_register_type(userdata:dict) -> str:
|
||||||
|
"""
|
||||||
|
Parses user data and detects that register type is.
|
||||||
|
It returns RegisterTypeEnum value.
|
||||||
|
"""
|
||||||
|
# Create adhoc inner serializer for avoid parse
|
||||||
|
# manually the user data.
|
||||||
|
class _serializer(serializers.Serializer):
|
||||||
|
existing = serializers.BooleanField()
|
||||||
|
|
||||||
|
instance = _serializer(data=userdata)
|
||||||
|
if not instance.is_valid():
|
||||||
|
raise exc.RequestValidationError(instance.errors)
|
||||||
|
|
||||||
|
if instance.data["existing"]:
|
||||||
|
return RegisterTypeEnum.existing_user
|
||||||
|
return RegisterTypeEnum.new_user
|
||||||
|
|
||||||
|
|
||||||
|
class AuthViewSet(viewsets.ViewSet):
|
||||||
|
permission_classes = (AllowAny,)
|
||||||
|
|
||||||
|
def _public_register(self, request):
|
||||||
|
if not is_public_register_enabled_for_domain(request.domain):
|
||||||
|
raise exc.BadRequest(_("Public register is disabled for this domain."))
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = parse_public_register_data(request.DATA)
|
||||||
|
user = public_register(request.domain, **data)
|
||||||
|
except exc.IntegrityError as e:
|
||||||
|
raise exc.BadRequest(e.detail)
|
||||||
|
|
||||||
|
data = make_auth_response_data(request.domain, user)
|
||||||
|
return Response(data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
def _private_register(self, request):
|
||||||
|
register_type = parse_register_type(request.DATA)
|
||||||
|
|
||||||
|
if register_type is RegisterTypeEnum.existing_user:
|
||||||
|
data = parse_private_register_for_existing_user_data(request.DATA)
|
||||||
|
user = private_register_for_existing_user(request.domain, **data)
|
||||||
|
else:
|
||||||
|
data = parse_private_register_for_new_user_data(request.DATA)
|
||||||
|
user = private_register_for_new_user(request.domain, **data)
|
||||||
|
|
||||||
|
data = make_auth_response_data(request.domain, user)
|
||||||
|
return Response(data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
@list_route(methods=["POST"], permission_classes=[AllowAny])
|
||||||
|
def register(self, request, **kwargs):
|
||||||
|
type = request.DATA.get("type", None)
|
||||||
|
if type == "public":
|
||||||
|
return self._public_register(request)
|
||||||
|
elif type == "private":
|
||||||
|
return self._private_register(request)
|
||||||
|
raise exc.BadRequest(_("invalid register type"))
|
||||||
|
|
||||||
|
# Login view: /api/v1/auth
|
||||||
|
def create(self, request, **kwargs):
|
||||||
|
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(request.domain, user)
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
|
@ -1,4 +1,20 @@
|
||||||
# -*- coding: utf-8 -*-
|
"""
|
||||||
|
Authentication backends for rest framework.
|
||||||
|
|
||||||
|
This module exposes two backends: session and token.
|
||||||
|
|
||||||
|
The first (session) is a modified version of standard
|
||||||
|
session authentication backend of restframework with
|
||||||
|
csrf token disabled.
|
||||||
|
|
||||||
|
And the second (token) implements own version of oauth2
|
||||||
|
like authentiacation but with selfcontained tokens. Thats
|
||||||
|
makes authentication totally stateles.
|
||||||
|
|
||||||
|
It uses django signing framework for create new
|
||||||
|
selfcontained tokens. This trust tokes from external
|
||||||
|
fraudulent modifications.
|
||||||
|
"""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import re
|
import re
|
||||||
|
@ -6,22 +22,21 @@ import re
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.db.models import get_model
|
from django.db.models import get_model
|
||||||
from rest_framework.authentication import BaseAuthentication
|
from rest_framework.authentication import BaseAuthentication
|
||||||
|
from taiga.base import exceptions as exc
|
||||||
import taiga.base.exceptions as exc
|
|
||||||
|
|
||||||
|
|
||||||
class Session(BaseAuthentication):
|
class Session(BaseAuthentication):
|
||||||
"""
|
"""
|
||||||
Same as rest_framework.authentication.SessionAuthentication
|
Session based authentication like the standard
|
||||||
but without csrf.
|
`rest_framework.authentication.SessionAuthentication`
|
||||||
|
but with csrf disabled (for obvious reasons because
|
||||||
|
it is for api.
|
||||||
|
|
||||||
|
NOTE: this is only for api web interface. Is not used
|
||||||
|
for common api usage and should be disabled on production.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def authenticate(self, request):
|
def authenticate(self, request):
|
||||||
"""
|
|
||||||
Returns a `User` if the request session currently has a logged in user.
|
|
||||||
Otherwise returns `None`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
http_request = request._request
|
http_request = request._request
|
||||||
user = getattr(http_request, 'user', None)
|
user = getattr(http_request, 'user', None)
|
||||||
|
|
||||||
|
@ -32,11 +47,23 @@ class Session(BaseAuthentication):
|
||||||
|
|
||||||
|
|
||||||
def get_token_for_user(user):
|
def get_token_for_user(user):
|
||||||
|
"""
|
||||||
|
Generate a new signed token containing
|
||||||
|
a specified user.
|
||||||
|
"""
|
||||||
data = {"user_id": user.id}
|
data = {"user_id": user.id}
|
||||||
return signing.dumps(data)
|
return signing.dumps(data)
|
||||||
|
|
||||||
|
|
||||||
def get_user_for_token(token):
|
def get_user_for_token(token):
|
||||||
|
"""
|
||||||
|
Given a selfcontained token, try parse and
|
||||||
|
unsign it.
|
||||||
|
|
||||||
|
If token passes a validation, returns
|
||||||
|
a user instance corresponding with user_id stored
|
||||||
|
in the incoming token.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
data = signing.loads(token)
|
data = signing.loads(token)
|
||||||
except signing.BadSignature:
|
except signing.BadSignature:
|
||||||
|
@ -54,7 +81,10 @@ def get_user_for_token(token):
|
||||||
|
|
||||||
class Token(BaseAuthentication):
|
class Token(BaseAuthentication):
|
||||||
"""
|
"""
|
||||||
Stateless authentication system partially based on oauth.
|
Self-contained stateles authentication implementatrion
|
||||||
|
that work similar to oauth2.
|
||||||
|
It uses django signing framework for trust data stored
|
||||||
|
in the token.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
auth_rx = re.compile(r"^Bearer (.+)$")
|
auth_rx = re.compile(r"^Bearer (.+)$")
|
|
@ -14,16 +14,10 @@ class PublicRegisterSerializer(BaseRegisterSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PrivateRegisterSerializer(BaseRegisterSerializer):
|
class PrivateRegisterForNewUserSerializer(BaseRegisterSerializer):
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PrivateGenericRegisterSerializer(serializers.Serializer):
|
|
||||||
token = serializers.CharField(max_length=255, required=True)
|
token = serializers.CharField(max_length=255, required=True)
|
||||||
existing = serializers.BooleanField()
|
|
||||||
# existing = serializers.ChoiceField(choices=[("on", "on"), ("off", "off")])
|
|
||||||
|
|
||||||
|
class PrivateRegisterForExistingUserSerializer(serializers.Serializer):
|
||||||
class PrivateRegisterExistingSerializer(serializers.Serializer):
|
|
||||||
username = serializers.CharField(max_length=200)
|
username = serializers.CharField(max_length=200)
|
||||||
password = serializers.CharField(min_length=4)
|
password = serializers.CharField(min_length=4)
|
||||||
|
token = serializers.CharField(max_length=255, required=True)
|
|
@ -0,0 +1,179 @@
|
||||||
|
"""
|
||||||
|
This module contains a domain logic for authentication
|
||||||
|
process. It called services because in DDD says it.
|
||||||
|
|
||||||
|
NOTE: Python doesn't have java limitations for "everytghing
|
||||||
|
should be contained in a class". Because of that, it
|
||||||
|
not uses clasess and uses simple functions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db.models.loading import get_model
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.db import transaction as tx
|
||||||
|
from django.db import IntegrityError
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from djmail.template_mail import MagicMailBuilder
|
||||||
|
|
||||||
|
from taiga.base import exceptions as exc
|
||||||
|
from taiga.users.serializers import UserSerializer
|
||||||
|
from taiga.users.services import get_and_validate_user
|
||||||
|
from taiga.domains.services import (create_domain_member,
|
||||||
|
is_user_exists_on_domain)
|
||||||
|
|
||||||
|
from .backends import get_token_for_user
|
||||||
|
|
||||||
|
|
||||||
|
def send_public_register_email(user) -> bool:
|
||||||
|
"""
|
||||||
|
Given a user, send public register welcome email
|
||||||
|
message to specified user.
|
||||||
|
"""
|
||||||
|
|
||||||
|
context = {"user": user}
|
||||||
|
mbuilder = MagicMailBuilder()
|
||||||
|
email = mbuilder.public_register_user(user.email, context)
|
||||||
|
return bool(email.send())
|
||||||
|
|
||||||
|
|
||||||
|
def send_private_register_email(user, **kwargs) -> bool:
|
||||||
|
"""
|
||||||
|
Given a user, send private register welcome
|
||||||
|
email message to specified user.
|
||||||
|
"""
|
||||||
|
context = {"user": user}
|
||||||
|
context.update(kwargs)
|
||||||
|
|
||||||
|
mbuilder = MagicMailBuilder()
|
||||||
|
email = mbuilder.private_register_user(user.email, context)
|
||||||
|
return bool(email.send())
|
||||||
|
|
||||||
|
|
||||||
|
def is_user_already_registred(*, username:str, email:str) -> 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))
|
||||||
|
return qs.exists()
|
||||||
|
|
||||||
|
|
||||||
|
def get_membership_by_token(token:str):
|
||||||
|
"""
|
||||||
|
Given a token, returns a membership instance
|
||||||
|
that matches with specified token.
|
||||||
|
|
||||||
|
If not matches with any membership NotFound exception
|
||||||
|
is raised.
|
||||||
|
"""
|
||||||
|
membership_model = get_model("projects", "Membership")
|
||||||
|
qs = membership_model.objects.filter(token=token)
|
||||||
|
if len(qs) == 0:
|
||||||
|
raise exc.NotFound("Token not matches any member.")
|
||||||
|
return qs[0]
|
||||||
|
|
||||||
|
|
||||||
|
@tx.atomic
|
||||||
|
def public_register(domain, *, username:str, password:str,
|
||||||
|
email:str, first_name:str, last_name:str):
|
||||||
|
"""
|
||||||
|
Given a parsed parameters, try register a new user
|
||||||
|
knowing that it follows a public register flow.
|
||||||
|
|
||||||
|
This can raise `exc.IntegrityError` exceptions in
|
||||||
|
case of conflics found.
|
||||||
|
|
||||||
|
:returns: User
|
||||||
|
"""
|
||||||
|
|
||||||
|
if is_user_already_registred(username=username, email=email):
|
||||||
|
raise exc.IntegrityError("User is already registred.")
|
||||||
|
|
||||||
|
user_model = get_model("users", "User")
|
||||||
|
user = user_model(username=username,
|
||||||
|
email=email,
|
||||||
|
first_name=first_name,
|
||||||
|
last_name=last_name)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
if not is_user_exists_on_domain(domain, user):
|
||||||
|
create_domain_member(domain, user)
|
||||||
|
|
||||||
|
# send_public_register_email(user)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@tx.atomic
|
||||||
|
def private_register_for_existing_user(domain, *, token:str, username:str, password:str):
|
||||||
|
"""
|
||||||
|
Register works not only for register users, also serves for accept
|
||||||
|
inviatations for projects as existing user.
|
||||||
|
|
||||||
|
Given a invitation token with parsed parameters, accept inviation
|
||||||
|
as existing user.
|
||||||
|
"""
|
||||||
|
|
||||||
|
user = get_and_validate_user(username=username, password=password)
|
||||||
|
membership = get_membership_by_token(token)
|
||||||
|
|
||||||
|
if not is_user_exists_on_domain(domain, user):
|
||||||
|
create_domain_member(domain, user)
|
||||||
|
|
||||||
|
membership.user = user
|
||||||
|
membership.save(update_fields=["user"])
|
||||||
|
|
||||||
|
# send_private_register_email(user)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@tx.atomic
|
||||||
|
def private_register_for_new_user(domain, *, token:str, username:str, email:str,
|
||||||
|
first_name:str, last_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 = user_model(username=username,
|
||||||
|
email=email,
|
||||||
|
first_name=first_name,
|
||||||
|
last_name=last_name)
|
||||||
|
|
||||||
|
user.set_password(password)
|
||||||
|
try:
|
||||||
|
user.save()
|
||||||
|
except IntegrityError:
|
||||||
|
raise exc.IntegrityError(_("Error on creating new user."))
|
||||||
|
|
||||||
|
if not is_user_exists_on_domain(domain, user):
|
||||||
|
create_domain_member(domain, user)
|
||||||
|
|
||||||
|
membership = get_membership_by_token(token)
|
||||||
|
membership.user = user
|
||||||
|
membership.save(update_fields=["user"])
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def make_auth_response_data(domain, user) -> dict:
|
||||||
|
"""
|
||||||
|
Given a domain and user, creates data structure
|
||||||
|
using python dict containing a representation
|
||||||
|
of the logged user.
|
||||||
|
"""
|
||||||
|
serializer = UserSerializer(user)
|
||||||
|
data = dict(serializer.data)
|
||||||
|
|
||||||
|
data['is_site_owner'] = domain.user_is_owner(user)
|
||||||
|
data['is_site_staff'] = domain.user_is_staff(user)
|
||||||
|
data["auth_token"] = get_token_for_user(user)
|
||||||
|
|
||||||
|
return data
|
|
@ -4,26 +4,30 @@ import uuid
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.conf.urls import patterns, include, url
|
from django.conf.urls import patterns, url
|
||||||
from django import test
|
from django import test
|
||||||
from django.db.models import get_model
|
from django.db.models import get_model
|
||||||
|
|
||||||
from rest_framework.views import APIView
|
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from taiga import urls
|
from taiga import urls
|
||||||
from taiga.base import auth
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.users.tests import create_user, create_domain
|
from taiga.users.tests import create_user, create_domain
|
||||||
from taiga.projects.tests import create_project
|
from taiga.domains.models import DomainMember
|
||||||
|
from taiga.domains.services import is_user_exists_on_domain
|
||||||
|
from taiga.domains import get_default_domain
|
||||||
|
from taiga.auth.backends import Token as TokenAuthBackend
|
||||||
|
from taiga.auth.backends import get_token_for_user
|
||||||
|
from taiga.auth import services
|
||||||
|
|
||||||
from taiga.domains.models import Domain, DomainMember
|
from taiga.projects.tests import create_project
|
||||||
from taiga.projects.models import Membership
|
from taiga.projects.tests import add_membership
|
||||||
|
|
||||||
|
|
||||||
class TestAuthView(viewsets.ViewSet):
|
class TestAuthView(viewsets.ViewSet):
|
||||||
authentication_classes = (auth.Token,)
|
authentication_classes = (TokenAuthBackend,)
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
@ -35,8 +39,119 @@ urls.urlpatterns += patterns("",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TokenAuthTests(test.TestCase):
|
class AuthServicesTests(test.TestCase):
|
||||||
fixtures = ["initial_domains.json",]
|
fixtures = ["initial_domains.json",]
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.user1 = create_user(1)
|
||||||
|
self.domain = get_default_domain()
|
||||||
|
|
||||||
|
def test_send_public_register_email(self):
|
||||||
|
"""
|
||||||
|
This test should explictly fail because these emails
|
||||||
|
at this momment does not exists.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
services.send_public_register_email(self.user1)
|
||||||
|
|
||||||
|
def test_send_private_register_email(self):
|
||||||
|
"""
|
||||||
|
This test should explictly fail because these emails
|
||||||
|
at this momment does not exists.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
services.send_private_register_email(self.user1)
|
||||||
|
|
||||||
|
def test_is_user_already_registred(self):
|
||||||
|
username = self.user1.username
|
||||||
|
email = self.user1.email
|
||||||
|
|
||||||
|
self.assertTrue(services.is_user_already_registred(username=username, email=email))
|
||||||
|
self.assertTrue(services.is_user_already_registred(username=username, email="foo@bar.com"))
|
||||||
|
self.assertTrue(services.is_user_already_registred(username="foo", email=email))
|
||||||
|
self.assertFalse(services.is_user_already_registred(username="foo", email="foo@bar.com"))
|
||||||
|
|
||||||
|
def test_get_membership_by_token(self):
|
||||||
|
with self.assertRaises(exc.NotFound):
|
||||||
|
services.get_membership_by_token("invalidtoken")
|
||||||
|
|
||||||
|
project = create_project(1, self.user1)
|
||||||
|
membership = add_membership(project, self.user1, "back")
|
||||||
|
membership.token = "foobar"
|
||||||
|
membership.save()
|
||||||
|
|
||||||
|
m = services.get_membership_by_token("foobar")
|
||||||
|
self.assertEqual(m.id, membership.id)
|
||||||
|
|
||||||
|
def test_public_register(self):
|
||||||
|
with self.assertRaises(exc.IntegrityError):
|
||||||
|
services.public_register(self.domain,
|
||||||
|
username=self.user1.username,
|
||||||
|
password="secret",
|
||||||
|
email=self.user1.email,
|
||||||
|
first_name="foo",
|
||||||
|
last_name="bar")
|
||||||
|
|
||||||
|
user = services.public_register(self.domain,
|
||||||
|
username="foousername",
|
||||||
|
password="foosecret",
|
||||||
|
email="foo@bar.ca",
|
||||||
|
first_name="Foo",
|
||||||
|
last_name="Bar")
|
||||||
|
self.assertEqual(user.username, "foousername")
|
||||||
|
self.assertTrue(user.check_password("foosecret"))
|
||||||
|
self.assertTrue(is_user_exists_on_domain(self.domain, user))
|
||||||
|
|
||||||
|
def test_private_register(self):
|
||||||
|
project = create_project(1, self.user1)
|
||||||
|
|
||||||
|
membership = add_membership(project, self.user1, "back")
|
||||||
|
membership.user = None
|
||||||
|
membership.token = "foobar"
|
||||||
|
membership.save()
|
||||||
|
|
||||||
|
# Try register with invalid token
|
||||||
|
with self.assertRaises(exc.NotFound):
|
||||||
|
services.private_register_for_existing_user(self.domain,
|
||||||
|
token="barfoo",
|
||||||
|
username=self.user1.username,
|
||||||
|
password=self.user1.username)
|
||||||
|
|
||||||
|
# Try register with valid token and valid existing user
|
||||||
|
self.assertEqual(membership.user, None)
|
||||||
|
user = services.private_register_for_existing_user(self.domain,
|
||||||
|
token="foobar",
|
||||||
|
username=self.user1.username,
|
||||||
|
password=self.user1.username)
|
||||||
|
|
||||||
|
membership = membership.__class__.objects.get(pk=membership.pk)
|
||||||
|
self.assertEqual(membership.user, user)
|
||||||
|
|
||||||
|
# Try register new user
|
||||||
|
membership.user = None
|
||||||
|
membership.token = "token"
|
||||||
|
membership.save()
|
||||||
|
|
||||||
|
user = services.private_register_for_new_user(self.domain,
|
||||||
|
token="token",
|
||||||
|
username="user2",
|
||||||
|
password="user2",
|
||||||
|
email="user2@bar.ca",
|
||||||
|
first_name="Foo",
|
||||||
|
last_name="Bar")
|
||||||
|
|
||||||
|
|
||||||
|
membership = membership.__class__.objects.get(pk=membership.pk)
|
||||||
|
self.assertEqual(membership.user, user)
|
||||||
|
self.assertTrue(is_user_exists_on_domain(self.domain, user))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TokenAuthenticationBackendTests(test.TestCase):
|
||||||
|
fixtures = ["initial_domains.json",]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user1 = create_user(1)
|
self.user1 = create_user(1)
|
||||||
|
|
||||||
|
@ -45,14 +160,14 @@ class TokenAuthTests(test.TestCase):
|
||||||
self.assertEqual(response.status_code, 401)
|
self.assertEqual(response.status_code, 401)
|
||||||
|
|
||||||
def test_token_auth_02(self):
|
def test_token_auth_02(self):
|
||||||
token = auth.get_token_for_user(self.user1)
|
token = get_token_for_user(self.user1)
|
||||||
response = self.client.get(reverse("test-token-auth"),
|
response = self.client.get(reverse("test-token-auth"),
|
||||||
HTTP_AUTHORIZATION="Bearer {}".format(token))
|
HTTP_AUTHORIZATION="Bearer {}".format(token))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.content, b'"ok"')
|
self.assertEqual(response.content, b'"ok"')
|
||||||
|
|
||||||
|
|
||||||
class RegisterTests(test.TestCase):
|
class RegisterApiTests(test.TestCase):
|
||||||
fixtures = ["initial_domains.json",]
|
fixtures = ["initial_domains.json",]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -79,7 +194,6 @@ class RegisterTests(test.TestCase):
|
||||||
self.assertEqual(DomainMember.objects.filter(domain=self.domain1).count(), 1)
|
self.assertEqual(DomainMember.objects.filter(domain=self.domain1).count(), 1)
|
||||||
self.assertEqual(self.project.memberships.count(), 0)
|
self.assertEqual(self.project.memberships.count(), 0)
|
||||||
|
|
||||||
|
|
||||||
def test_public_register_02(self):
|
def test_public_register_02(self):
|
||||||
data = {
|
data = {
|
||||||
"username": "pepe",
|
"username": "pepe",
|
||||||
|
@ -159,7 +273,6 @@ class RegisterTests(test.TestCase):
|
||||||
self.assertEqual(DomainMember.objects.filter(domain=self.domain1).count(), 0)
|
self.assertEqual(DomainMember.objects.filter(domain=self.domain1).count(), 0)
|
||||||
self.assertEqual(DomainMember.objects.filter(domain=self.domain2).count(), 1)
|
self.assertEqual(DomainMember.objects.filter(domain=self.domain2).count(), 1)
|
||||||
|
|
||||||
|
|
||||||
def _create_invitation(self, email):
|
def _create_invitation(self, email):
|
||||||
token = str(uuid.uuid1())
|
token = str(uuid.uuid1())
|
||||||
membership_model = get_model("projects", "Membership")
|
membership_model = get_model("projects", "Membership")
|
|
@ -1,160 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.contrib.auth import logout, login, authenticate
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.permissions import AllowAny
|
|
||||||
from rest_framework import status, viewsets
|
|
||||||
from taiga.base.decorators import list_route
|
|
||||||
|
|
||||||
from taiga.domains.models import DomainMember
|
|
||||||
from taiga.domains import get_active_domain
|
|
||||||
from taiga.base.users.models import User, Role
|
|
||||||
from taiga.base.users.serializers import UserSerializer
|
|
||||||
from taiga.base import exceptions as exc
|
|
||||||
from taiga.base import auth
|
|
||||||
|
|
||||||
from .serializers import (PublicRegisterSerializer,
|
|
||||||
PrivateRegisterSerializer,
|
|
||||||
PrivateGenericRegisterSerializer,
|
|
||||||
PrivateRegisterExistingSerializer)
|
|
||||||
|
|
||||||
|
|
||||||
class AuthViewSet(viewsets.ViewSet):
|
|
||||||
permission_classes = (AllowAny,)
|
|
||||||
|
|
||||||
def _create_response(self, user):
|
|
||||||
serializer = UserSerializer(user)
|
|
||||||
response_data = serializer.data
|
|
||||||
|
|
||||||
domain = get_active_domain()
|
|
||||||
response_data['is_site_owner'] = domain.user_is_owner(user)
|
|
||||||
response_data['is_site_staff'] = domain.user_is_staff(user)
|
|
||||||
response_data["auth_token"] = auth.get_token_for_user(user)
|
|
||||||
return response_data
|
|
||||||
|
|
||||||
def _create_domain_member(self, user):
|
|
||||||
domain = get_active_domain()
|
|
||||||
|
|
||||||
if domain.members.filter(user=user).count() == 0:
|
|
||||||
domain_member = DomainMember(domain=domain, user=user, email=user.email,
|
|
||||||
is_owner=False, is_staff=False)
|
|
||||||
domain_member.save()
|
|
||||||
|
|
||||||
def _send_public_register_email(self, user):
|
|
||||||
context = {"user": user}
|
|
||||||
|
|
||||||
mbuilder = MagicMailBuilder()
|
|
||||||
email = mbuilder.public_register_user(user.email, context)
|
|
||||||
email.send()
|
|
||||||
|
|
||||||
def _public_register(self, request):
|
|
||||||
if not request.domain.public_register:
|
|
||||||
raise exc.BadRequest(_("Public register is disabled for this domain."))
|
|
||||||
|
|
||||||
serializer = PublicRegisterSerializer(data=request.DATA)
|
|
||||||
if not serializer.is_valid():
|
|
||||||
raise exc.BadRequest(serializer.errors)
|
|
||||||
|
|
||||||
data = serializer.data
|
|
||||||
|
|
||||||
if User.objects.filter(Q(username=data["username"]) | Q(email=data["email"])).exists():
|
|
||||||
raise exc.BadRequest(_("This username or email is already in use."))
|
|
||||||
|
|
||||||
user = User(username=data["username"],
|
|
||||||
first_name=data["first_name"],
|
|
||||||
last_name=data["last_name"],
|
|
||||||
email=data["email"])
|
|
||||||
user.set_password(data["password"])
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
self._create_domain_member(user)
|
|
||||||
#self._send_public_register_email(user)
|
|
||||||
|
|
||||||
response_data = self._create_response(user)
|
|
||||||
return Response(response_data, status=status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
def _send_private_register_email(self, user, **kwargs):
|
|
||||||
context = {"user": user}
|
|
||||||
context.update(kwargs)
|
|
||||||
|
|
||||||
mbuilder = MagicMailBuilder()
|
|
||||||
email = mbuilder.private_register_user(user.email, context)
|
|
||||||
email.send()
|
|
||||||
|
|
||||||
def _private_register(self, request):
|
|
||||||
base_serializer = PrivateGenericRegisterSerializer(data=request.DATA)
|
|
||||||
if not base_serializer.is_valid():
|
|
||||||
raise exc.BadRequest(base_serializer.errors)
|
|
||||||
|
|
||||||
membership_model = get_model("projects", "Membership")
|
|
||||||
try:
|
|
||||||
membership = membership_model.objects.get(token=base_serializer.data["token"])
|
|
||||||
except membership_model.DoesNotExist as e:
|
|
||||||
raise exc.BadRequest(_("Invalid token")) from e
|
|
||||||
|
|
||||||
if base_serializer.data["existing"]:
|
|
||||||
serializer = PrivateRegisterExistingSerializer(data=request.DATA)
|
|
||||||
if not serializer.is_valid():
|
|
||||||
raise exc.BadRequest(serializer.errors)
|
|
||||||
|
|
||||||
user = get_object_or_404(User, username=serializer.data["username"])
|
|
||||||
if not user.check_password(serializer.data["password"]):
|
|
||||||
raise exc.BadRequest({"password": _("Incorrect password")})
|
|
||||||
|
|
||||||
else:
|
|
||||||
serializer = PrivateRegisterSerializer(data=request.DATA)
|
|
||||||
if not serializer.is_valid():
|
|
||||||
raise exc.BadRequest(serializer.errors)
|
|
||||||
|
|
||||||
data = serializer.data
|
|
||||||
|
|
||||||
if User.objects.filter(Q(username=data["username"]) | Q(email=data["email"])).exists():
|
|
||||||
raise exc.BadRequest(_("This username or email is already in use."))
|
|
||||||
|
|
||||||
user = User(username=data["username"],
|
|
||||||
first_name=data["first_name"],
|
|
||||||
last_name=data["last_name"],
|
|
||||||
email=data["email"])
|
|
||||||
user.set_password(data["password"])
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
self._create_domain_member(user)
|
|
||||||
|
|
||||||
membership.user = user
|
|
||||||
membership.save()
|
|
||||||
|
|
||||||
#self._send_private_register_email(user, membership=membership)
|
|
||||||
|
|
||||||
response_data = self._create_response(user)
|
|
||||||
return Response(response_data, status=status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
@list_route(methods=["POST"], permission_classes=[AllowAny])
|
|
||||||
def register(self, request, **kwargs):
|
|
||||||
type = request.DATA.get("type", None)
|
|
||||||
if type == "public":
|
|
||||||
return self._public_register(request)
|
|
||||||
elif type == "private":
|
|
||||||
return self._private_register(request)
|
|
||||||
|
|
||||||
raise exc.BadRequest(_("invalid register type"))
|
|
||||||
|
|
||||||
# Login view: /api/v1/auth
|
|
||||||
def create(self, request, **kwargs):
|
|
||||||
username = request.DATA.get('username', None)
|
|
||||||
password = request.DATA.get('password', None)
|
|
||||||
|
|
||||||
try:
|
|
||||||
user = User.objects.get(username=username)
|
|
||||||
except User.DoesNotExist:
|
|
||||||
raise exc.BadRequest(_("Invalid username or password"))
|
|
||||||
|
|
||||||
if not user.check_password(password):
|
|
||||||
raise exc.BadRequest(_("Invalid username or password"))
|
|
||||||
|
|
||||||
response_data = self._create_response(user)
|
|
||||||
return Response(response_data, status=status.HTTP_200_OK)
|
|
|
@ -14,7 +14,7 @@ from .utils.json import to_json
|
||||||
|
|
||||||
class BaseException(exceptions.APIException):
|
class BaseException(exceptions.APIException):
|
||||||
status_code = status.HTTP_400_BAD_REQUEST
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
default_detail = _('Unexpected error')
|
default_detail = _("Unexpected error")
|
||||||
|
|
||||||
def __init__(self, detail=None):
|
def __init__(self, detail=None):
|
||||||
self.detail = detail or self.default_detail
|
self.detail = detail or self.default_detail
|
||||||
|
@ -26,7 +26,7 @@ class NotFound(BaseException):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
status_code = status.HTTP_404_NOT_FOUND
|
status_code = status.HTTP_404_NOT_FOUND
|
||||||
default_detail = _('Not found.')
|
default_detail = _("Not found.")
|
||||||
|
|
||||||
|
|
||||||
class NotSupported(BaseException):
|
class NotSupported(BaseException):
|
||||||
|
@ -39,7 +39,7 @@ class BadRequest(BaseException):
|
||||||
Exception used on bad arguments detected
|
Exception used on bad arguments detected
|
||||||
on api view.
|
on api view.
|
||||||
"""
|
"""
|
||||||
default_detail = _('Wrong arguments.')
|
default_detail = _("Wrong arguments.")
|
||||||
|
|
||||||
|
|
||||||
class WrongArguments(BaseException):
|
class WrongArguments(BaseException):
|
||||||
|
@ -47,7 +47,11 @@ class WrongArguments(BaseException):
|
||||||
Exception used on bad arguments detected
|
Exception used on bad arguments detected
|
||||||
on service. This is same as `BadRequest`.
|
on service. This is same as `BadRequest`.
|
||||||
"""
|
"""
|
||||||
default_detail = _('Wrong arguments.')
|
default_detail = _("Wrong arguments.")
|
||||||
|
|
||||||
|
|
||||||
|
class RequestValidationError(BaseException):
|
||||||
|
default_detail = _("Data validation error")
|
||||||
|
|
||||||
|
|
||||||
class PermissionDenied(exceptions.PermissionDenied):
|
class PermissionDenied(exceptions.PermissionDenied):
|
||||||
|
@ -58,6 +62,11 @@ class PermissionDenied(exceptions.PermissionDenied):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class IntegrityError(BaseException):
|
||||||
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
|
default_detail = _("Integrity Error for wrong or invalid arguments")
|
||||||
|
|
||||||
|
|
||||||
class PreconditionError(BaseException):
|
class PreconditionError(BaseException):
|
||||||
"""
|
"""
|
||||||
Error raised on precondition method on viewset.
|
Error raised on precondition method on viewset.
|
||||||
|
@ -108,20 +117,20 @@ def exception_handler(exc):
|
||||||
|
|
||||||
if isinstance(exc, exceptions.APIException):
|
if isinstance(exc, exceptions.APIException):
|
||||||
headers = {}
|
headers = {}
|
||||||
if getattr(exc, 'auth_header', None):
|
if getattr(exc, "auth_header", None):
|
||||||
headers['WWW-Authenticate'] = exc.auth_header
|
headers["WWW-Authenticate"] = exc.auth_header
|
||||||
if getattr(exc, 'wait', None):
|
if getattr(exc, "wait", None):
|
||||||
headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait
|
headers["X-Throttle-Wait-Seconds"] = "%d" % exc.wait
|
||||||
|
|
||||||
detail = format_exception(exc)
|
detail = format_exception(exc)
|
||||||
return Response(detail, status=exc.status_code, headers=headers)
|
return Response(detail, status=exc.status_code, headers=headers)
|
||||||
|
|
||||||
elif isinstance(exc, Http404):
|
elif isinstance(exc, Http404):
|
||||||
return Response({'_error_message': _('Not found')},
|
return Response({"_error_message": _("Not found")},
|
||||||
status=status.HTTP_404_NOT_FOUND)
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
elif isinstance(exc, DjangoPermissionDenied):
|
elif isinstance(exc, DjangoPermissionDenied):
|
||||||
return Response({'_error_message': _('Permission denied')},
|
return Response({"_error_message": _("Permission denied")},
|
||||||
status=status.HTTP_403_FORBIDDEN)
|
status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
# Note: Unhandled exceptions will raise a 500 error.
|
# Note: Unhandled exceptions will raise a 500 error.
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from taiga.base.users.serializers import UserSerializer
|
from taiga.users.serializers import UserSerializer
|
||||||
|
|
||||||
from .models import Domain, DomainMember
|
from .models import Domain, DomainMember
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
"""
|
||||||
|
This module contains a domain logic for domains application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db.models.loading import get_model
|
||||||
|
from django.db import transaction as tx
|
||||||
|
from django.db import IntegrityError
|
||||||
|
|
||||||
|
from taiga.base import exceptions as exc
|
||||||
|
|
||||||
|
|
||||||
|
def is_user_exists_on_domain(domain, user) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if user is alredy exists on domain.
|
||||||
|
"""
|
||||||
|
return domain.members.filter(user=user).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def is_public_register_enabled_for_domain(domain) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if a specified domain have public register
|
||||||
|
activated.
|
||||||
|
|
||||||
|
The implementation is very simple but it encapsulates
|
||||||
|
request attribute access into more semantic function
|
||||||
|
call.
|
||||||
|
"""
|
||||||
|
return domain.public_register
|
||||||
|
|
||||||
|
|
||||||
|
@tx.atomic
|
||||||
|
def create_domain_member(domain, user):
|
||||||
|
"""
|
||||||
|
Given a domain and user, add user as member to
|
||||||
|
specified domain.
|
||||||
|
|
||||||
|
:returns: DomainMember
|
||||||
|
"""
|
||||||
|
domain_member_model = get_model("domains", "DomainMember")
|
||||||
|
|
||||||
|
try:
|
||||||
|
domain_member = domain_member_model(domain=domain, user=user,
|
||||||
|
email=user.email, is_owner=False,
|
||||||
|
is_staff=False)
|
||||||
|
domain_member.save()
|
||||||
|
except IntegrityError:
|
||||||
|
raise exc.IntegrityError("User is already member in a site")
|
||||||
|
|
||||||
|
return domain_member
|
|
@ -18,7 +18,7 @@ from django.http import HttpResponse
|
||||||
|
|
||||||
from taiga.projects.tests import create_project
|
from taiga.projects.tests import create_project
|
||||||
from taiga.projects.issues.tests import create_issue
|
from taiga.projects.issues.tests import create_issue
|
||||||
from taiga.base.users.tests import create_user
|
from taiga.users.tests import create_user
|
||||||
|
|
||||||
from . import middleware as mw
|
from . import middleware as mw
|
||||||
from . import changes as ch
|
from . import changes as ch
|
||||||
|
|
|
@ -18,7 +18,7 @@ from django.contrib import admin
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes import generic
|
||||||
|
|
||||||
from taiga.projects.milestones.admin import MilestoneInline
|
from taiga.projects.milestones.admin import MilestoneInline
|
||||||
from taiga.base.users.admin import RoleInline
|
from taiga.users.admin import RoleInline
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
import reversion
|
import reversion
|
||||||
|
|
|
@ -32,8 +32,8 @@ from taiga.base import filters
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.decorators import list_route, detail_route
|
from taiga.base.decorators import list_route, detail_route
|
||||||
from taiga.base.permissions import has_project_perm
|
from taiga.base.permissions import has_project_perm
|
||||||
from taiga.base.api import ModelCrudViewSet, RetrieveModelMixin
|
from taiga.base.api import ModelCrudViewSet, ModelListViewSet, RetrieveModelMixin
|
||||||
from taiga.base.users.models import Role
|
from taiga.users.models import Role
|
||||||
|
|
||||||
from . import serializers
|
from . import serializers
|
||||||
from . import models
|
from . import models
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django import test
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from taiga.base.users.tests import create_user
|
from taiga.users.tests import create_user
|
||||||
from taiga.projects.tests import create_project, add_membership
|
from taiga.projects.tests import create_project, add_membership
|
||||||
from taiga.projects.milestones.tests import create_milestone
|
from taiga.projects.milestones.tests import create_milestone
|
||||||
from taiga.projects.issues.models import Issue
|
from taiga.projects.issues.models import Issue
|
||||||
|
|
|
@ -23,7 +23,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from sampledatahelper.helper import SampleDataHelper
|
from sampledatahelper.helper import SampleDataHelper
|
||||||
|
|
||||||
from taiga.base.users.models import *
|
from taiga.users.models import *
|
||||||
from taiga.projects.models import *
|
from taiga.projects.models import *
|
||||||
from taiga.projects.milestones.models import *
|
from taiga.projects.milestones.models import *
|
||||||
from taiga.projects.userstories.models import *
|
from taiga.projects.userstories.models import *
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django import test
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from taiga.base.users.tests import create_user
|
from taiga.users.tests import create_user
|
||||||
from taiga.projects.tests import create_project, add_membership
|
from taiga.projects.tests import create_project, add_membership
|
||||||
from taiga.projects.milestones.models import Milestone
|
from taiga.projects.milestones.models import Milestone
|
||||||
|
|
||||||
|
|
|
@ -32,14 +32,19 @@ from django.utils import timezone
|
||||||
|
|
||||||
from picklefield.fields import PickledObjectField
|
from picklefield.fields import PickledObjectField
|
||||||
|
|
||||||
|
from taiga.users.models import Role
|
||||||
from taiga.domains.models import DomainMember
|
from taiga.domains.models import DomainMember
|
||||||
from taiga.projects.userstories.models import UserStory
|
from taiga.projects.userstories.models import UserStory
|
||||||
from taiga.base.utils.slug import slugify_uniquely
|
from taiga.base.utils.slug import slugify_uniquely
|
||||||
from taiga.base.utils.dicts import dict_sum
|
from taiga.base.utils.dicts import dict_sum
|
||||||
from taiga.base.users.models import Role
|
|
||||||
|
|
||||||
from . import choices
|
from . import choices
|
||||||
|
|
||||||
|
# FIXME: this should to be on choices module (?)
|
||||||
|
VIDEOCONFERENCES_CHOICES = (
|
||||||
|
('appear-in', 'AppearIn'),
|
||||||
|
('talky', 'Talky'),
|
||||||
|
)
|
||||||
|
|
||||||
class Membership(models.Model):
|
class Membership(models.Model):
|
||||||
# This model stores all project memberships. Also
|
# This model stores all project memberships. Also
|
||||||
|
|
|
@ -14,15 +14,14 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from os import path
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from taiga.base.serializers import PickleField
|
from taiga.base.serializers import PickleField
|
||||||
from taiga.base.users.models import Role
|
from taiga.users.models import Role
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
|
|
||||||
class AttachmentSerializer(serializers.ModelSerializer):
|
class AttachmentSerializer(serializers.ModelSerializer):
|
||||||
name = serializers.SerializerMethodField("get_name")
|
name = serializers.SerializerMethodField("get_name")
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
import reversion
|
import reversion
|
||||||
|
|
||||||
from taiga.base.users.tests import create_user
|
from taiga.users.tests import create_user
|
||||||
from taiga.projects.tests import create_project, add_membership
|
from taiga.projects.tests import create_project, add_membership
|
||||||
from taiga.projects.milestones.tests import create_milestone
|
from taiga.projects.milestones.tests import create_milestone
|
||||||
from taiga.projects.userstories.tests import create_userstory
|
from taiga.projects.userstories.tests import create_userstory
|
||||||
|
|
|
@ -7,10 +7,12 @@ from django.core.urlresolvers import reverse
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.db.models import get_model
|
from django.db.models import get_model
|
||||||
|
|
||||||
from taiga.base.users.tests import create_user
|
from taiga.users.tests import create_user
|
||||||
from taiga.projects.models import Project, Membership
|
from taiga.projects.models import Project, Membership
|
||||||
|
|
||||||
from . import create_project, add_membership
|
from . import create_project
|
||||||
|
from . import add_membership
|
||||||
|
|
||||||
|
|
||||||
class ProfileTestCase(test.TestCase):
|
class ProfileTestCase(test.TestCase):
|
||||||
fixtures = ["initial_domains.json"]
|
fixtures = ["initial_domains.json"]
|
||||||
|
|
|
@ -7,12 +7,13 @@ from django.core.urlresolvers import reverse
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.db.models import get_model
|
from django.db.models import get_model
|
||||||
|
|
||||||
from taiga.base.users.tests import create_user
|
from taiga.users.tests import create_user
|
||||||
from taiga.projects.models import Project, Membership
|
from taiga.projects.models import Project, Membership
|
||||||
from taiga.projects.issues.tests import create_issue
|
from taiga.projects.issues.tests import create_issue
|
||||||
from taiga.projects.tasks.tests import create_task
|
from taiga.projects.tasks.tests import create_task
|
||||||
|
|
||||||
from . import create_project, add_membership
|
from . import create_project
|
||||||
|
from . import add_membership
|
||||||
|
|
||||||
class AllProjectEventsNotificationsTestCase(test.TestCase):
|
class AllProjectEventsNotificationsTestCase(test.TestCase):
|
||||||
fixtures = ["initial_domains.json"]
|
fixtures = ["initial_domains.json"]
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django import test
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from taiga.base.users.tests import create_user
|
from taiga.users.tests import create_user
|
||||||
from taiga.projects.tests import create_project, add_membership
|
from taiga.projects.tests import create_project, add_membership
|
||||||
from taiga.projects.milestones.tests import create_milestone
|
from taiga.projects.milestones.tests import create_milestone
|
||||||
from taiga.projects.userstories.models import UserStory
|
from taiga.projects.userstories.models import UserStory
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django import test
|
from django import test
|
||||||
|
|
||||||
from taiga.base.users.tests import create_user
|
from taiga.users.tests import create_user
|
||||||
from taiga.projects.tests import create_project
|
from taiga.projects.tests import create_project
|
||||||
|
|
||||||
from . import create_userstory
|
|
||||||
from .. import services
|
from .. import services
|
||||||
from .. import models
|
from .. import models
|
||||||
|
from . import create_userstory
|
||||||
|
|
||||||
|
|
||||||
class UserStoriesServiceTestCase(test.TestCase):
|
class UserStoriesServiceTestCase(test.TestCase):
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django import test
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from taiga.base.users.tests import create_user
|
from taiga.users.tests import create_user
|
||||||
from taiga.projects.tests import create_project, add_membership
|
from taiga.projects.tests import create_project, add_membership
|
||||||
from taiga.projects.wiki.models import WikiPage
|
from taiga.projects.wiki.models import WikiPage
|
||||||
|
|
||||||
|
|
|
@ -16,32 +16,37 @@
|
||||||
|
|
||||||
from taiga.base import routers
|
from taiga.base import routers
|
||||||
|
|
||||||
|
from taiga.auth.api import AuthViewSet
|
||||||
|
from taiga.users.api import UsersViewSet, PermissionsViewSet
|
||||||
|
from taiga.base.searches.api import SearchViewSet
|
||||||
|
from taiga.base.resolver.api import ResolverViewSet
|
||||||
|
from taiga.projects.api import (ProjectViewSet, MembershipViewSet, InvitationViewSet,
|
||||||
|
UserStoryStatusViewSet, PointsViewSet, TaskStatusViewSet,
|
||||||
|
IssueStatusViewSet, IssueTypeViewSet, PriorityViewSet,
|
||||||
|
SeverityViewSet, ProjectAdminViewSet, RolesViewSet) #, QuestionStatusViewSet)
|
||||||
|
from taiga.domains.api import DomainViewSet, DomainMembersViewSet
|
||||||
|
from taiga.projects.milestones.api import MilestoneViewSet
|
||||||
|
from taiga.projects.userstories.api import UserStoryViewSet, UserStoryAttachmentViewSet
|
||||||
|
from taiga.projects.tasks.api import TaskViewSet, TaskAttachmentViewSet
|
||||||
|
from taiga.projects.issues.api import IssueViewSet, IssueAttachmentViewSet
|
||||||
|
#from taiga.projects.questions.api import QuestionViewSet, QuestionAttachmentViewSet
|
||||||
|
#from taiga.projects.documents.api import DocumentViewSet, DocumentAttachmentViewSet
|
||||||
|
from taiga.projects.wiki.api import WikiViewSet, WikiAttachmentViewSet
|
||||||
|
|
||||||
|
|
||||||
router = routers.DefaultRouter(trailing_slash=False)
|
router = routers.DefaultRouter(trailing_slash=False)
|
||||||
|
|
||||||
|
# taiga.users
|
||||||
# Users & Auth
|
|
||||||
from taiga.base.users.api import UsersViewSet
|
|
||||||
from taiga.base.users.api import PermissionsViewSet
|
|
||||||
from taiga.base.auth.api import AuthViewSet
|
|
||||||
|
|
||||||
router.register(r"users", UsersViewSet, base_name="users")
|
router.register(r"users", UsersViewSet, base_name="users")
|
||||||
router.register(r"permissions", PermissionsViewSet, base_name="permissions")
|
router.register(r"permissions", PermissionsViewSet, base_name="permissions")
|
||||||
router.register(r"auth", AuthViewSet, base_name="auth")
|
router.register(r"auth", AuthViewSet, base_name="auth")
|
||||||
|
|
||||||
|
|
||||||
# Resolver & Search
|
# Resolver & Search
|
||||||
from taiga.base.resolver.api import ResolverViewSet
|
|
||||||
from taiga.base.searches.api import SearchViewSet
|
|
||||||
|
|
||||||
router.register(r"resolver", ResolverViewSet, base_name="resolver")
|
router.register(r"resolver", ResolverViewSet, base_name="resolver")
|
||||||
router.register(r"search", SearchViewSet, base_name="search")
|
router.register(r"search", SearchViewSet, base_name="search")
|
||||||
|
|
||||||
|
|
||||||
# Domains
|
# Domains
|
||||||
from taiga.domains.api import DomainViewSet
|
|
||||||
from taiga.domains.api import DomainMembersViewSet
|
|
||||||
from taiga.projects.api import ProjectAdminViewSet
|
|
||||||
|
|
||||||
router.register(r"sites", DomainViewSet, base_name="sites")
|
router.register(r"sites", DomainViewSet, base_name="sites")
|
||||||
router.register(r"site-members", DomainMembersViewSet, base_name="site-members")
|
router.register(r"site-members", DomainMembersViewSet, base_name="site-members")
|
||||||
router.register(r"site-projects", ProjectAdminViewSet, base_name="site-projects")
|
router.register(r"site-projects", ProjectAdminViewSet, base_name="site-projects")
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
"""
|
||||||
|
This model contains a domain logic for users application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db.models.loading import get_model
|
||||||
|
from taiga.base import exceptions as exc
|
||||||
|
|
||||||
|
|
||||||
|
def get_and_validate_user(*, username:str, password:str) -> bool:
|
||||||
|
"""
|
||||||
|
Check if user with username exists and specified
|
||||||
|
password matchs well with existing user password.
|
||||||
|
|
||||||
|
if user is valid, user is returned else, corresponding
|
||||||
|
exception is raised.
|
||||||
|
"""
|
||||||
|
|
||||||
|
user_model = get_model("users", "User")
|
||||||
|
qs = user_model.objects.filter(username=username)
|
||||||
|
if len(qs) == 0:
|
||||||
|
raise exc.WrongArguments("Username or password does not matches user.")
|
||||||
|
|
||||||
|
user = qs[0]
|
||||||
|
if not user.check_password(password):
|
||||||
|
raise exc.WrongArguments("Username or password does not matches user.")
|
||||||
|
|
||||||
|
return user
|
Loading…
Reference in New Issue