Ability to configure user agreements for the register user form (#1464)
parent
fc2f02a406
commit
380be58edc
@ -0,0 +1,38 @@
|
||||
// Copyright (C) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
|
||||
import getCore from 'cvat-core-wrapper';
|
||||
import { UserAgreement } from 'reducers/interfaces'
|
||||
|
||||
const core = getCore();
|
||||
|
||||
export enum UserAgreementsActionTypes {
|
||||
GET_USER_AGREEMENTS = 'GET_USER_AGREEMENTS',
|
||||
GET_USER_AGREEMENTS_SUCCESS = 'GET_USER_AGREEMENTS_SUCCESS',
|
||||
GET_USER_AGREEMENTS_FAILED = 'GET_USER_AGREEMENTS_FAILED',
|
||||
}
|
||||
|
||||
const userAgreementsActions = {
|
||||
getUserAgreements: () => createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS),
|
||||
getUserAgreementsSuccess: (userAgreements: UserAgreement[]) =>
|
||||
createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_SUCCESS, userAgreements),
|
||||
getUserAgreementsFailed: (error: any) =>
|
||||
createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_FAILED, { error }),
|
||||
};
|
||||
|
||||
export type UserAgreementsActions = ActionUnion<typeof userAgreementsActions>;
|
||||
|
||||
export const getUserAgreementsAsync = (): ThunkAction => async (dispatch): Promise<void> => {
|
||||
dispatch(userAgreementsActions.getUserAgreements());
|
||||
|
||||
try {
|
||||
const userAgreements = await core.server.userAgreements();
|
||||
dispatch(
|
||||
userAgreementsActions.getUserAgreementsSuccess(userAgreements),
|
||||
);
|
||||
} catch (error) {
|
||||
dispatch(userAgreementsActions.getUserAgreementsFailed(error));
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { boundariesActions, BoundariesActionTypes } from 'actions/boundaries-actions';
|
||||
import { UserAgreementsActions, UserAgreementsActionTypes } from 'actions/useragreements-actions';
|
||||
import { AuthActions, AuthActionTypes } from 'actions/auth-actions';
|
||||
import { UserAgreementsState } from './interfaces';
|
||||
|
||||
const defaultState: UserAgreementsState = {
|
||||
list: [],
|
||||
fetching: false,
|
||||
initialized: false,
|
||||
};
|
||||
|
||||
export default function (
|
||||
state: UserAgreementsState = defaultState,
|
||||
action: UserAgreementsActions | AuthActions | boundariesActions,
|
||||
): UserAgreementsState {
|
||||
switch (action.type) {
|
||||
case UserAgreementsActionTypes.GET_USER_AGREEMENTS: {
|
||||
return {
|
||||
...state,
|
||||
fetching: true,
|
||||
initialized: false,
|
||||
};
|
||||
}
|
||||
case UserAgreementsActionTypes.GET_USER_AGREEMENTS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
fetching: false,
|
||||
initialized: true,
|
||||
list: action.payload,
|
||||
};
|
||||
case UserAgreementsActionTypes.GET_USER_AGREEMENTS_FAILED:
|
||||
return {
|
||||
...state,
|
||||
fetching: false,
|
||||
initialized: true,
|
||||
};
|
||||
case AuthActionTypes.LOGOUT_SUCCESS:
|
||||
case BoundariesActionTypes.RESET_AFTER_ERROR: {
|
||||
return {
|
||||
...defaultState,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
# Copyright (C) 2020 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
@ -0,0 +1,10 @@
|
||||
# Copyright (C) 2020 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RestrictionsConfig(AppConfig):
|
||||
name = 'cvat.apps.restrictions'
|
||||
@ -0,0 +1,3 @@
|
||||
# Copyright (C) 2020 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
@ -0,0 +1,43 @@
|
||||
# Copyright (C) 2020 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
from rest_framework import serializers
|
||||
from django.conf import settings
|
||||
|
||||
from cvat.apps.authentication.serializers import RegisterSerializerEx
|
||||
|
||||
class UserAgreementSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(max_length=256)
|
||||
display_text = serializers.CharField(max_length=2048, default='')
|
||||
url = serializers.CharField(max_length=2048, default='')
|
||||
required = serializers.BooleanField(default=False)
|
||||
value = serializers.BooleanField(default=False)
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def to_representation(self, instance):
|
||||
instance_ = instance.copy()
|
||||
instance_['displayText'] = instance_.pop('display_text')
|
||||
return instance_
|
||||
|
||||
class RestrictedRegisterSerializer(RegisterSerializerEx):
|
||||
confirmations = UserAgreementSerializer(many=True, required=False)
|
||||
|
||||
def validate(self, data):
|
||||
validated_data = super().validate(data)
|
||||
server_user_agreements = settings.RESTRICTIONS['user_agreements']
|
||||
for server_agreement in server_user_agreements:
|
||||
if server_agreement['required']:
|
||||
is_confirmed = False
|
||||
for confirmation in validated_data['confirmations']:
|
||||
if confirmation['name'] == server_agreement['name'] \
|
||||
and confirmation['value']:
|
||||
is_confirmed = True
|
||||
|
||||
if not is_confirmed:
|
||||
raise serializers.ValidationError(
|
||||
'Agreement {} must be accepted'.format(server_agreement['name'])
|
||||
)
|
||||
|
||||
return validated_data
|
||||
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<body>
|
||||
<h1>CVAT terms of use</h1>
|
||||
<p>
|
||||
PLEASE READ THE FOLLOWING TERMS CAREFULLY. IF YOU CLICK TO ACCEPT THIS AGREEMENT, DOWNLOAD,
|
||||
OR INSTALL THE CVAT SOFTWARE, YOU ARE AGREEING TO BE LEGALLY BOUND BY THIS AGREEMENT
|
||||
IN ADDITION TO ANY OTHER TERMS PROVIDED. IF YOU DO NOT AGREE TO THESE TERMS, DO NOT USE
|
||||
THE CVAT SOFTWARE AND DESTROY ALL COPIES IN YOUR POSSESSION.
|
||||
</p>
|
||||
<p>
|
||||
You understand and acknowledge that you are responsible for your use of the CVAT Software
|
||||
and any content which you may alter with use of the CVAT Software.
|
||||
</p>
|
||||
<p>
|
||||
You further understand and acknowledge that any content you may elect to use may be subject to privacy,
|
||||
data and intellectual property rights and that you are responsible to your compliance with such laws and regulations.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,109 @@
|
||||
# Copyright (C) 2020 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
from rest_framework.test import APITestCase, APIClient
|
||||
from rest_framework import status
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class UserAgreementsTest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user_agreements = settings.RESTRICTIONS['user_agreements']
|
||||
settings.RESTRICTIONS['user_agreements'] = [
|
||||
{
|
||||
'name': 'agreement_1',
|
||||
'display_text': 'some display text 1',
|
||||
'url': 'https://example.com',
|
||||
'required': True,
|
||||
},
|
||||
{
|
||||
'name': 'agreement_2',
|
||||
'display_text': 'some display text 2',
|
||||
'url': 'https://example2.com',
|
||||
'required': True,
|
||||
},
|
||||
{
|
||||
'name': 'agreement_3',
|
||||
'display_text': 'some display text 3',
|
||||
'url': 'https://example3.com',
|
||||
'required': False,
|
||||
},
|
||||
]
|
||||
|
||||
def tearDown(self):
|
||||
settings.RESTRICTIONS['user_agreements'] = self.user_agreements
|
||||
|
||||
def _get_user_agreements(self):
|
||||
response = self.client.get('/api/v1/restrictions/user-agreements')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
for agreements in response.data:
|
||||
assert 'name' in agreements, agreements['name']
|
||||
assert 'displayText' in agreements
|
||||
assert 'required' in agreements
|
||||
return response.data
|
||||
|
||||
def _register_user(self, data):
|
||||
response = self.client.post('/api/v1/auth/register', data=data, format="json")
|
||||
return response
|
||||
|
||||
|
||||
def test_user_agreements(self):
|
||||
self._get_user_agreements()
|
||||
|
||||
def test_register_user_with_required_confirmations(self):
|
||||
agreements = self._get_user_agreements()
|
||||
data = {
|
||||
'username': 'some_username1',
|
||||
'first_name': 'some first name 1',
|
||||
'last_name': 'some last name 1',
|
||||
'email': 'user1@example.com',
|
||||
'password1': 'FnvL4YdF24NAmnQ8',
|
||||
'password2': 'FnvL4YdF24NAmnQ8',
|
||||
'confirmations':[],
|
||||
}
|
||||
for agreement in agreements:
|
||||
if agreement['required']:
|
||||
data['confirmations'].append({
|
||||
'name': agreement['name'],
|
||||
'value': True,
|
||||
})
|
||||
response = self._register_user(data)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
|
||||
def test_register_user_without_confirmations(self):
|
||||
data = {
|
||||
'username': 'some_username2',
|
||||
'first_name': 'some first name 2',
|
||||
'last_name': 'some last name 2',
|
||||
'email': 'user2@example.com',
|
||||
'password1': 'FnvL4YdF24NAmnQ8',
|
||||
'password2': 'FnvL4YdF24NAmnQ8',
|
||||
'confirmations':[],
|
||||
}
|
||||
|
||||
response = self._register_user(data)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
def test_register_user_with_all_confirmations(self):
|
||||
agreements = self._get_user_agreements()
|
||||
data = {
|
||||
'username': 'some_username3',
|
||||
'first_name': 'some first name 3',
|
||||
'last_name': 'some last name 3',
|
||||
'email': 'user3@example.com',
|
||||
'password1': 'FnvL4YdF24NAmnQ8',
|
||||
'password2': 'FnvL4YdF24NAmnQ8',
|
||||
'confirmations':[],
|
||||
}
|
||||
|
||||
for agreement in agreements:
|
||||
data['confirmations'].append({
|
||||
'name': agreement['name'],
|
||||
'value': True,
|
||||
})
|
||||
|
||||
response = self._register_user(data)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
@ -0,0 +1,41 @@
|
||||
# Copyright (C) 2020 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.conf import settings
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.renderers import TemplateHTMLRenderer
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
|
||||
from cvat.apps.restrictions.serializers import UserAgreementSerializer
|
||||
|
||||
class RestrictionsViewSet(viewsets.ViewSet):
|
||||
serializer_class = None
|
||||
permission_classes = [AllowAny]
|
||||
authentication_classes = []
|
||||
|
||||
# To get nice documentation about ServerViewSet actions it is necessary
|
||||
# to implement the method. By default, ViewSet doesn't provide it.
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@swagger_auto_schema(
|
||||
method='get',
|
||||
operation_summary='Method provides user agreements that the user must accept to register',
|
||||
responses={'200': UserAgreementSerializer})
|
||||
@action(detail=False, methods=['GET'], serializer_class=UserAgreementSerializer, url_path='user-agreements')
|
||||
def user_agreements(request):
|
||||
user_agreements = settings.RESTRICTIONS['user_agreements']
|
||||
serializer = UserAgreementSerializer(data=user_agreements, many=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return Response(data=serializer.data)
|
||||
|
||||
@staticmethod
|
||||
@action(detail=False, methods=['GET'], renderer_classes=(TemplateHTMLRenderer,),
|
||||
url_path='terms-of-use')
|
||||
def terms_of_use(request):
|
||||
return Response(template_name='restrictions/terms_of_use.html')
|
||||
Loading…
Reference in New Issue