kopia lustrzana https://github.com/OpenDroneMap/WebODM
Moar tests
rodzic
039df51cc6
commit
a709c8fdf6
|
@ -37,31 +37,3 @@ class ExternalTokenAuth(APIView):
|
|||
except Exception as e:
|
||||
return Response({'error': str(e)})
|
||||
|
||||
# TODO: move to simple http server
|
||||
# class TestExternalAuth(APIView):
|
||||
# permission_classes = (permissions.AllowAny,)
|
||||
# parser_classes = (parsers.JSONParser, parsers.FormParser,)
|
||||
|
||||
# def post(self, request):
|
||||
# print("YO!!!")
|
||||
# if settings.EXTERNAL_AUTH_ENDPOINT == '':
|
||||
# return Response({'message': 'Disabled'})
|
||||
|
||||
# username = request.data.get("username")
|
||||
# password = request.data.get("password")
|
||||
|
||||
# print("HERE", username)
|
||||
|
||||
# if username == "extuser1" and password == "test1234":
|
||||
# return Response({
|
||||
# 'user_id': 100,
|
||||
# 'username': 'extuser1',
|
||||
# 'maxQuota': 500,
|
||||
# 'token': 'test',
|
||||
# 'node': {
|
||||
# 'hostname': 'localhost',
|
||||
# 'port': 4444
|
||||
# }
|
||||
# })
|
||||
# else:
|
||||
# return Response({'message': "Invalid credentials"})
|
|
@ -2,7 +2,7 @@ import requests
|
|||
from django.contrib.auth.backends import ModelBackend
|
||||
from django.contrib.auth.models import User
|
||||
from nodeodm.models import ProcessingNode
|
||||
from webodm.settings import EXTERNAL_AUTH_ENDPOINT
|
||||
from webodm import settings
|
||||
from guardian.shortcuts import assign_perm
|
||||
import logging
|
||||
|
||||
|
@ -15,45 +15,48 @@ def get_user_from_external_auth_response(res):
|
|||
if 'user_id' in res and 'username' in res:
|
||||
try:
|
||||
user = User.objects.get(pk=res['user_id'])
|
||||
|
||||
# Update user info
|
||||
if user.username != res['username']:
|
||||
user.username = res['username']
|
||||
user.save()
|
||||
|
||||
# Update quotas
|
||||
maxQuota = -1
|
||||
if 'maxQuota' in res:
|
||||
maxQuota = res['maxQuota']
|
||||
if 'node' in res and 'limits' in res['node'] and 'maxQuota' in res['node']['limits']:
|
||||
maxQuota = res['node']['limits']['maxQuota']
|
||||
|
||||
if user.profile.quota != maxQuota:
|
||||
user.profile.quota = maxQuota
|
||||
user.save()
|
||||
except User.DoesNotExist:
|
||||
user = User(pk=res['user_id'], username=username)
|
||||
user = User(pk=res['user_id'], username=res['username'])
|
||||
user.save()
|
||||
|
||||
# Update user info
|
||||
if user.username != res['username']:
|
||||
user.username = res['username']
|
||||
user.save()
|
||||
|
||||
maxQuota = -1
|
||||
if 'maxQuota' in res:
|
||||
maxQuota = res['maxQuota']
|
||||
if 'node' in res and 'limits' in res['node'] and 'maxQuota' in res['node']['limits']:
|
||||
maxQuota = res['node']['limits']['maxQuota']
|
||||
|
||||
# Update quotas
|
||||
if user.profile.quota != maxQuota:
|
||||
user.profile.quota = maxQuota
|
||||
user.save()
|
||||
|
||||
# Setup/update processing node
|
||||
if ('api_key' in res or 'token' in res) and 'node' in res:
|
||||
if 'node' in res and 'hostname' in res['node'] and 'port' in res['node']:
|
||||
hostname = res['node']['hostname']
|
||||
port = res['node']['port']
|
||||
token = res['api_key'] if 'api_key' in res else res['token']
|
||||
token = res['node'].get('token', '')
|
||||
|
||||
try:
|
||||
node = ProcessingNode.objects.get(token=token)
|
||||
if node.hostname != hostname or node.port != port:
|
||||
node.hostname = hostname
|
||||
node.port = port
|
||||
# Only add/update if a token is provided, since we use
|
||||
# tokens as unique identifiers for hostname/port updates
|
||||
if token != "":
|
||||
try:
|
||||
node = ProcessingNode.objects.get(token=token)
|
||||
if node.hostname != hostname or node.port != port:
|
||||
node.hostname = hostname
|
||||
node.port = port
|
||||
node.save()
|
||||
|
||||
except ProcessingNode.DoesNotExist:
|
||||
node = ProcessingNode(hostname=hostname, port=port, token=token)
|
||||
node.save()
|
||||
|
||||
except ProcessingNode.DoesNotExist:
|
||||
node = ProcessingNode(hostname=hostname, port=port, token=token)
|
||||
node.save()
|
||||
|
||||
if not user.has_perm('view_processingnode', node):
|
||||
assign_perm('view_processingnode', user, node)
|
||||
if not user.has_perm('view_processingnode', node):
|
||||
assign_perm('view_processingnode', user, node)
|
||||
|
||||
return user
|
||||
else:
|
||||
|
@ -61,11 +64,11 @@ def get_user_from_external_auth_response(res):
|
|||
|
||||
class ExternalBackend(ModelBackend):
|
||||
def authenticate(self, request, username=None, password=None):
|
||||
if EXTERNAL_AUTH_ENDPOINT == "":
|
||||
if settings.EXTERNAL_AUTH_ENDPOINT == "":
|
||||
return None
|
||||
|
||||
try:
|
||||
r = requests.post(EXTERNAL_AUTH_ENDPOINT, {
|
||||
r = requests.post(settings.EXTERNAL_AUTH_ENDPOINT, {
|
||||
'username': username,
|
||||
'password': password
|
||||
}, headers={'Accept': 'application/json'})
|
||||
|
@ -76,7 +79,7 @@ class ExternalBackend(ModelBackend):
|
|||
return None
|
||||
|
||||
def get_user(self, user_id):
|
||||
if EXTERNAL_AUTH_ENDPOINT == "":
|
||||
if settings.EXTERNAL_AUTH_ENDPOINT == "":
|
||||
return None
|
||||
|
||||
try:
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import http.server
|
||||
from http.server import SimpleHTTPRequestHandler
|
||||
import socketserver
|
||||
import sys
|
||||
import threading
|
||||
from time import sleep
|
||||
import json
|
||||
|
||||
class MyHandler(SimpleHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type','text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes("Simple auth server is running", encoding="utf-8"))
|
||||
|
||||
|
||||
def send_error(self, code, error):
|
||||
self.send_json(code, {"error": error})
|
||||
|
||||
def send_json(self, code, data):
|
||||
response = bytes(json.dumps(data), encoding="utf-8")
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type','application/json')
|
||||
self.send_header('Content-length', len(response))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
|
||||
def do_POST(self):
|
||||
if self.path == '/auth':
|
||||
if not 'Content-Length' in self.headers:
|
||||
self.send_error(403, "Missing form data")
|
||||
return
|
||||
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data_str = self.rfile.read(content_length).decode("utf-8")
|
||||
post_data = {}
|
||||
for item in post_data_str.split('&'):
|
||||
k,v = item.split('=')
|
||||
post_data[k] = v
|
||||
|
||||
username = post_data.get("username")
|
||||
password = post_data.get("password")
|
||||
|
||||
print("Login request for " + username)
|
||||
|
||||
if username == "extuser1" and password == "test1234":
|
||||
print("Granted")
|
||||
self.send_json(200, {
|
||||
'user_id': 100,
|
||||
'username': 'extuser1',
|
||||
'maxQuota': 500,
|
||||
'node': {
|
||||
'hostname': 'localhost',
|
||||
'port': 4444,
|
||||
'token': 'test'
|
||||
}
|
||||
})
|
||||
else:
|
||||
print("Unauthorized")
|
||||
return self.send_error(401, "unauthorized")
|
||||
else:
|
||||
self.send_error(404, "not found")
|
||||
|
||||
class WebServer(threading.Thread):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.host = "0.0.0.0"
|
||||
self.port = int(sys.argv[1]) if len(sys.argv) >= 2 else 8080
|
||||
self.ws = socketserver.TCPServer((self.host, self.port), MyHandler)
|
||||
|
||||
def run(self):
|
||||
print("WebServer started at Port:", self.port)
|
||||
self.ws.serve_forever()
|
||||
|
||||
def shutdown(self):
|
||||
# set the two flags needed to shutdown the HTTP server manually
|
||||
# self.ws._BaseServer__is_shut_down.set()
|
||||
# self.ws.__shutdown_request = True
|
||||
|
||||
print('Shutting down server.')
|
||||
# call it anyway, for good measure...
|
||||
self.ws.shutdown()
|
||||
print('Closing server.')
|
||||
self.ws.server_close()
|
||||
self.join()
|
||||
|
||||
if __name__=='__main__':
|
||||
webServer = WebServer()
|
||||
webServer.start()
|
||||
while True:
|
||||
try:
|
||||
sleep(0.5)
|
||||
except KeyboardInterrupt:
|
||||
print('Keyboard Interrupt sent.')
|
||||
webServer.shutdown()
|
||||
exit(0)
|
|
@ -1,8 +1,10 @@
|
|||
from django.contrib.auth.models import User, Group
|
||||
from nodeodm.models import ProcessingNode
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from .classes import BootTestCase
|
||||
from .utils import start_simple_auth_server
|
||||
from webodm import settings
|
||||
|
||||
class TestAuth(BootTestCase):
|
||||
|
@ -19,50 +21,28 @@ class TestAuth(BootTestCase):
|
|||
settings.EXTERNAL_AUTH_ENDPOINT = ''
|
||||
|
||||
# Try to log-in
|
||||
user = client.login(username='extuser1', password='test1234')
|
||||
self.assertFalse(user)
|
||||
ok = client.login(username='extuser1', password='test1234')
|
||||
self.assertFalse(ok)
|
||||
|
||||
# Enable
|
||||
settings.EXTERNAL_AUTH_ENDPOINT = 'http://0.0.0.0:5555'
|
||||
settings.EXTERNAL_AUTH_ENDPOINT = 'http://0.0.0.0:5555/auth'
|
||||
|
||||
# TODO: start simplehttp auth server
|
||||
|
||||
user = client.login(username='extuser1', password='test1234')
|
||||
# self.assertEqual(user.username, 'extuser1')
|
||||
# self.assertEqual(user.id, 100)
|
||||
|
||||
|
||||
# client.login(username="testuser", password="test1234")
|
||||
|
||||
# user = User.objects.get(username="testuser")
|
||||
|
||||
# # Cannot list profiles (not admin)
|
||||
# res = client.get('/api/admin/profiles/')
|
||||
# self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# res = client.get('/api/admin/profiles/%s/' % user.id)
|
||||
# self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# # Cannot update quota deadlines
|
||||
# res = client.post('/api/admin/profiles/%s/update_quota_deadline/' % user.id, data={'hours': 1})
|
||||
# self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# # Admin can
|
||||
# client.login(username="testsuperuser", password="test1234")
|
||||
|
||||
# res = client.get('/api/admin/profiles/')
|
||||
# self.assertEqual(res.status_code, status.HTTP_200_OK)
|
||||
# self.assertTrue(len(res.data) > 0)
|
||||
|
||||
# res = client.get('/api/admin/profiles/%s/' % user.id)
|
||||
# self.assertEqual(res.status_code, status.HTTP_200_OK)
|
||||
# self.assertTrue('quota' in res.data)
|
||||
# self.assertTrue('user' in res.data)
|
||||
|
||||
# # User is the primary key (not profile id)
|
||||
# self.assertEqual(res.data['user'], user.id)
|
||||
|
||||
# # There should be no quota by default
|
||||
# self.assertEqual(res.data['quota'], -1)
|
||||
|
||||
|
||||
with start_simple_auth_server(["5555"]):
|
||||
ok = client.login(username='extuser1', password='invalid')
|
||||
self.assertFalse(ok)
|
||||
self.assertFalse(User.objects.filter(username="extuser1").exists())
|
||||
ok = client.login(username='extuser1', password='test1234')
|
||||
self.assertTrue(ok)
|
||||
user = User.objects.get(username="extuser1")
|
||||
self.assertEqual(user.id, 100)
|
||||
self.assertEqual(user.profile.quota, 500)
|
||||
pnode = ProcessingNode.objects.get(token='test')
|
||||
self.assertEqual(pnode.hostname, 'localhost')
|
||||
self.assertEqual(pnode.port, 4444)
|
||||
self.assertTrue(user.has_perm('view_processingnode', pnode))
|
||||
self.assertFalse(user.has_perm('delete_processingnode', pnode))
|
||||
self.assertFalse(user.has_perm('change_processingnode', pnode))
|
||||
|
||||
# Re-test login
|
||||
ok = client.login(username='extuser1', password='test1234')
|
||||
self.assertTrue(ok)
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
from django.contrib.auth.models import User, Group
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
from app.models import Task, Project
|
||||
from .classes import BootTestCase
|
||||
|
||||
class TestQuota(BootTestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_quota(self):
|
||||
c = APIClient()
|
||||
c.login(username="testuser", password="test1234")
|
||||
|
||||
user = User.objects.get(username="testuser")
|
||||
self.assertEqual(user.profile.quota, -1)
|
||||
|
||||
# There should be no quota panel
|
||||
res = c.get('/dashboard/', follow=True)
|
||||
body = res.content.decode("utf-8")
|
||||
|
||||
# There should be no quota panel
|
||||
self.assertFalse('<div class="info-item quotas">' in body)
|
||||
|
||||
user.profile.quota = 2000
|
||||
user.save()
|
||||
|
||||
res = c.get('/dashboard/', follow=True)
|
||||
body = res.content.decode("utf-8")
|
||||
|
||||
# There should be a quota panel
|
||||
self.assertTrue('<div class="info-item quotas">' in body)
|
||||
|
||||
# There should be no warning
|
||||
self.assertFalse("disk quota is being exceeded" in body)
|
||||
|
||||
self.assertEqual(user.profile.used_quota(), 0)
|
||||
self.assertEqual(user.profile.used_quota_cached(), 0)
|
||||
|
||||
# Create a task with size
|
||||
p = Project.objects.create(owner=user, name='Test')
|
||||
p.save()
|
||||
t = Task.objects.create(project=p, name='Test', size=2005)
|
||||
t.save()
|
||||
|
||||
# Simulate call to task.update_size which calls clear_used_quota_cache
|
||||
user.profile.clear_used_quota_cache()
|
||||
|
||||
self.assertTrue(user.profile.has_exceeded_quota())
|
||||
self.assertTrue(user.profile.has_exceeded_quota_cached())
|
||||
|
||||
res = c.get('/dashboard/', follow=True)
|
||||
body = res.content.decode("utf-8")
|
||||
|
||||
# self.assertTrue("disk quota is being exceeded" in body)
|
|
@ -25,6 +25,16 @@ def start_processing_node(args = []):
|
|||
node_odm.terminate()
|
||||
time.sleep(1) # Wait for the server to stop
|
||||
|
||||
@contextmanager
|
||||
def start_simple_auth_server(args = []):
|
||||
current_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
s = subprocess.Popen(['python', 'simple_auth_server.py'] + args, shell=False,
|
||||
cwd=os.path.join(current_dir, "scripts"))
|
||||
time.sleep(2) # Wait for the server to launch
|
||||
yield s
|
||||
s.terminate()
|
||||
time.sleep(1) # Wait for the server to stop
|
||||
|
||||
# We need to clear previous media_root content
|
||||
# This points to the test directory, but just in case
|
||||
# we double check that the directory is indeed a test directory
|
||||
|
|
|
@ -15,7 +15,9 @@ from pyodm import Node
|
|||
from pyodm import exceptions
|
||||
from django.db.models import signals
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger('app.logger')
|
||||
|
||||
class ProcessingNode(models.Model):
|
||||
hostname = models.CharField(verbose_name=_("Hostname"), max_length=255, help_text=_("Hostname or IP address where the node is located (can be an internal hostname as well). If you are using Docker, this is never 127.0.0.1 or localhost. Find the IP address of your host machine by running ifconfig on Linux or by checking your network settings."))
|
||||
|
@ -197,6 +199,8 @@ def auto_update_node_info(sender, instance, created, **kwargs):
|
|||
instance.update_node_info()
|
||||
except exceptions.OdmError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning("auto_update_node_info: " + str(e))
|
||||
|
||||
class ProcessingNodeUserObjectPermission(UserObjectPermissionBase):
|
||||
content_object = models.ForeignKey(ProcessingNode, on_delete=models.CASCADE)
|
||||
|
|
|
@ -401,7 +401,7 @@ QUOTA_EXCEEDED_GRACE_PERIOD = 8
|
|||
|
||||
if TESTING or FLUSHING:
|
||||
CELERY_TASK_ALWAYS_EAGER = True
|
||||
EXTERNAL_AUTH_ENDPOINT = '/_test-external-auth'
|
||||
EXTERNAL_AUTH_ENDPOINT = 'http://0.0.0.0:5555/auth'
|
||||
|
||||
try:
|
||||
from .local_settings import *
|
||||
|
|
Ładowanie…
Reference in New Issue