cvat-ui in docker (serve using nginx) (#658)

main
Nikita Manovich 7 years ago committed by GitHub
parent eaede02824
commit 8359db3580
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6,5 +6,6 @@
/.vscode
/db.sqlite3
/keys
package-lock.json
node_modules
**/node_modules
cvat-ui
cvat-canvas

@ -0,0 +1,2 @@
build
node_modules

@ -6,7 +6,4 @@ REACT_APP_API_PORT=7000
REACT_APP_API_HOST_URL=${REACT_APP_API_PROTOCOL}://${REACT_APP_API_HOST}:${REACT_APP_API_PORT}
REACT_APP_API_FULL_URL=${REACT_APP_API_PROTOCOL}://${REACT_APP_API_HOST}:${REACT_APP_API_PORT}/api/v1
REACT_APP_LOGIN=admin
REACT_APP_PASSWORD=admin
SKIP_PREFLIGHT_CHECK=true

@ -0,0 +1,9 @@
REACT_APP_VERSION=${npm_package_version}
REACT_APP_API_PROTOCOL=http
REACT_APP_API_HOST=localhost
REACT_APP_API_PORT=8080
REACT_APP_API_HOST_URL=${REACT_APP_API_PROTOCOL}://${REACT_APP_API_HOST}:${REACT_APP_API_PORT}
REACT_APP_API_FULL_URL=${REACT_APP_API_PROTOCOL}://${REACT_APP_API_HOST}:${REACT_APP_API_PORT}/api/v1
SKIP_PREFLIGHT_CHECK=true

@ -0,0 +1,36 @@
FROM ubuntu:18.04 AS cvat-ui
ARG http_proxy
ARG https_proxy
ARG no_proxy
ARG socks_proxy
ENV TERM=xterm \
http_proxy=${http_proxy} \
https_proxy=${https_proxy} \
no_proxy=${no_proxy} \
socks_proxy=${socks_proxy}
ENV LANG='C.UTF-8' \
LC_ALL='C.UTF-8'
# Install necessary apt packages
RUN apt update && apt install -yq nodejs npm curl && \
npm install -g n && n 10.16.3
# Create output directory
RUN mkdir /tmp/cvat-ui
WORKDIR /tmp/cvat-ui/
# Install dependencies
COPY package*.json /tmp/cvat-ui/
RUN npm install
# Build source code
COPY . /tmp/cvat-ui/
RUN mv .env.production .env && npm run build
FROM nginx
# Replace default.conf configuration to remove unnecessary rules
COPY react_nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=cvat-ui /tmp/cvat-ui/build /usr/share/nginx/html/

@ -24,7 +24,8 @@
"react-redux": "^7.1.0",
"react-router-dom": "^5.0.1",
"react-scripts": "3.0.1",
"redux": "^4.0.3",
"react-scripts-ts": "^3.1.0",
"redux": "^4.0.4",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0",
"source-map-explorer": "^1.8.0",

@ -0,0 +1,7 @@
server {
root /usr/share/nginx/html;
# Any route that doesn't have a file extension (e.g. /devices)
location / {
try_files $uri $uri/ /index.html;
}
}

@ -39,6 +39,10 @@ const ProtectedRoute = ({ component: Component, ...rest }: any) => {
};
class App extends PureComponent<any, any> {
componentDidMount() {
(window as any).cvat.config.backendAPI = process.env.REACT_APP_API_FULL_URL;
}
render() {
return(
<Router>

@ -8,10 +8,12 @@ from rest_auth.views import (
LoginView, LogoutView, PasswordChangeView,
PasswordResetView, PasswordResetConfirmView)
from rest_auth.registration.views import RegisterView
from .views import SigningView
urlpatterns = [
path('login', LoginView.as_view(), name='rest_login'),
path('logout', LogoutView.as_view(), name='rest_logout'),
path('signing', SigningView.as_view(), name='signing')
]
if settings.DJANGO_AUTH_TYPE == 'BASIC':

@ -7,7 +7,10 @@ from django.conf import settings
from django.db.models import Q
import rules
from . import AUTH_ROLE
from . import signature
from rest_framework.permissions import BasePermission
from django.core import signing
from rest_framework import authentication, exceptions
def register_signals():
from django.db.models.signals import post_migrate, post_save
@ -30,6 +33,30 @@ def register_signals():
django_auth_ldap.backend.populate_user.connect(create_user)
class SignatureAuthentication(authentication.BaseAuthentication):
"""
Authentication backend for signed URLs.
"""
def authenticate(self, request):
"""
Returns authenticated user if URL signature is valid.
"""
signer = signature.Signer()
sign = request.query_params.get(signature.QUERY_PARAM)
if not sign:
return
try:
user = signer.unsign(sign, request.build_absolute_uri())
except signing.SignatureExpired:
raise exceptions.AuthenticationFailed('This URL has expired.')
except signing.BadSignature:
raise exceptions.AuthenticationFailed('Invalid signature.')
if not user.is_active:
raise exceptions.AuthenticationFailed('User inactive or deleted.')
return (user, None)
# AUTH PREDICATES
has_admin_role = rules.is_group_member(str(AUTH_ROLE.ADMIN))
has_user_role = rules.is_group_member(str(AUTH_ROLE.USER))

@ -0,0 +1,44 @@
from django.contrib.auth import get_user_model
from django.core import signing
from furl import furl
import hashlib
QUERY_PARAM = 'sign'
MAX_AGE = 30
# Got implementation ideas in https://github.com/marcgibbons/drf_signed_auth
class Signer:
@classmethod
def get_salt(cls, url):
normalized_url = furl(url).remove(QUERY_PARAM).url.encode('utf-8')
salt = hashlib.sha256(normalized_url).hexdigest()
return salt
def sign(self, user, url):
"""
Create a signature for a user object.
"""
data = {
'user_id': user.pk,
'username': user.get_username()
}
return signing.dumps(data, salt=self.get_salt(url))
def unsign(self, signature, url):
"""
Return a user object for a valid signature.
"""
User = get_user_model()
data = signing.loads(signature, salt=self.get_salt(url), max_age=MAX_AGE)
if not isinstance(data, dict):
raise signing.BadSignature()
try:
return User.objects.get(**{
'pk': data.get('user_id'),
User.USERNAME_FIELD: data.get('username')
})
except User.DoesNotExist:
raise signing.BadSignature()

@ -5,8 +5,13 @@
from django.shortcuts import render, redirect
from django.conf import settings
from django.contrib.auth import login, authenticate
from rest_framework import views
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
from furl import furl
from . import forms
from . import signature
def register_user(request):
if request.method == 'POST':
@ -21,3 +26,16 @@ def register_user(request):
else:
form = forms.NewUserForm()
return render(request, 'register.html', {'form': form})
class SigningView(views.APIView):
def post(self, request):
url = request.data.get('url')
if not url:
raise ValidationError('Please provide `url` parameter')
signer = signature.Signer()
url = self.request.build_absolute_uri(url)
sign = signer.sign(self.request.user, url)
url = furl(url).add({signature.QUERY_PARAM: sign}).url
return Response(url)

@ -40,3 +40,5 @@ cython==0.29.13
matplotlib==3.0.3
scikit-image>=0.14.0
tensorflow==1.12.3
django-cors-headers==3.0.2
furl==2.0.0

@ -9,6 +9,5 @@ pylint-plugin-utils==0.2.6
rope==0.11
wrapt==1.10.11
django-extensions==2.0.6
Werkzeug==0.14.1
Werkzeug==0.15.3
snakeviz==0.4.2
django-cors-headers==3.0.2

@ -111,6 +111,7 @@ INSTALLED_APPS = [
'django.contrib.sites',
'allauth',
'allauth.account',
'corsheaders',
'allauth.socialaccount',
'rest_auth.registration'
]
@ -123,6 +124,7 @@ REST_FRAMEWORK = {
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'cvat.apps.authentication.auth.SignatureAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
],
@ -173,8 +175,18 @@ MIDDLEWARE = [
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'dj_pagination.middleware.PaginationMiddleware',
'corsheaders.middleware.CorsMiddleware',
]
# Cross-Origin Resource Sharing settings for CVAT UI
UI_SCHEME = os.environ.get('UI_SCHEME', 'http')
UI_HOST = os.environ.get('UI_HOST', 'localhost')
UI_PORT = os.environ.get('UI_PORT', '3000')
CORS_ALLOW_CREDENTIALS = True
CSRF_TRUSTED_ORIGINS = [UI_HOST]
UI_URL = '{}://{}:{}'.format(UI_SCHEME, UI_HOST, UI_PORT)
CORS_ORIGIN_WHITELIST = [UI_URL]
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',

@ -9,20 +9,6 @@ DEBUG = True
INSTALLED_APPS += [
'django_extensions',
'corsheaders',
]
MIDDLEWARE += [
'corsheaders.middleware.CorsMiddleware',
]
CORS_ALLOW_CREDENTIALS = True
CSRF_TRUSTED_ORIGINS = [
'http://localhost:3000'
]
CORS_ORIGIN_WHITELIST = [
"http://localhost:3000",
]
ALLOWED_HOSTS.append('testserver')

@ -53,12 +53,34 @@ services:
OPENVINO_TOOLKIT: "no"
environment:
DJANGO_MODWSGI_EXTRA_ARGS: ""
UI_PORT: 9080
volumes:
- cvat_data:/home/django/data
- cvat_keys:/home/django/keys
- cvat_logs:/home/django/logs
- cvat_models:/home/django/models
cvat_ui:
container_name: cvat_ui
image: nginx
build:
context: cvat-ui
args:
http_proxy:
https_proxy:
no_proxy:
socks_proxy:
dockerfile: Dockerfile
networks:
default:
aliases:
- ui
depends_on:
- cvat
ports:
- "9080:80"
volumes:
cvat_db:
cvat_data:

Loading…
Cancel
Save