1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-18 16:36:15 +00:00

Merge pull request #1601 from haiwen/share-link

Share link
This commit is contained in:
xiez
2017-06-17 11:07:02 +08:00
committed by GitHub
11 changed files with 230 additions and 14 deletions

View File

@@ -70,7 +70,7 @@ def get_share_link_info(fileshare):
data['ctime'] = ctime
data['expire_date'] = expire_date
data['is_expired'] = fileshare.is_expired()
data['permissions'] = fileshare.get_permissions()
return data
class ShareLinks(APIView):
@@ -91,6 +91,36 @@ class ShareLinks(APIView):
return (None, None)
def _check_permissions_arg(self, request):
permissions = request.data.get('permissions', None)
if permissions is not None:
if isinstance(permissions, dict):
perm_dict = permissions
elif isinstance(permissions, basestring):
import json
try:
perm_dict = json.loads(permissions)
except ValueError:
error_msg = 'permissions invalid: %s' % permissions
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
else:
error_msg = 'permissions invalid: %s' % permissions
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
else:
perm_dict = None
can_preview = True
can_download = True
if perm_dict is not None:
can_preview = perm_dict.get('can_preview', True)
can_download = perm_dict.get('can_download', True)
if can_preview and can_download:
perm = FileShare.PERM_VIEW_DL
if can_preview and not can_download:
perm = FileShare.PERM_VIEW_ONLY
return perm
def get(self, request):
""" Get all share links of a user.
@@ -188,6 +218,8 @@ class ShareLinks(APIView):
else:
expire_date = timezone.now() + relativedelta(days=expire_days)
perm = self._check_permissions_arg(request)
# resource check
repo = seafile_api.get_repo(repo_id)
if not repo:
@@ -221,13 +253,15 @@ class ShareLinks(APIView):
fs = FileShare.objects.get_file_link_by_path(username, repo_id, path)
if not fs:
fs = FileShare.objects.create_file_link(username, repo_id, path,
password, expire_date)
password, expire_date,
permission=perm)
elif s_type == 'd':
fs = FileShare.objects.get_dir_link_by_path(username, repo_id, path)
if not fs:
fs = FileShare.objects.create_dir_link(username, repo_id, path,
password, expire_date)
password, expire_date,
permission=perm)
if is_org_context(request):
org_id = request.user.org.org_id

View File

@@ -201,6 +201,19 @@ class FileShare(models.Model):
else:
return '%s/d/%s/' % (service_url, self.token)
def get_permissions(self):
perm_dict = {}
if self.permission == FileShare.PERM_VIEW_DL:
perm_dict['can_preview'] = True
perm_dict['can_download'] = True
elif self.permission == FileShare.PERM_VIEW_ONLY:
perm_dict['can_preview'] = True
perm_dict['can_download'] = False
else:
assert False
return perm_dict
class OrgFileShareManager(models.Manager):
def set_org_file_share(self, org_id, file_share):
"""Set a share link as org share link.

View File

@@ -699,6 +699,7 @@
<% if (!repo_encrypted && can_generate_share_link) { %>
<div id="download-link-share" class="tabs-panel">
<form id="generate-download-link-form" action="" class="hide">
<label class="checkbox-label">
<input type="checkbox" name="use_passwd" class="vam" />
<span class="checkbox-option vam">{% trans "Add password protection"%}</span>
@@ -713,6 +714,7 @@
<label for="passwd-again">{% trans "Password again" %}</label><br />
<input type="password" name="password_again" class="input" id="passwd-again" />
</div>
<label class="checkbox-label">
<input type="checkbox" name="set_expiration" class="vam" />
<span class="checkbox-option vam">{% trans "Add auto expiration"%}</span>
@@ -721,6 +723,14 @@
<label for="expire-days">{% trans "Days" %}</label><br />
<input type="text" name="expire_days" class="input" id="expire-days" />
</div>
<% if (app.pageOptions.is_pro) { %>
<label class="checkbox-label">
<input type="checkbox" name="preview_only" class="vam" />
<span class="checkbox-option vam">{% trans "Online preview only" %}</span>
</label>
<% } %>
<p class="error hide"></p>
<input type="submit" value="{% trans 'Generate'%}" />
</form>

View File

@@ -5,6 +5,18 @@
{% block extra_style %}
{% include 'snippets/file_view_style.html' %}
<style type="text/css">
{% if not permissions.can_download %}
body {
user-select:none;
}
@media print {
html {
display:none;
}
}
{% endif %}
</style>
{% endblock %}
{% block main_content %}
@@ -31,6 +43,7 @@
</div>
<div class="fright js-file-op">
{% if permissions.can_download %}
{% if request.user.is_authenticated and request.user.username != shared_by %}
{% if save_to_link %}
<button data="{{save_to_link}}" id="save" class="shared-file-op-btn">{% trans "Save to..."%}</button>
@@ -39,11 +52,13 @@
{% if not traffic_over_limit %}
<a href="{% if from_shared_dir %}?p={{path|urlencode}}&dl=1{% else %}?dl=1{% endif %}" class="obv-btn shared-file-op-btn">{% trans "Download" %} ({{file_size|filesizeformat}})</a>
{% endif %}
{% endif %}
</div>
</div>
{% include 'snippets/file_content_html.html' %}
{% if permissions.can_download %}
<form id="file-save-form" action="{{save_to_link}}" method="post" class="file-choose-form hide">{% csrf_token %}
<h3>{% trans "Save To:" %}</h3>
<div class="dir-tree-cont">
@@ -58,6 +73,7 @@
<button type="submit" class="submit">{% trans "Submit" %}</button>
<button class="simplemodal-close">{% trans "Cancel" %}</button>
</form>
{% endif %}
{% endblock %}
@@ -87,7 +103,7 @@ function setFileViewAreaHeight() {
$(window).load(setFileViewAreaHeight).resize(setFileViewAreaHeight);
{% endif %}
{% if request.user.is_authenticated and request.user.username != shared_by %}
{% if request.user.is_authenticated and request.user.username != shared_by and permissions.can_download %}
$('#save').click(function() {
var $form = $('#file-save-form');
$form.modal({appendTo:'#main', autoResize:true, focus:false});
@@ -138,5 +154,19 @@ $('#file-save-form').submit(function() {
$('#image-view').attr('src', '{{ raw_path|escapejs }}');
{% endif %}
{% endif %}
{% if not permissions.can_download %}
$(document)
.contextmenu(function() {
return false;
})
.bind('keydown', function(e) {
// prevent ctrl + s/p/a/c, i.e, 'save', 'print', 'select all', 'copy'
if (e.ctrlKey && (e.which == 83 || e.which == 80 || e.which == 65 || e.which == 67)) {
e.preventDefault();
return false;
}
});
{% endif %}
</script>
{% endblock %}

View File

@@ -20,6 +20,7 @@
<label for="password-again">{% trans "Password again"%}</label><br />
<input type="password" name="password_again" disabled="disabled" class="input input-disabled" id="password-again" />
</div>
<label class="checkbox-label">
<input type="checkbox" name="set-expiration" id="link-expire-switch" class="vam" />
<span class="checkbox-option vam">{% trans "Add auto expiration"%}</span>
@@ -28,6 +29,14 @@
<label for="expire-days">{% trans "Days" %}</label><br />
<input type="text" id="expire-days" name="expire-days" disabled="disabled" class="input input-disabled" />
</div>
{% if is_pro %}
<label class="checkbox-label">
<input type="checkbox" name="preview_only" class="vam" />
<span class="checkbox-option vam">{% trans "Online preview only" %}</span>
</label>
{% endif %}
<p class="error hide"></p>
</div>
<button id="gen-link-btn" class="hide">{% trans "Generate"%}</button>

View File

@@ -123,6 +123,7 @@ $('#gen-link-btn').click(function() {
form_id = form.attr('id'),
use_passwd = $('#link-passwd-switch').prop('checked'),
set_expiration = $('#link-expire-switch').prop('checked'),
preview_only = $('[name="preview_only"]', form).prop('checked'),
password, password_again, expire_days,
post_data = {};
@@ -163,6 +164,13 @@ $('#gen-link-btn').click(function() {
}
}
if (preview_only) {
post_data["permissions"] = JSON.stringify({
"can_preview": true,
"can_download": false
});
}
$.ajax({
url: '{% url 'api-v2.1-share-links' %}',
type: 'POST',
@@ -176,8 +184,9 @@ $('#gen-link-btn').click(function() {
$('#link-options, #link-options .error').addClass('hide');
$('#link-passwd, #link-expire').hide(); // slideDown use 'show()'
$('#link-passwd-switch, #link-expire-switch').attr('checked', false).parent().removeClass('checkbox-checked');
$('#link-passwd-switch, #link-expire-switch').prop('checked', false);
$('[type="password"], [name="expire-days"]', form).val('').attr('disabled', false).removeClass('input-disabled');
$('[name="preview_only"]', form).prop('checked', false);
$('#shared-link-text, #link-send-form input[name="file_shared_link"]').val(link);
$('#main').append('<p id="linkwidth" class="hide">' + link + '</p>');

View File

@@ -37,7 +37,7 @@
<img src="{{ MEDIA_URL }}img/grid.png" alt="{% trans "Grid" %}" width="20" />
{% endif %}
</a>
{% if not traffic_over_limit %}
{% if not traffic_over_limit and permissions.can_download %}
<a href="#" class="obv-btn vam shared-dir-zip">{% trans "ZIP"%}</a>
{% endif %}
</div>
@@ -64,7 +64,7 @@
<td></td>
<td>{{ dirent.last_modified|translate_seahub_time }}</td>
<td>
{% if not traffic_over_limit %}
{% if not traffic_over_limit and permissions.can_download %}
<a class="op-icon vh download-dir" data-name="{{ dirent.obj_name }}" href="#" title="{% trans 'Download' %}" aria-label="{% trans 'Download' %}">
<img src="{{MEDIA_URL}}img/download.png" alt="{% trans 'Download' %}" />
</a>
@@ -96,7 +96,7 @@
<td>{{ dirent.file_size|filesizeformat }}</td>
<td>{{ dirent.last_modified|translate_seahub_time }}</td>
<td>
{% if not traffic_over_limit %}
{% if not traffic_over_limit and permissions.can_download %}
<a class="op-icon vh" href="{% url "view_file_via_shared_dir" token %}?p={{ path|urlencode }}{{ dirent.obj_name|urlencode }}&dl=1" title="{% trans "Download"%}" aria-label="{% trans "Download"%}">
<img src="{{MEDIA_URL}}img/download.png" alt="{% trans "Download"%}" />
</a>
@@ -114,7 +114,7 @@
<img src="{{ MEDIA_URL }}img/folder-beige-192.png" alt="" width="96" class="vam" />
</a>
<a href="?p={{ path|urlencode }}{{ dirent.obj_name|urlencode }}&mode={{mode}}" class="normal text-link ellipsis">{{ dirent.obj_name }}</a>
{% if not traffic_over_limit %}
{% if not traffic_over_limit and permissions.can_download %}
<a class="op-icon vh download-dir" data-name="{{ dirent.obj_name }}" href="#" title="{% trans 'Download' %}">
<img src="{{MEDIA_URL}}img/download.png" alt="" />
</a>
@@ -148,7 +148,7 @@
<a class="normal text-link ellipsis" href="{% url "view_file_via_shared_dir" token %}?p={{ path|urlencode }}{{ dirent.obj_name|urlencode }}">{{ dirent.obj_name }}</a>
{% endif %}
{% if not traffic_over_limit %}
{% if not traffic_over_limit and permissions.can_download %}
<a class="op-icon vh" href="{% url "view_file_via_shared_dir" token %}?p={{ path|urlencode }}{{ dirent.obj_name|urlencode }}&dl=1" title="{% trans "Download"%}">
<img src="{{MEDIA_URL}}img/download.png" alt="" />
</a>
@@ -187,7 +187,7 @@ var magnificOptions = {
}
};
{% if not repo.encrypted and ENABLE_THUMBNAIL %}
{% if ENABLE_THUMBNAIL %}
$('[data-mfp-src]').each(function(index, item) {
var $item = $(item);
var file_name = $item.closest('.file-item').attr('data-name');
@@ -197,6 +197,8 @@ $('[data-mfp-src]').each(function(index, item) {
$('.img-name-link').magnificPopup(magnificOptions);
$('.img-img-link').magnificPopup(magnificOptions);
{% if not traffic_over_limit and permissions.can_download %}
$('.shared-dir-zip, .download-dir').click(function (e) {
var cur_download_dir_path;
var interval,
@@ -258,6 +260,7 @@ $('.shared-dir-zip, .download-dir').click(function (e) {
return false;
});
{% endif %}
{% if not repo.encrypted and ENABLE_THUMBNAIL %}
// get thumbnails for image files

View File

@@ -877,6 +877,8 @@ def view_shared_file(request, fileshare):
save_to_link = reverse('save_shared_link') + '?t=' + token
traffic_over_limit = user_traffic_over_limit(shared_by)
permissions = fileshare.get_permissions()
return render_to_response('shared_file_view.html', {
'repo': repo,
'obj_id': obj_id,
@@ -897,6 +899,7 @@ def view_shared_file(request, fileshare):
'accessible_repos': accessible_repos,
'save_to_link': save_to_link,
'traffic_over_limit': traffic_over_limit,
'permissions': permissions,
}, context_instance=RequestContext(request))
def view_raw_shared_file(request, token, obj_id, file_name):

View File

@@ -219,6 +219,8 @@ def view_shared_dir(request, fileshare):
traffic_over_limit = user_traffic_over_limit(fileshare.username)
permissions = fileshare.get_permissions()
# mode to view dir/file items
mode = request.GET.get('mode', 'list')
if mode != 'list':
@@ -248,6 +250,7 @@ def view_shared_dir(request, fileshare):
'dir_list': dir_list,
'zipped': zipped,
'traffic_over_limit': traffic_over_limit,
'permissions': permissions,
'ENABLE_THUMBNAIL': ENABLE_THUMBNAIL,
'mode': mode,
'thumbnail_size': thumbnail_size,

View File

@@ -110,7 +110,7 @@ define([
clickCheckbox: function(e) {
var $el = $(e.currentTarget);
// for link options such as 'password', 'expire'
$el.closest('.checkbox-label').next().toggleClass('hide');
$el.closest('.checkbox-label').next('div').toggleClass('hide');
},
downloadLinkPanelInit: function() {
@@ -134,6 +134,11 @@ define([
_this.download_link = link; // for 'link send'
_this.download_link_token = link_data.token; // for 'link delete'
_this.$('#download-link').html(link);
if (app.pageOptions.is_pro && !link_data.permissions.can_download) {
_this.$('#direct-dl-link').hide().prev('dt').hide();
}
_this.$('#direct-dl-link').html(link + '?dl=1');
if (link_data.is_expired) {
_this.$('#send-download-link').addClass('hide');
@@ -217,6 +222,11 @@ define([
if (link_type == 'download') {
var set_expiration_checkbox = $('[name="set_expiration"]', form),
set_expiration = set_expiration_checkbox.prop('checked');
if (app.pageOptions.is_pro) {
var $preview_only = $('[name="preview_only"]', form);
var preview_only = $preview_only.prop('checked');
}
}
var post_data = {};
@@ -244,7 +254,7 @@ define([
post_data["password"] = passwd;
}
if (set_expiration) { // for upload link, 'set_expiration' is undefined
if (link_type == 'download' && set_expiration) {
var expire_days_input = $('[name="expire_days"]', form),
expire_days = $.trim(expire_days_input.val());
if (!expire_days) {
@@ -258,6 +268,13 @@ define([
post_data["expire_days"] = expire_days;
}
if (link_type == 'download' && preview_only) {
post_data["permissions"] = JSON.stringify({
"can_preview": true,
"can_download": false
});
}
$('.error', form).addClass('hide').html('');
var gen_btn = $('[type="submit"]', form);
Common.disableButton(gen_btn);
@@ -280,7 +297,7 @@ define([
passwd_input.val('');
passwd_again_input.val('');
}
if (set_expiration) {
if (link_type == 'download' && set_expiration) {
set_expiration_checkbox.prop('checked', false)
.parent().removeClass('checkbox-checked')
// hide 'day' input
@@ -288,9 +305,19 @@ define([
expire_days_input.val('');
}
if (link_type == 'download' && preview_only) {
$preview_only.prop('checked', false);
}
if (link_type == 'download') {
_this.$('#download-link').html(data["link"]); // TODO: add 'click & select' func
if (data.permissions.can_download) {
_this.$('#direct-dl-link').show().prev('dt').show();
} else {
_this.$('#direct-dl-link').hide().prev('dt').hide();
}
_this.$('#direct-dl-link').html(data['link'] + '?dl=1');
_this.download_link = data["link"]; // for 'link send'
_this.download_link_token = data["token"]; // for 'link delete'
_this.$('#download-link-operations').removeClass('hide');

View File

@@ -103,6 +103,81 @@ class ShareLinksTest(BaseTestCase):
self._remove_share_link(json_resp['token'])
def test_create_file_share_link_with_permissions(self):
self.login_as(self.user)
json_str = json.dumps({'path': self.file_path, 'repo_id': self.repo_id,
'permissions': {
'can_preview': True,
'can_download': True
}})
resp = self.client.post(self.url, json_str,
content_type="application/json")
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
assert json_resp['link'] is not None
assert json_resp['token'] is not None
assert json_resp['is_expired'] is not None
assert json_resp['token'] in json_resp['link']
assert 'f' in json_resp['link']
assert json_resp['permissions']['can_preview'] is True
assert json_resp['permissions']['can_download'] is True
self._remove_share_link(json_resp['token'])
def test_create_file_share_link_with_invalid_permissions(self):
self.login_as(self.user)
json_str = json.dumps({'path': self.file_path, 'repo_id': self.repo_id,
'permissions': {
'can_previewxxx': True,
'can_downloadyyy': False
}})
resp = self.client.post(self.url, json_str,
content_type="application/json")
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
assert json_resp['link'] is not None
assert json_resp['token'] is not None
assert json_resp['is_expired'] is not None
assert json_resp['token'] in json_resp['link']
assert 'f' in json_resp['link']
assert json_resp['permissions']['can_preview'] is True
assert json_resp['permissions']['can_download'] is True
self._remove_share_link(json_resp['token'])
def test_create_file_share_link_with_view_only_permission(self):
self.login_as(self.user)
json_str = json.dumps({'path': self.file_path, 'repo_id': self.repo_id,
'permissions': {
'can_preview': True,
'can_download': False
}})
resp = self.client.post(self.url, json_str,
content_type="application/json")
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
assert json_resp['link'] is not None
assert json_resp['token'] is not None
assert json_resp['is_expired'] is not None
assert json_resp['token'] in json_resp['link']
assert 'f' in json_resp['link']
assert json_resp['permissions']['can_preview'] is True
assert json_resp['permissions']['can_download'] is False
self._remove_share_link(json_resp['token'])
def test_create_dir_share_link(self):
self.login_as(self.user)