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