1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-25 10:11:24 +00:00
seahub/seahub/api2/endpoints/ocm.py
Leo 267e9f525c
Ocm (#4640)
* ocm share local and inter-server api, ocm share page (#4335)

* ocm share

* optimize code

* ocm repo (#4341)

* ocm repo

* hide share if not repo owner

* optimize code

* fix All refresh page, hide upload if is r perm

* update permission check

* update return status code

* update code

* add receive user drop share
2020-09-24 10:57:45 +08:00

518 lines
19 KiB
Python

import logging
import random
import string
import requests
import json
from constance import config
from rest_framework import status
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
from seaserv import seafile_api, ccnet_api
from seahub.utils.repo import get_available_repo_perms, get_repo_owner
from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE
from seahub.ocm.models import OCMShareReceived, OCMShare
from seahub.ocm.settings import ENABLE_OCM, SUPPORTED_OCM_PROTOCOLS, \
OCM_SEAFILE_PROTOCOL, OCM_RESOURCE_TYPE_LIBRARY, OCM_API_VERSION, \
OCM_SHARE_TYPES, OCM_ENDPOINT, OCM_PROVIDER_ID, OCM_NOTIFICATION_TYPE_LIST, \
OCM_NOTIFICATION_SHARE_UNSHARED, OCM_NOTIFICATION_SHARE_DECLINED, OCM_PROTOCOL_URL, \
OCM_NOTIFICATION_URL, OCM_CREATE_SHARE_URL
logger = logging.getLogger(__name__)
# Convert seafile permission to ocm protocol standard permission
SEAFILE_PERMISSION2OCM_PERMISSION = {
PERMISSION_READ: ['read'],
PERMISSION_READ_WRITE: ['read', 'write'],
}
def gen_shared_secret(length=23):
return ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(length))
def get_remote_protocol(url):
response = requests.get(url)
return json.loads(response.text)
def is_valid_url(url):
if not url.startswith('https://') and not url.startswith('http://'):
return False
if not url.endswith('/'):
return False
return True
def check_url_slash(url):
if not url.endswith('/'):
url += '/'
return url
class OCMProtocolView(APIView):
throttle_classes = (UserRateThrottle,)
def get(self, request):
"""
return ocm protocol info to remote server
"""
# TODO
# currently if ENABLE_OCM is False, return 404 as if ocm protocol is not implemented
# ocm protocol is not clear about this, https://github.com/GEANT/OCM-API/pull/37
if not ENABLE_OCM:
error_msg = 'feature not enabled.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
result = {
'enabled': True,
'apiVersion': OCM_API_VERSION,
'endPoint': config.SERVICE_URL + '/' + OCM_ENDPOINT,
'resourceTypes': {
'name': OCM_RESOURCE_TYPE_LIBRARY,
'shareTypes': OCM_SHARE_TYPES,
'protocols': {
OCM_SEAFILE_PROTOCOL: OCM_SEAFILE_PROTOCOL,
}
}
}
return Response(result)
class OCMSharesView(APIView):
throttle_classes = (UserRateThrottle,)
def post(self, request):
"""
create ocm in consumer server
"""
# argument check
share_with = request.data.get('shareWith', '')
if not share_with:
error_msg = 'shareWith invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# curently only support repo share
repo_name = request.data.get('name', '')
if not repo_name:
error_msg = 'name invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
sender = request.data.get('sender', '')
if not sender:
error_msg = 'sender invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
share_type = request.data.get('shareType', '')
if share_type not in OCM_SHARE_TYPES:
error_msg = 'shareType %s invalid.' % share_type
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
resource_type = request.data.get('resourceType', '')
if resource_type != OCM_RESOURCE_TYPE_LIBRARY:
error_msg = 'resourceType %s invalid.' % resource_type
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
provider_id = request.data.get('providerId', '')
if not provider_id:
error_msg = 'providerId invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
"""
other ocm protocol fields currently not used
description = request.data.get('description', '')
owner = request.data.get('owner', '')
ownerDisplayName = request.data.get('ownerDisplayName', '')
senderDisplayName = request.data.get('senderDisplayName', '')
"""
protocol = request.data.get('protocol', '')
if not protocol:
error_msg = 'protocol invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if 'name' not in protocol.keys():
error_msg = 'protocol.name invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if protocol['name'] not in SUPPORTED_OCM_PROTOCOLS:
error_msg = 'protocol %s not support.' % protocol['name']
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if 'options' not in protocol.keys():
error_msg = 'protocol.options invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if 'sharedSecret' not in protocol['options'].keys():
error_msg = 'protocol.options.sharedSecret invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if 'permissions' not in protocol['options'].keys():
error_msg = 'protocol.options.permissions invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if protocol['name'] == OCM_SEAFILE_PROTOCOL:
if 'repoId' not in protocol['options'].keys():
error_msg = 'protocol.options.repoId invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if 'seafileServiceURL' not in protocol['options'].keys():
error_msg = 'protocol.options.seafileServiceURL invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if protocol['name'] == OCM_SEAFILE_PROTOCOL:
shared_secret = protocol['options']['sharedSecret']
permissions = protocol['options']['permissions']
repo_id = protocol['options']['repoId']
from_server_url = protocol['options']['seafileServiceURL']
if OCMShareReceived.objects.filter(
from_user=sender,
to_user=share_with,
from_server_url=from_server_url,
repo_id=repo_id,
repo_name=repo_name,
provider_id=provider_id,
).exists():
return api_error(status.HTTP_400_BAD_REQUEST, 'same share already exists.')
if 'write' in permissions:
permission = PERMISSION_READ_WRITE
else:
permission = PERMISSION_READ
OCMShareReceived.objects.add(
shared_secret=shared_secret,
from_user=sender,
to_user=share_with,
from_server_url=from_server_url,
repo_id=repo_id,
repo_name=repo_name,
permission=permission,
provider_id=provider_id,
)
return Response(request.data, status=status.HTTP_201_CREATED)
class OCMNotificationsView(APIView):
throttle_classes = (UserRateThrottle,)
def post(self, request):
""" Handle notifications from remote server
"""
notification_type = request.data.get('notificationType', '')
if not notification_type:
error_msg = 'notificationType %s invalid.' % notification_type
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if notification_type not in OCM_NOTIFICATION_TYPE_LIST:
error_msg = 'notificationType %s not supportd.' % notification_type
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
resource_type = request.data.get('resourceType', '')
if resource_type != OCM_RESOURCE_TYPE_LIBRARY:
error_msg = 'resourceType %s invalid.' % resource_type
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
notification = request.data.get('notification', '')
if not notification:
error_msg = 'notification invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
shared_secret = notification.get('sharedSecret', '')
if not shared_secret:
error_msg = 'sharedSecret invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if notification_type == OCM_NOTIFICATION_SHARE_UNSHARED:
"""
Provider unshared, then delete ocm_share_received record on Consumer
"""
try:
ocm_share_received = OCMShareReceived.objects.get(shared_secret=shared_secret)
except OCMShareReceived.DoesNotExist:
return Response(request.data)
if ocm_share_received:
try:
ocm_share_received.delete()
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Invernal Server Error')
elif notification_type == OCM_NOTIFICATION_SHARE_DECLINED:
"""
Consumer declined share, then delete ocm_share record on Provider
"""
try:
ocm_share = OCMShare.objects.get(shared_secret=shared_secret)
except OCMShareReceived.DoesNotExist:
return Response(request.data)
if ocm_share:
try:
ocm_share.delete()
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Invernal Server Error')
return Response(request.data)
class OCMSharesPrepareView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def get(self, request):
"""
list ocm shares of request user, filt by repo_id
"""
repo_id = request.GET.get('repo_id', '')
if repo_id:
ocm_shares = OCMShare.objects.filter(repo_id=repo_id, from_user=request.user.username)
else:
ocm_shares = OCMShare.objects.filter(from_user=request.user.username)
ocm_share_list = []
for ocm_share in ocm_shares:
ocm_share_list.append(ocm_share.to_dict())
return Response({'ocm_share_list': ocm_share_list})
def post(self, request):
"""
prepare provider server info for ocm, and send post request to consumer
three step:
1. send get request to remote server, ask if support ocm, and get other info
2. send post request to remote server, remote server create a recored in remote
ocm_share_received table
3. store a recored in local ocm_share table
"""
# argument check
to_user = request.data.get('to_user', '')
if not to_user:
error_msg = 'to_user invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
to_server_url = request.data.get('to_server_url', '').lower().strip()
if not to_server_url or not is_valid_url(to_server_url):
error_msg = 'to_server_url %s invalid.' % to_server_url
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
repo_id = request.data.get('repo_id', '')
if not repo_id:
error_msg = 'repo_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
repo = seafile_api.get_repo(repo_id)
if not repo:
return api_error(status.HTTP_404_NOT_FOUND, 'Library %s not found.' % repo_id)
path = request.data.get('path', '/')
# TODO
# 1. folder check
# 2. encrypted repo check
#
# if seafile_api.get_dir_id_by_path(repo.id, path) is None:
# return api_error(status.HTTP_404_NOT_FOUND, 'Folder %s not found.' % path)
#
# if repo.encrypted and path != '/':
# return api_error(status.HTTP_400_BAD_REQUEST, 'Folder invalid.')
permission = request.data.get('permission', PERMISSION_READ)
if permission not in get_available_repo_perms():
return api_error(status.HTTP_400_BAD_REQUEST, 'permission invalid.')
username = request.user.username
repo_owner = get_repo_owner(request, repo_id)
if repo_owner != username:
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
if OCMShare.objects.filter(
from_user=request.user.username,
to_user=to_user,
to_server_url=to_server_url,
repo_id=repo_id,
repo_name=repo.repo_name,
path=path,
).exists():
return api_error(status.HTTP_400_BAD_REQUEST, 'same share already exists.')
consumer_protocol = get_remote_protocol(to_server_url + OCM_PROTOCOL_URL)
shared_secret = gen_shared_secret()
from_user = username
post_data = {
'shareWith': to_user,
'name': repo.repo_name,
'description': '',
'providerId': OCM_PROVIDER_ID,
'owner': repo_owner,
'sender': from_user,
'ownerDisplayName': email2nickname(repo_owner),
'senderDisplayName': email2nickname(from_user),
'shareType': consumer_protocol['resourceTypes']['shareTypes'][0], # currently only support user type
'resourceType': consumer_protocol['resourceTypes']['name'], # currently only support repo
'protocol': {
'name': OCM_SEAFILE_PROTOCOL,
'options': {
'sharedSecret': shared_secret,
'permissions': SEAFILE_PERMISSION2OCM_PERMISSION[permission],
'repoId': repo_id,
'seafileServiceURL': check_url_slash(config.SERVICE_URL),
},
},
}
url = consumer_protocol['endPoint'] + OCM_CREATE_SHARE_URL
try:
requests.post(url, json=post_data)
except Exception as e:
logging.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
ocm_share = OCMShare.objects.add(
shared_secret=shared_secret,
from_user=request.user.username,
to_user=to_user,
to_server_url=to_server_url,
repo_id=repo_id,
repo_name=repo.repo_name,
path=path,
permission=permission,
)
return Response(ocm_share.to_dict())
class OCMSharePrepareView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def delete(self, request, pk):
"""
delete an share received record
"""
try:
ocm_share = OCMShare.objects.get(pk=pk)
except OCMShareReceived.DoesNotExist:
error_msg = 'OCMShare %s not found.' % pk
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if ocm_share.from_user != request.user.username:
error_msg = 'permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
to_server_url = ocm_share.to_server_url
shared_secret = ocm_share.shared_secret
consumer_protocol = get_remote_protocol(to_server_url + OCM_PROTOCOL_URL)
# send unshare notification to consumer
post_data = {
'notificationType': OCM_NOTIFICATION_SHARE_UNSHARED,
'resourceType': OCM_RESOURCE_TYPE_LIBRARY,
'providerId': OCM_PROVIDER_ID,
'notification': {
'sharedSecret': shared_secret,
'message': '',
},
}
url = consumer_protocol['endPoint'] + OCM_NOTIFICATION_URL
try:
requests.post(url, json=post_data)
except Exception as e:
logging.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
try:
ocm_share.delete()
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
return Response({'success': True})
class OCMSharesReceivedView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def get(self, request):
"""
list ocm shares received
"""
ocm_share_received_list = []
ocm_shares_received = OCMShareReceived.objects.filter(to_user=request.user.username)
for ocm_share_received in ocm_shares_received:
ocm_share_received_list.append(ocm_share_received.to_dict())
return Response({'ocm_share_received_list': ocm_share_received_list})
class OCMShareReceivedView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def delete(self, request, pk):
"""
delete an share received record
"""
try:
ocm_share_received = OCMShareReceived.objects.get(pk=pk)
except OCMShareReceived.DoesNotExist:
error_msg = 'OCMShareReceived %s not found.' % pk
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if ocm_share_received.to_user != request.user.username:
error_msg = 'permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
from_server_url = ocm_share_received.from_server_url
shared_secret = ocm_share_received.shared_secret
provider_protocol = get_remote_protocol(from_server_url + OCM_PROTOCOL_URL)
# send unshare notification to consumer
post_data = {
'notificationType': OCM_NOTIFICATION_SHARE_DECLINED,
'resourceType': OCM_RESOURCE_TYPE_LIBRARY,
'providerId': OCM_PROVIDER_ID,
'notification': {
'sharedSecret': shared_secret,
'message': '',
},
}
url = provider_protocol['endPoint'] + OCM_NOTIFICATION_URL
try:
requests.post(url, json=post_data)
except Exception as e:
logging.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
try:
ocm_share_received.delete()
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
return Response({'success': True})