Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Share link download notification #6995

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions frontend/src/components/common/notice-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const MSG_TYPE_DELETED_FILES = 'deleted_files';
const MSG_TYPE_SAML_SSO_FAILED = 'saml_sso_failed';
const MSG_TYPE_REPO_SHARE_PERM_CHANGE = 'repo_share_perm_change';
const MSG_TYPE_REPO_SHARE_PERM_DELETE = 'repo_share_perm_delete';
const MSG_TYPE_SHARE_LINK_DOWNLOAD = 'share_link_download';

dayjs.extend(relativeTime);

Expand Down Expand Up @@ -361,6 +362,25 @@ class NoticeItem extends React.Component {
return { avatar_url: null, notice };
}

if (noticeType === MSG_TYPE_SHARE_LINK_DOWNLOAD) {
const {
from_username,
repo_id,
repo_name,
op_type,
share_link_download_avatar_url
} = detail;
const repoURL = `${siteRoot}library/${repo_id}/${encodeURIComponent(repo_name)}/`;
const repoLink = `<a href=${repoURL} target="_blank">${Utils.HTMLescape(repo_name)}</a>`;
let notice = gettext('{fromUser} has viewed the library named {libraryName} that through the sharing link');
if (op_type === 'dl') {
notice = gettext('{fromUser} have downloaded files from a library named {libraryName} through a shared link');
}
notice = notice.replace('{libraryName}', repoLink);
notice = notice.replace('{fromUser}', from_username);
return { avatar_url: share_link_download_avatar_url, notice };
}

// if (noticeType === MSG_TYPE_GUEST_INVITATION_ACCEPTED) {

// }
Expand Down
20 changes: 14 additions & 6 deletions frontend/src/components/share-link-panel/link-creation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import dayjs from 'dayjs';
import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, Alert } from 'reactstrap';
import { gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, shareLinkForceUsePassword, shareLinkPasswordMinLength, shareLinkPasswordStrengthLevel, isEmailConfigured } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { shareLinkAPI } from '../../utils/share-link-api';
import { Utils } from '../../utils/utils';
import ShareLink from '../../models/share-link';
Expand Down Expand Up @@ -45,7 +44,7 @@ class LinkCreation extends React.Component {
passwdnew: '',
errorInfo: '',
currentPermission: props.currentPermission,

notif_enable: false,
currentScope: 'all_users',
selectedOption: null,
inputEmails: ''
Expand Down Expand Up @@ -106,7 +105,7 @@ class LinkCreation extends React.Component {
if (isValid) {
this.setState({ errorInfo: '' });
let { type, itemPath, repoID } = this.props;
let { linkAmount, isShowPasswordInput, password, isExpireChecked, expType, expireDays, expDate } = this.state;
let { linkAmount, isShowPasswordInput, password, isExpireChecked, expType, expireDays, expDate, notif_enable } = this.state;

const permissionDetails = Utils.getShareLinkPermissionObject(this.state.currentPermission).permissionDetails;
let permissions;
Expand All @@ -125,7 +124,7 @@ class LinkCreation extends React.Component {
let users;
if (type === 'batch') {
const autoGeneratePassword = shareLinkForceUsePassword || isShowPasswordInput;
request = seafileAPI.batchCreateMultiShareLink(repoID, itemPath, linkAmount, autoGeneratePassword, expirationTime, permissions);
request = shareLinkAPI.batchCreateMultiShareLink(repoID, itemPath, linkAmount, autoGeneratePassword, expirationTime, permissions, notif_enable);
} else {
const { currentScope, selectedOption, inputEmails } = this.state;
if (currentScope === 'specific_users' && selectedOption) {
Expand All @@ -134,7 +133,7 @@ class LinkCreation extends React.Component {
if (currentScope === 'specific_emails' && inputEmails) {
users = inputEmails;
}
request = shareLinkAPI.createMultiShareLink(repoID, itemPath, password, expirationTime, permissions, currentScope, users);
request = shareLinkAPI.createMultiShareLink(repoID, itemPath, password, expirationTime, permissions, currentScope, users, notif_enable);
}

request.then((res) => {
Expand Down Expand Up @@ -167,6 +166,10 @@ class LinkCreation extends React.Component {
this.setState({ expireDays: day });
};

onNotifChecked = (e) => {
this.setState({ notif_enable: e.target.checked });
};

validateParamsInput = () => {
const { type } = this.props;
let { linkAmount, isShowPasswordInput, password, passwdnew, isExpireChecked, expType, expireDays, expDate } = this.state;
Expand Down Expand Up @@ -279,7 +282,6 @@ class LinkCreation extends React.Component {
render() {
const { userPerm, type, permissionOptions } = this.props;
const { isCustomPermission } = Utils.getUserPermission(userPerm);

return (
<Fragment>
<div className="d-flex align-items-center pb-2 border-bottom">
Expand Down Expand Up @@ -356,6 +358,12 @@ class LinkCreation extends React.Component {
</div>
}
</FormGroup>
<FormGroup check>
<Label check>
<Input type="checkbox" onChange={this.onNotifChecked} checked={this.state.notif_enable}/>
<span>{gettext('Receive notification')}</span>
</Label>
</FormGroup>
{!isCustomPermission && (
<FormGroup check>
<Label check>
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/utils/share-link-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,13 @@ class ShareLinkAPI {
return this.req.put(url, form);
}

createMultiShareLink(repoID, path, password, expirationTime, permissions, scope, users) {
createMultiShareLink(repoID, path, password, expirationTime, permissions, scope, users, notif_enable) {
const url = this.server + '/api/v2.1/multi-share-links/';
let form = {
'path': path,
'repo_id': repoID,
'user_scope': scope,
'notif_enable': notif_enable
};
if (permissions) {
form['permissions'] = permissions;
Expand All @@ -122,6 +123,27 @@ class ShareLinkAPI {
}
return this._sendPostRequest(url, form);
}

batchCreateMultiShareLink(repoID, path, shareLinkNum, autoGeneratePassword, expirationTime, permissions, notif_enable) {
const url = this.server + '/api/v2.1/multi-share-links/batch/';
let form = {
'path': path,
'repo_id': repoID,
'number': shareLinkNum,
'auto_generate_password': autoGeneratePassword,
'notif_enable': notif_enable
};

if (permissions) {
form['permissions'] = permissions;
}

if (expirationTime) {
form['expiration_time'] = expirationTime;
}

return this._sendPostRequest(url, form);
}
}

let shareLinkAPI = new ShareLinkAPI();
Expand Down
23 changes: 11 additions & 12 deletions seahub/api2/endpoints/multi_share_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def post(self, request):
error_msg = 'path invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

notif_enable = request.data.get('notif_enable', False)
password = request.data.get('password', None)
if config.SHARE_LINK_FORCE_USE_PASSWORD and not password:
error_msg = _('Password is required.')
Expand Down Expand Up @@ -251,12 +252,14 @@ def post(self, request):
if s_type == 'f':
fs = FileShare.objects.create_file_link(username, repo_id, path,
password, expire_date,
permission=perm, org_id=org_id)
permission=perm, org_id=org_id,
notif_enable=notif_enable)

else:
fs = FileShare.objects.create_dir_link(username, repo_id, path,
password, expire_date,
permission=perm, org_id=org_id)
permission=perm, org_id=org_id,
notif_enable=notif_enable)

user_scope = request.data.get('user_scope', '')
emails_list = []
Expand Down Expand Up @@ -337,18 +340,12 @@ def post(self, request):
error_msg = 'number invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

notif_enable = request.data.get('notif_enable', False)
auto_generate_password = request.data.get('auto_generate_password')
if not auto_generate_password:
if auto_generate_password is None:
error_msg = 'auto_generate_password invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

auto_generate_password = auto_generate_password.lower()
if auto_generate_password not in ('true', 'false'):
error_msg = 'auto_generate_password invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

auto_generate_password = auto_generate_password == 'true'

if config.SHARE_LINK_FORCE_USE_PASSWORD and not auto_generate_password:
error_msg = _('Password is required.')
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
Expand Down Expand Up @@ -499,12 +496,14 @@ def generate_password():
if s_type == 'f':
fs = FileShare.objects.create_file_link(username, repo_id, path,
password, expire_date,
permission=perm, org_id=org_id)
permission=perm, org_id=org_id,
notif_enable=notif_enable)

elif s_type == 'd':
fs = FileShare.objects.create_dir_link(username, repo_id, path,
password, expire_date,
permission=perm, org_id=org_id)
permission=perm, org_id=org_id,
notif_enable=notif_enable)

created_share_links.append(fs)

Expand Down
15 changes: 14 additions & 1 deletion seahub/api2/endpoints/share_link_zip_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@

from seahub.api2.throttling import ShareLinkZipTaskThrottle
from seahub.api2.utils import api_error
from seahub.base.templatetags.seahub_tags import email2nickname

from seahub.views.file import send_file_access_msg
from seahub.share.models import FileShare, check_share_link_access, check_share_link_access_by_scope
from seahub.utils import is_windows_operating_system, is_pro_version, \
normalize_dir_path
normalize_dir_path
from seahub.utils.repo import parse_repo_perm
from seahub.settings import SHARE_LINK_LOGIN_REQUIRED
from seahub.signals import share_link_download_successful


from seaserv import seafile_api

Expand Down Expand Up @@ -116,6 +119,16 @@ def get(self, request, format=None):
request.user.username = request.session.get('anonymous_email')

send_file_access_msg(request, repo, real_path, 'share-link')
# send a signal when successfully download folder
if fileshare.is_notification_enabled:
try:
to_user = fileshare.username
from_user = request.user.username
from_username = 'Anonymous user' if email2nickname(from_user) == '' else email2nickname(from_user)
share_link_download_successful.send(sender=None,from_user=from_user, from_username=from_username,
to_user=to_user, repo_id=repo_id, repo_name=repo.name, op_type='dl')
except Exception as e:
logger.error(e)

return Response({'zip_token': zip_token})

Expand Down
16 changes: 16 additions & 0 deletions seahub/notifications/management/commands/send_notices.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,19 @@ def format_saml_sso_error_msg(self, notice):
notice.error_msg = d['error_msg']
return notice

def format_share_link_download_msg(self, notice):
d = json.loads(notice.detail)
repo_id = d['repo_id']
repo_name = d['repo_name']
from_user = d['from_user']
notice.repo_url = reverse('lib_view', args=[repo_id, repo_name, ''])
notice.repo_name = repo_name
notice.from_user = from_user
notice.op_type = d['op_type']
notice.avatar_src = d['share_link_download_avatar_url']
return notice


def format_sdoc_msg(self, sdoc_queryset, sdoc_notice):
sdoc_obj = sdoc_queryset.filter(uuid=sdoc_notice.doc_uuid).first()
if not sdoc_obj:
Expand Down Expand Up @@ -464,6 +477,9 @@ def do_action(self):
elif notice.is_saml_sso_error_msg():
notice = self.format_saml_sso_error_msg(notice)

elif notice.is_share_link_download_msg():
notice = self.format_share_link_download_msg(notice)

if notice is None:
continue

Expand Down
42 changes: 40 additions & 2 deletions seahub/notifications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from seaserv import seafile_api, ccnet_api

from seahub.avatar.templatetags.avatar_tags import api_avatar_url
from seahub.base.fields import LowerCaseCharField
from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.invitations.models import Invitation
Expand Down Expand Up @@ -80,6 +81,7 @@ class Meta:
MSG_TYPE_REPO_MINOTOR = 'repo_monitor'
MSG_TYPE_DELETED_FILES = 'deleted_files'
MSG_TYPE_SAML_SSO_FAILED = 'saml_sso_failed'
MSG_TYPE_SHARE_LINK_DOWNLOAD = 'share_link_download'

USER_NOTIFICATION_COUNT_CACHE_PREFIX = 'USER_NOTIFICATION_COUNT_'

Expand Down Expand Up @@ -156,6 +158,17 @@ def saml_sso_error_msg_to_json(error_msg):
return json.dumps({'error_msg': error_msg})


def share_link_download_msg_to_json(from_user, from_username, repo_id, repo_name, op_type, avatar_url):
return json.dumps({
'from_user': from_user,
'from_username': from_username,
'repo_id': repo_id,
'repo_name': repo_name,
'op_type': op_type,
'share_link_download_avatar_url': avatar_url
})


def get_cache_key_of_unseen_notifications(username):
return normalize_cache_key(username,
USER_NOTIFICATION_COUNT_CACHE_PREFIX)
Expand Down Expand Up @@ -356,6 +369,12 @@ def add_saml_sso_error_msg(self, to_user, detail):
"""
return self._add_user_notification(to_user, MSG_TYPE_SAML_SSO_FAILED, detail)

def add_share_link_download_msg(self, to_user, detail):
"""
Notify ``to_user`` that a file/folder has been view or download
"""
return self._add_user_notification(to_user, MSG_TYPE_SHARE_LINK_DOWNLOAD, detail)


class UserNotification(models.Model):
to_user = LowerCaseCharField(db_index=True, max_length=255)
Expand Down Expand Up @@ -475,6 +494,9 @@ def is_deleted_files_msg(self):
def is_saml_sso_error_msg(self):
return self.msg_type == MSG_TYPE_SAML_SSO_FAILED

def is_share_link_download_msg(self):
return self.msg_type == MSG_TYPE_SHARE_LINK_DOWNLOAD

def user_message_detail_to_dict(self):
"""Parse user message detail, returns dict contains ``message`` and
``msg_from``.
Expand Down Expand Up @@ -881,8 +903,8 @@ def format_repo_transfer_msg(self):
########## handle signals
from django.dispatch import receiver

from seahub.signals import upload_file_successful, upload_folder_successful,\
comment_file_successful, repo_transfer
from seahub.signals import upload_file_successful, upload_folder_successful, \
comment_file_successful, repo_transfer, share_link_download_successful
from seahub.group.signals import group_join_request, add_user_to_group
from seahub.share.signals import share_repo_to_user_successful, \
share_repo_to_group_successful, change_repo_perm_successful, delete_repo_perm_successful
Expand Down Expand Up @@ -1083,3 +1105,19 @@ def saml_sso_failed_cb(sender, **kwargs):

detail = saml_sso_error_msg_to_json(error_msg)
UserNotification.objects.add_saml_sso_error_msg(to_user, detail)


@receiver(share_link_download_successful)
def share_link_download_cb(sender, **kwargs):
"""
Send when others view or download files through shared links
"""
to_user = kwargs['to_user']
from_user = kwargs['from_user']
from_username = kwargs['from_username']
repo_id = kwargs['repo_id']
repo_name = kwargs['repo_name']
op_type = kwargs['op_type']
avatar_url, _, _ = api_avatar_url(from_user, 256)
detail = share_link_download_msg_to_json(from_user, from_username, repo_id, repo_name, op_type, avatar_url)
UserNotification.objects.add_share_link_download_msg(to_user, detail)
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@
{% elif notice.is_saml_sso_error_msg %}
<p style="line-height:1.5; margin:.2em 10px .2em 0;">{{notice.error_msg}}</p>

{% elif notice.is_share_link_download_msg %}
<p style="line-height:1.5; margin:.2em 10px .2em 0;">
{% if notice.op_type == 'dl' %}
{{ notice.from_user|email2nickname|escape|default:"Anonymous user" }} downloaded the files under the Library named {{ notice.repo_name }} through your shared link
{% else %}
{{ notice.from_user|email2nickname|escape|default:"Anonymous user" }} have viewed your Library named {{ notice.repo_name }} through a shared link
{% endif %}
</p>
{% elif notice.is_repo_monitor_msg %}
<p style="line-height:1.5; margin:.2em 10px .2em 0;">
{% if notice.obj_type == 'file' %}
Expand Down
7 changes: 7 additions & 0 deletions seahub/notifications/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,13 @@ def update_notice_detail(request, notices):
except Exception as e:
logger.error(e)

elif notice.is_share_link_download_msg():
try:
d = json.loads(notice.detail)
notice.detail = d
except Exception as e:
logger.error(e)

return notices


Expand Down
Loading
Loading