1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-02 07:27:04 +00:00

update create share/upload link

add SHARE_LINK_FORCE_USE_PASSWORD and SHARE_LINK_PASSWORD_STRENGTH_LEVEL
This commit is contained in:
lian
2021-08-12 15:02:03 +08:00
parent 73fd93dd07
commit 8e78d73b53
16 changed files with 180 additions and 40 deletions

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import moment from 'moment'; import moment from 'moment';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, InputGroupText, Alert, FormText } from 'reactstrap'; import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, InputGroupText, Alert, FormText } from 'reactstrap';
import { isPro, gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, shareLinkPasswordMinLength, canSendShareLinkEmail } from '../../utils/constants'; import { isPro, gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, shareLinkForceUsePassword, shareLinkPasswordMinLength, shareLinkPasswordStrengthLevel, canSendShareLinkEmail } from '../../utils/constants';
import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor'; import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
@@ -47,7 +47,7 @@ class GenerateShareLink extends React.Component {
this.state = { this.state = {
isOpIconShown: false, isOpIconShown: false,
isValidate: false, isValidate: false,
isShowPasswordInput: false, isShowPasswordInput: shareLinkForceUsePassword ? true : false,
isPasswordVisible: false, isPasswordVisible: false,
isExpireChecked: !this.isExpireDaysNoLimit, isExpireChecked: !this.isExpireDaysNoLimit,
setExp: 'by-days', setExp: 'by-days',
@@ -232,8 +232,8 @@ class GenerateShareLink extends React.Component {
seafileAPI.deleteShareLink(sharedLinkInfo.token).then(() => { seafileAPI.deleteShareLink(sharedLinkInfo.token).then(() => {
this.setState({ this.setState({
password: '', password: '',
passwordnew: '', passwdnew: '',
isShowPasswordInput: false, isShowPasswordInput: shareLinkForceUsePassword ? true : false,
expireDays: this.defaultExpireDays, expireDays: this.defaultExpireDays,
isExpireChecked: !this.isExpireDaysNoLimit, isExpireChecked: !this.isExpireDaysNoLimit,
errorInfo: '', errorInfo: '',
@@ -272,6 +272,10 @@ class GenerateShareLink extends React.Component {
this.setState({errorInfo: 'Passwords don\'t match'}); this.setState({errorInfo: 'Passwords don\'t match'});
return false; return false;
} }
if (Utils.getStrengthLevel(password) < shareLinkPasswordStrengthLevel) {
this.setState({errorInfo: gettext('Password is too weak, should have at least {shareLinkPasswordStrengthLevel} of the following: num, upper letter, lower letter and other symbols'.replace('{shareLinkPasswordStrengthLevel}', shareLinkPasswordStrengthLevel))});
return false;
}
} }
if (isExpireChecked) { if (isExpireChecked) {
@@ -359,8 +363,9 @@ class GenerateShareLink extends React.Component {
return <Loading />; return <Loading />;
} }
let passwordLengthTip = gettext('(at least {passwordLength} characters)'); let passwordLengthTip = gettext('(at least {passwordLength} characters and has {shareLinkPasswordStrengthLevel} of the following: num, upper letter, lower letter and other symbols)');
passwordLengthTip = passwordLengthTip.replace('{passwordLength}', shareLinkPasswordMinLength); passwordLengthTip = passwordLengthTip.replace('{passwordLength}', shareLinkPasswordMinLength)
.replace('{shareLinkPasswordStrengthLevel}', shareLinkPasswordStrengthLevel);
if (this.state.sharedLinkInfo) { if (this.state.sharedLinkInfo) {
let sharedLinkInfo = this.state.sharedLinkInfo; let sharedLinkInfo = this.state.sharedLinkInfo;
@@ -442,10 +447,17 @@ class GenerateShareLink extends React.Component {
return ( return (
<Form className="generate-share-link"> <Form className="generate-share-link">
<FormGroup check> <FormGroup check>
{shareLinkForceUsePassword ? (
<Label check>
<Input type="checkbox" checked readOnly disabled />
<span>{gettext('Add password protection')}</span>
</Label>
) : (
<Label check> <Label check>
<Input type="checkbox" onChange={this.onPasswordInputChecked} /> <Input type="checkbox" onChange={this.onPasswordInputChecked} />
<span>{gettext('Add password protection')}</span> <span>{gettext('Add password protection')}</span>
</Label> </Label>
)}
{this.state.isShowPasswordInput && {this.state.isShowPasswordInput &&
<div className="ml-4"> <div className="ml-4">
<FormGroup> <FormGroup>

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import moment from 'moment'; import moment from 'moment';
import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, InputGroupText, Alert, FormText } from 'reactstrap'; import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, InputGroupText, Alert, FormText } from 'reactstrap';
import { gettext, shareLinkPasswordMinLength, canSendShareLinkEmail, uploadLinkExpireDaysMin, uploadLinkExpireDaysMax, uploadLinkExpireDaysDefault } from '../../utils/constants'; import { gettext, shareLinkForceUsePassword, shareLinkPasswordMinLength, shareLinkPasswordStrengthLevel, canSendShareLinkEmail, uploadLinkExpireDaysMin, uploadLinkExpireDaysMax, uploadLinkExpireDaysDefault } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import UploadLink from '../../models/upload-link'; import UploadLink from '../../models/upload-link';
@@ -42,10 +42,10 @@ class GenerateUploadLink extends React.Component {
this.expirationLimitTip = expirationLimitTip; this.expirationLimitTip = expirationLimitTip;
this.state = { this.state = {
showPasswordInput: false, showPasswordInput: shareLinkForceUsePassword ? true : false,
passwordVisible: false, passwordVisible: false,
password: '', password: '',
passwdnew: '', passwordnew: '',
sharedUploadInfo: null, sharedUploadInfo: null,
isSendLinkShown: false, isSendLinkShown: false,
isExpireChecked: !this.isExpireDaysNoLimit, isExpireChecked: !this.isExpireDaysNoLimit,
@@ -80,7 +80,7 @@ class GenerateUploadLink extends React.Component {
this.setState({ this.setState({
showPasswordInput: !this.state.showPasswordInput, showPasswordInput: !this.state.showPasswordInput,
password: '', password: '',
passwdnew: '', passwordnew: '',
errorInfo: '' errorInfo: ''
}); });
} }
@@ -155,6 +155,10 @@ class GenerateUploadLink extends React.Component {
this.setState({errorInfo: gettext('Passwords don\'t match')}); this.setState({errorInfo: gettext('Passwords don\'t match')});
return false; return false;
} }
if (Utils.getStrengthLevel(password) < shareLinkPasswordStrengthLevel) {
this.setState({errorInfo: gettext('Password is too weak, should have at least {shareLinkPasswordStrengthLevel} of the following: num, upper letter, lower letter and other symbols'.replace('{shareLinkPasswordStrengthLevel}', shareLinkPasswordStrengthLevel))});
return false;
}
} }
if (isExpireChecked) { if (isExpireChecked) {
@@ -233,7 +237,7 @@ class GenerateUploadLink extends React.Component {
let sharedUploadInfo = this.state.sharedUploadInfo; let sharedUploadInfo = this.state.sharedUploadInfo;
seafileAPI.deleteUploadLink(sharedUploadInfo.token).then(() => { seafileAPI.deleteUploadLink(sharedUploadInfo.token).then(() => {
this.setState({ this.setState({
showPasswordInput: false, showPasswordInput: shareLinkForceUsePassword ? true : false,
expireDays: this.defaultExpireDays, expireDays: this.defaultExpireDays,
isExpireChecked: !this.isExpireDaysNoLimit, isExpireChecked: !this.isExpireDaysNoLimit,
password: '', password: '',
@@ -256,8 +260,10 @@ class GenerateUploadLink extends React.Component {
const { isSendLinkShown } = this.state; const { isSendLinkShown } = this.state;
let passwordLengthTip = gettext('(at least {passwordLength} characters)'); let passwordLengthTip = gettext('(at least {passwordLength} characters and has {shareLinkPasswordStrengthLevel} of the following: num, upper letter, lower letter and other symbols)');
passwordLengthTip = passwordLengthTip.replace('{passwordLength}', shareLinkPasswordMinLength); passwordLengthTip = passwordLengthTip.replace('{passwordLength}', shareLinkPasswordMinLength)
.replace('{shareLinkPasswordStrengthLevel}', shareLinkPasswordStrengthLevel);
if (this.state.sharedUploadInfo) { if (this.state.sharedUploadInfo) {
let sharedUploadInfo = this.state.sharedUploadInfo; let sharedUploadInfo = this.state.sharedUploadInfo;
return ( return (
@@ -297,8 +303,17 @@ class GenerateUploadLink extends React.Component {
<Form className="generate-upload-link"> <Form className="generate-upload-link">
<FormGroup check> <FormGroup check>
<Label check> <Label check>
<Input type="checkbox" onChange={this.addPassword} /> {shareLinkForceUsePassword ? (
<span>{gettext('Add password protection')}</span> <Label check>
<Input type="checkbox" checked readOnly disabled />
<span>{gettext('Add password protection')}</span>
</Label>
) : (
<Label check>
<Input type="checkbox" onChange={this.addPassword} />
<span>{gettext('Add password protection')}</span>
</Label>
)}
</Label> </Label>
{this.state.showPasswordInput && {this.state.showPasswordInput &&
<div className="ml-4"> <div className="ml-4">

View File

@@ -299,12 +299,26 @@ class WebSettings extends Component {
value={config_dict['REPO_PASSWORD_MIN_LENGTH']} value={config_dict['REPO_PASSWORD_MIN_LENGTH']}
helpTip={gettext('The least number of characters an encrypted library password should include.')} helpTip={gettext('The least number of characters an encrypted library password should include.')}
/> />
<CheckboxItem
saveSetting={this.saveSetting}
displayName={gettext('share/upload link force password')}
keyText='SHARE_LINK_FORCE_USE_PASSWORD'
value={config_dict['SHARE_LINK_FORCE_USE_PASSWORD']}
helpTip={gettext('Force user use password when generating share/upload link.')}
/>
<InputItem <InputItem
saveSetting={this.saveSetting} saveSetting={this.saveSetting}
displayName={gettext('download/upload link password minimum length')} displayName={gettext('share/upload link password minimum length')}
keyText='SHARE_LINK_PASSWORD_MIN_LENGTH' keyText='SHARE_LINK_PASSWORD_MIN_LENGTH'
value={config_dict['SHARE_LINK_PASSWORD_MIN_LENGTH']} value={config_dict['SHARE_LINK_PASSWORD_MIN_LENGTH']}
helpTip={gettext('The least number of characters a download/upload link password should include.')} helpTip={gettext('The least number of characters a share/upload link password should include.')}
/>
<InputItem
saveSetting={this.saveSetting}
displayName={gettext('share/upload link password strength level')}
keyText='SHARE_LINK_PASSWORD_STRENGTH_LEVEL'
value={config_dict['SHARE_LINK_PASSWORD_STRENGTH_LEVEL']}
helpTip={gettext('The level(1-4) of a share/upload link password\'s strength. For example, \'3\' means password must have at least 3 of the following: num, upper letter, lower letter and other symbols')}
/> />
<CheckboxItem <CheckboxItem
saveSetting={this.saveSetting} saveSetting={this.saveSetting}

View File

@@ -45,7 +45,9 @@ export const resumableUploadFileBlockSize = window.app.pageOptions.resumableUplo
export const storages = window.app.pageOptions.storages; // storage backends export const storages = window.app.pageOptions.storages; // storage backends
export const libraryTemplates = window.app.pageOptions.libraryTemplates; // library templates export const libraryTemplates = window.app.pageOptions.libraryTemplates; // library templates
export const enableRepoSnapshotLabel = window.app.pageOptions.enableRepoSnapshotLabel; export const enableRepoSnapshotLabel = window.app.pageOptions.enableRepoSnapshotLabel;
export const shareLinkForceUsePassword = window.app.pageOptions.shareLinkForceUsePassword;
export const shareLinkPasswordMinLength = window.app.pageOptions.shareLinkPasswordMinLength; export const shareLinkPasswordMinLength = window.app.pageOptions.shareLinkPasswordMinLength;
export const shareLinkPasswordStrengthLevel = window.app.pageOptions.shareLinkPasswordStrengthLevel;
export const shareLinkExpireDaysMin = window.app.pageOptions.shareLinkExpireDaysMin; export const shareLinkExpireDaysMin = window.app.pageOptions.shareLinkExpireDaysMin;
export const shareLinkExpireDaysMax = window.app.pageOptions.shareLinkExpireDaysMax; export const shareLinkExpireDaysMax = window.app.pageOptions.shareLinkExpireDaysMax;
export const sideNavFooterCustomHtml = window.app.pageOptions.sideNavFooterCustomHtml; export const sideNavFooterCustomHtml = window.app.pageOptions.sideNavFooterCustomHtml;

View File

@@ -1,4 +1,4 @@
import { mediaUrl, gettext, serviceURL, siteRoot, isPro, enableFileComment, fileAuditEnabled, canGenerateShareLink, canGenerateUploadLink, username, folderPermEnabled } from './constants'; import { mediaUrl, gettext, serviceURL, siteRoot, isPro, enableFileComment, fileAuditEnabled, canGenerateShareLink, canGenerateUploadLink, shareLinkPasswordMinLength, username, folderPermEnabled } from './constants';
import { strChineseFirstPY } from './pinyin-by-unicode'; import { strChineseFirstPY } from './pinyin-by-unicode';
import TextTranslation from './text-translation'; import TextTranslation from './text-translation';
import React from 'react'; import React from 'react';
@@ -1342,6 +1342,44 @@ export const Utils = {
hasNextPage(curPage, perPage, totalCount) { hasNextPage(curPage, perPage, totalCount) {
return curPage * perPage < totalCount; return curPage * perPage < totalCount;
} },
getStrengthLevel: function(pwd) {
const _this = this;
var num = 0;
if (pwd.length < shareLinkPasswordMinLength) {
return 0;
} else {
for (var i = 0; i < pwd.length; i++) {
// return the unicode
// bitwise OR
num |= _this.getCharMode(pwd.charCodeAt(i));
}
return _this.calculateBitwise(num);
}
},
getCharMode: function(n) {
if (n >= 48 && n <= 57) // nums
return 1;
if (n >= 65 && n <= 90) // uppers
return 2;
if (n >= 97 && n <= 122) // lowers
return 4;
else
return 8;
},
calculateBitwise: function(num) {
var level = 0;
for (var i = 0; i < 4; i++){
// bitwise AND
if (num&1) level++;
// Right logical shift
num>>>=1;
}
return level;
},
}; };

View File

@@ -4,7 +4,6 @@ import logging
from constance import config from constance import config
from django.conf import settings as dj_settings from django.conf import settings as dj_settings
from django.utils.translation import ugettext as _
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAdminUser from rest_framework.permissions import IsAdminUser
@@ -25,6 +24,7 @@ DIGIT_WEB_SETTINGS = [
'ENABLE_REPO_HISTORY_SETTING', 'USER_STRONG_PASSWORD_REQUIRED', 'ENABLE_REPO_HISTORY_SETTING', 'USER_STRONG_PASSWORD_REQUIRED',
'ENABLE_ENCRYPTED_LIBRARY', 'USER_PASSWORD_MIN_LENGTH', 'ENABLE_ENCRYPTED_LIBRARY', 'USER_PASSWORD_MIN_LENGTH',
'USER_PASSWORD_STRENGTH_LEVEL', 'SHARE_LINK_PASSWORD_MIN_LENGTH', 'USER_PASSWORD_STRENGTH_LEVEL', 'SHARE_LINK_PASSWORD_MIN_LENGTH',
'SHARE_LINK_FORCE_USE_PASSWORD', 'SHARE_LINK_PASSWORD_STRENGTH_LEVEL',
'ENABLE_USER_CREATE_ORG_REPO', 'FORCE_PASSWORD_CHANGE', 'ENABLE_USER_CREATE_ORG_REPO', 'FORCE_PASSWORD_CHANGE',
'LOGIN_ATTEMPT_LIMIT', 'FREEZE_USER_ON_LOGIN_FAILED', 'LOGIN_ATTEMPT_LIMIT', 'FREEZE_USER_ON_LOGIN_FAILED',
'ENABLE_SHARE_TO_ALL_GROUPS', 'ENABLE_TWO_FACTOR_AUTH', 'ENABLE_SHARE_TO_ALL_GROUPS', 'ENABLE_TWO_FACTOR_AUTH',
@@ -87,6 +87,10 @@ class AdminWebSettings(APIView):
error_msg = 'value invalid.' error_msg = 'value invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if key == 'SHARE_LINK_PASSWORD_STRENGTH_LEVEL' and value not in (1, 2, 3, 4):
error_msg = 'value invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if (key in STRING_WEB_SETTINGS and key != 'CUSTOM_CSS') and not value: if (key in STRING_WEB_SETTINGS and key != 'CUSTOM_CSS') and not value:
error_msg = 'value invalid.' error_msg = 'value invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

View File

@@ -32,7 +32,7 @@ from seahub.constants import PERMISSION_READ_WRITE, PERMISSION_READ, \
from seahub.share.models import FileShare, UploadLinkShare, check_share_link_access from seahub.share.models import FileShare, UploadLinkShare, check_share_link_access
from seahub.utils import gen_shared_link, is_org_context, normalize_file_path, \ from seahub.utils import gen_shared_link, is_org_context, normalize_file_path, \
normalize_dir_path, is_pro_version, get_file_type_and_ext, \ normalize_dir_path, is_pro_version, get_file_type_and_ext, \
check_filename_with_rename, gen_file_upload_url check_filename_with_rename, gen_file_upload_url, get_password_strength_level
from seahub.utils.file_op import if_locked_by_online_office from seahub.utils.file_op import if_locked_by_online_office
from seahub.utils.file_types import IMAGE, VIDEO, XMIND from seahub.utils.file_types import IMAGE, VIDEO, XMIND
from seahub.utils.timeutils import datetime_to_isoformat_timestr, \ from seahub.utils.timeutils import datetime_to_isoformat_timestr, \
@@ -263,10 +263,21 @@ class ShareLinks(APIView):
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
password = request.data.get('password', None) password = request.data.get('password', None)
if password and len(password) < config.SHARE_LINK_PASSWORD_MIN_LENGTH:
error_msg = _('Password is too short.') if config.SHARE_LINK_FORCE_USE_PASSWORD and not password:
error_msg = _('Password is required.')
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if password:
if len(password) < config.SHARE_LINK_PASSWORD_MIN_LENGTH:
error_msg = _('Password is too short.')
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if get_password_strength_level(password) < config.SHARE_LINK_PASSWORD_STRENGTH_LEVEL:
error_msg = _('Password is too weak.')
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
expire_days = request.data.get('expire_days', '') expire_days = request.data.get('expire_days', '')
expiration_time = request.data.get('expiration_time', '') expiration_time = request.data.get('expiration_time', '')
if expire_days and expiration_time: if expire_days and expiration_time:

View File

@@ -26,7 +26,7 @@ from seahub.api2.permissions import CanGenerateUploadLink
from seahub.share.models import UploadLinkShare, check_share_link_common from seahub.share.models import UploadLinkShare, check_share_link_common
from seahub.utils import gen_shared_upload_link, gen_file_upload_url, \ from seahub.utils import gen_shared_upload_link, gen_file_upload_url, \
is_pro_version is_pro_version, get_password_strength_level
from seahub.views import check_folder_permission from seahub.views import check_folder_permission
from seahub.utils.timeutils import datetime_to_isoformat_timestr from seahub.utils.timeutils import datetime_to_isoformat_timestr
@@ -157,10 +157,21 @@ class UploadLinks(APIView):
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
password = request.data.get('password', None) password = request.data.get('password', None)
if password and len(password) < config.SHARE_LINK_PASSWORD_MIN_LENGTH:
error_msg = _('Password is too short') if config.SHARE_LINK_FORCE_USE_PASSWORD and not password:
error_msg = _('Password is required.')
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if password:
if len(password) < config.SHARE_LINK_PASSWORD_MIN_LENGTH:
error_msg = _('Password is too short.')
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if get_password_strength_level(password) < config.SHARE_LINK_PASSWORD_STRENGTH_LEVEL:
error_msg = _('Password is too weak.')
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
expire_days = request.data.get('expire_days', '') expire_days = request.data.get('expire_days', '')
expiration_time = request.data.get('expiration_time', '') expiration_time = request.data.get('expiration_time', '')
if expire_days and expiration_time: if expire_days and expiration_time:

View File

@@ -3996,7 +3996,7 @@ class SharedFileDetailView(APIView):
password = request.GET.get('password', '') password = request.GET.get('password', '')
if not password: if not password:
return api_error(status.HTTP_403_FORBIDDEN, "Password is required") return api_error(status.HTTP_403_FORBIDDEN, _('Password is required.'))
if not check_password(password, fileshare.password): if not check_password(password, fileshare.password):
return api_error(status.HTTP_403_FORBIDDEN, "Invalid Password") return api_error(status.HTTP_403_FORBIDDEN, "Invalid Password")
@@ -4116,7 +4116,7 @@ class SharedDirView(APIView):
password = request.GET.get('password', '') password = request.GET.get('password', '')
if not password: if not password:
return api_error(status.HTTP_403_FORBIDDEN, "Password is required") return api_error(status.HTTP_403_FORBIDDEN, _('Password is required.'))
if not check_password(password, fileshare.password): if not check_password(password, fileshare.password):
return api_error(status.HTTP_403_FORBIDDEN, "Invalid Password") return api_error(status.HTTP_403_FORBIDDEN, "Invalid Password")

View File

@@ -15,7 +15,7 @@ from django.conf import settings as dj_settings
from django.utils.functional import lazy from django.utils.functional import lazy
from constance import config from constance import config
from seahub.settings import SEAFILE_VERSION, SITE_TITLE, SITE_NAME, \ from seahub.settings import SEAFILE_VERSION, \
MAX_FILE_NAME, LOGO_PATH, BRANDING_CSS, LOGO_WIDTH, LOGO_HEIGHT,\ MAX_FILE_NAME, LOGO_PATH, BRANDING_CSS, LOGO_WIDTH, LOGO_HEIGHT,\
SHOW_REPO_DOWNLOAD_BUTTON, SITE_ROOT, ENABLE_GUEST_INVITATION, \ SHOW_REPO_DOWNLOAD_BUTTON, SITE_ROOT, ENABLE_GUEST_INVITATION, \
FAVICON_PATH, ENABLE_THUMBNAIL, THUMBNAIL_SIZE_FOR_ORIGINAL, \ FAVICON_PATH, ENABLE_THUMBNAIL, THUMBNAIL_SIZE_FOR_ORIGINAL, \
@@ -121,7 +121,9 @@ def base(request):
'max_file_name': MAX_FILE_NAME, 'max_file_name': MAX_FILE_NAME,
'has_file_search': HAS_FILE_SEARCH, 'has_file_search': HAS_FILE_SEARCH,
'show_repo_download_button': SHOW_REPO_DOWNLOAD_BUTTON, 'show_repo_download_button': SHOW_REPO_DOWNLOAD_BUTTON,
'share_link_force_use_password': config.SHARE_LINK_FORCE_USE_PASSWORD,
'share_link_password_min_length': config.SHARE_LINK_PASSWORD_MIN_LENGTH, 'share_link_password_min_length': config.SHARE_LINK_PASSWORD_MIN_LENGTH,
'share_link_password_strength_level': config.SHARE_LINK_PASSWORD_STRENGTH_LEVEL,
'repo_password_min_length': config.REPO_PASSWORD_MIN_LENGTH, 'repo_password_min_length': config.REPO_PASSWORD_MIN_LENGTH,
'events_enabled': EVENTS_ENABLED, 'events_enabled': EVENTS_ENABLED,
'sysadmin_extra_enabled': ENABLE_SYSADMIN_EXTRA, 'sysadmin_extra_enabled': ENABLE_SYSADMIN_EXTRA,

View File

@@ -353,8 +353,17 @@ UPLOAD_LINK_EXPIRE_DAYS_MAX = 0 # 0 means no limit
# greater than or equal to MIN and less than or equal to MAX # greater than or equal to MIN and less than or equal to MAX
UPLOAD_LINK_EXPIRE_DAYS_DEFAULT = 0 UPLOAD_LINK_EXPIRE_DAYS_DEFAULT = 0
# mininum length for the password of a share link # force use password when generate a share/upload link
SHARE_LINK_PASSWORD_MIN_LENGTH = 8 SHARE_LINK_FORCE_USE_PASSWORD = False
# mininum length for the password of a share/upload link
SHARE_LINK_PASSWORD_MIN_LENGTH = 10
# LEVEL for the password of a share/upload link
# based on four types of input:
# num, upper letter, lower letter, other symbols
# '3' means password must have at least 3 types of the above.
SHARE_LINK_PASSWORD_STRENGTH_LEVEL = 1
# enable or disable share link audit # enable or disable share link audit
ENABLE_SHARE_LINK_AUDIT = False ENABLE_SHARE_LINK_AUDIT = False
@@ -901,7 +910,9 @@ CONSTANCE_CONFIG = {
'USER_PASSWORD_STRENGTH_LEVEL': (USER_PASSWORD_STRENGTH_LEVEL, ''), 'USER_PASSWORD_STRENGTH_LEVEL': (USER_PASSWORD_STRENGTH_LEVEL, ''),
'SHARE_LINK_TOKEN_LENGTH': (SHARE_LINK_TOKEN_LENGTH, ''), 'SHARE_LINK_TOKEN_LENGTH': (SHARE_LINK_TOKEN_LENGTH, ''),
'SHARE_LINK_FORCE_USE_PASSWORD': (SHARE_LINK_FORCE_USE_PASSWORD, ''),
'SHARE_LINK_PASSWORD_MIN_LENGTH': (SHARE_LINK_PASSWORD_MIN_LENGTH, ''), 'SHARE_LINK_PASSWORD_MIN_LENGTH': (SHARE_LINK_PASSWORD_MIN_LENGTH, ''),
'SHARE_LINK_PASSWORD_STRENGTH_LEVEL': (SHARE_LINK_PASSWORD_STRENGTH_LEVEL, ''),
'ENABLE_TWO_FACTOR_AUTH': (ENABLE_TWO_FACTOR_AUTH, ''), 'ENABLE_TWO_FACTOR_AUTH': (ENABLE_TWO_FACTOR_AUTH, ''),
'TEXT_PREVIEW_EXT': (TEXT_PREVIEW_EXT, ''), 'TEXT_PREVIEW_EXT': (TEXT_PREVIEW_EXT, ''),

View File

@@ -92,7 +92,9 @@
return libraryTemplates; return libraryTemplates;
})(), })(),
enableRepoSnapshotLabel: {% if enable_repo_snapshot_label %} true {% else %} false {% endif %}, enableRepoSnapshotLabel: {% if enable_repo_snapshot_label %} true {% else %} false {% endif %},
shareLinkForceUsePassword: {% if share_link_force_use_password %} true {% else %} false {% endif %},
shareLinkPasswordMinLength: {{ share_link_password_min_length }}, shareLinkPasswordMinLength: {{ share_link_password_min_length }},
shareLinkPasswordStrengthLevel: {{ share_link_password_strength_level }},
sideNavFooterCustomHtml: "{{ side_nav_footer_custom_html|safe|escapejs }}", sideNavFooterCustomHtml: "{{ side_nav_footer_custom_html|safe|escapejs }}",
aboutDialogCustomHtml: "{{ about_dialog_custom_html|safe|escapejs }}", aboutDialogCustomHtml: "{{ about_dialog_custom_html|safe|escapejs }}",
maxFileName: "{{ max_file_name }}", maxFileName: "{{ max_file_name }}",

View File

@@ -13,7 +13,9 @@ window.app.pageOptions = {
canGenerateShareLink: {% if user.permissions.can_generate_share_link %} true {% else %} false {% endif %}, canGenerateShareLink: {% if user.permissions.can_generate_share_link %} true {% else %} false {% endif %},
canSendShareLinkEmail: {% if user.permissions.can_send_share_link_mail %} true {% else %} false {% endif %}, canSendShareLinkEmail: {% if user.permissions.can_send_share_link_mail %} true {% else %} false {% endif %},
shareLinkForceUsePassword: {% if share_link_force_use_password %} true {% else %} false {% endif %},
shareLinkPasswordMinLength: {{ share_link_password_min_length }}, shareLinkPasswordMinLength: {{ share_link_password_min_length }},
shareLinkPasswordStrengthLevel: {{ share_link_password_strength_level }},
shareLinkExpireDaysDefault: {{ share_link_expire_days_default }}, shareLinkExpireDaysDefault: {{ share_link_expire_days_default }},
shareLinkExpireDaysMin: {{ share_link_expire_days_min }}, shareLinkExpireDaysMin: {{ share_link_expire_days_min }},
shareLinkExpireDaysMax: {{ share_link_expire_days_max }}, shareLinkExpireDaysMax: {{ share_link_expire_days_max }},

View File

@@ -47,7 +47,9 @@
hasDraft: '{{ has_draft }}' === 'True', hasDraft: '{{ has_draft }}' === 'True',
draftFilePath: '{{ draft_file_path }}', draftFilePath: '{{ draft_file_path }}',
draftOriginFilePath: '{{ draft_origin_file_path }}', draftOriginFilePath: '{{ draft_origin_file_path }}',
shareLinkForceUsePassword: {% if share_link_force_use_password %} true {% else %} false {% endif %},
shareLinkPasswordMinLength: {{ share_link_password_min_length }}, shareLinkPasswordMinLength: {{ share_link_password_min_length }},
shareLinkPasswordStrengthLevel: {{ share_link_password_strength_level }},
shareLinkExpireDaysDefault: {{ share_link_expire_days_default }}, shareLinkExpireDaysDefault: {{ share_link_expire_days_default }},
shareLinkExpireDaysMin: {{ share_link_expire_days_min }}, shareLinkExpireDaysMin: {{ share_link_expire_days_min }},
shareLinkExpireDaysMax: {{ share_link_expire_days_max }}, shareLinkExpireDaysMax: {{ share_link_expire_days_max }},

View File

@@ -3,19 +3,18 @@
from functools import partial from functools import partial
import os import os
import re import re
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.request, urllib.error, urllib.parse import urllib.parse
import urllib.error
import uuid import uuid
import logging import logging
import hashlib import hashlib
import tempfile import tempfile
import locale
import configparser import configparser
import mimetypes import mimetypes
import contextlib import contextlib
from datetime import datetime from datetime import datetime
from urllib.parse import urlparse, urljoin from urllib.parse import urlparse, urljoin
import json
from constance import config from constance import config
import seaserv import seaserv
@@ -24,9 +23,9 @@ from seaserv import seafile_api
from django.urls import reverse from django.urls import reverse
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.shortcuts import render from django.shortcuts import render
from django.template import Context, loader from django.template import loader
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseNotModified from django.http import HttpResponseRedirect, HttpResponse
from django.utils.http import urlquote from django.utils.http import urlquote
from django.utils.html import escape from django.utils.html import escape
from django.utils.timezone import make_naive, is_aware from django.utils.timezone import make_naive, is_aware
@@ -35,7 +34,7 @@ from django.views.static import serve as django_static_serve
from seahub.auth import REDIRECT_FIELD_NAME from seahub.auth import REDIRECT_FIELD_NAME
from seahub.api2.models import Token, TokenV2 from seahub.api2.models import Token, TokenV2
import seahub.settings import seahub.settings
from seahub.settings import SITE_NAME, MEDIA_URL, LOGO_PATH, \ from seahub.settings import MEDIA_URL, LOGO_PATH, \
MEDIA_ROOT, CUSTOM_LOGO_PATH MEDIA_ROOT, CUSTOM_LOGO_PATH
try: try:
from seahub.settings import EVENTS_CONFIG_FILE from seahub.settings import EVENTS_CONFIG_FILE
@@ -1254,6 +1253,16 @@ def is_user_password_strong(password):
else: else:
return True return True
def get_password_strength_level(password):
num = 0
for letter in password:
# get ascii dec
# bitwise OR
num |= get_char_mode(ord(letter))
return calculate_bitwise(num)
def get_char_mode(n): def get_char_mode(n):
"""Return different num according to the type of given letter: """Return different num according to the type of given letter:
'1': num, '1': num,

View File

@@ -10,7 +10,9 @@ import json
import time import time
import uuid import uuid
import stat import stat
import urllib.request, urllib.error, urllib.parse import urllib.request
import urllib.error
import urllib.parse
import chardet import chardet
import logging import logging
import posixpath import posixpath
@@ -45,7 +47,7 @@ from seahub.onlyoffice.utils import get_onlyoffice_dict
from seahub.auth.decorators import login_required from seahub.auth.decorators import login_required
from seahub.base.decorators import repo_passwd_set_required from seahub.base.decorators import repo_passwd_set_required
from seahub.base.accounts import ANONYMOUS_EMAIL from seahub.base.accounts import ANONYMOUS_EMAIL
from seahub.base.templatetags.seahub_tags import file_icon_filter, email2nickname from seahub.base.templatetags.seahub_tags import file_icon_filter
from seahub.share.models import FileShare, check_share_link_common from seahub.share.models import FileShare, check_share_link_common
from seahub.share.decorators import share_link_audit, share_link_login_required from seahub.share.decorators import share_link_audit, share_link_login_required
from seahub.wiki.utils import get_wiki_dirent from seahub.wiki.utils import get_wiki_dirent
@@ -87,6 +89,7 @@ import seahub.settings as settings
from seahub.settings import FILE_ENCODING_LIST, FILE_PREVIEW_MAX_SIZE, \ from seahub.settings import FILE_ENCODING_LIST, FILE_PREVIEW_MAX_SIZE, \
FILE_ENCODING_TRY_LIST, MEDIA_URL, SEAFILE_COLLAB_SERVER, ENABLE_WATERMARK, \ FILE_ENCODING_TRY_LIST, MEDIA_URL, SEAFILE_COLLAB_SERVER, ENABLE_WATERMARK, \
SHARE_LINK_EXPIRE_DAYS_MIN, SHARE_LINK_EXPIRE_DAYS_MAX, SHARE_LINK_PASSWORD_MIN_LENGTH, \ SHARE_LINK_EXPIRE_DAYS_MIN, SHARE_LINK_EXPIRE_DAYS_MAX, SHARE_LINK_PASSWORD_MIN_LENGTH, \
SHARE_LINK_FORCE_USE_PASSWORD, SHARE_LINK_PASSWORD_STRENGTH_LEVEL, \
SHARE_LINK_EXPIRE_DAYS_DEFAULT, ENABLE_SHARE_LINK_REPORT_ABUSE SHARE_LINK_EXPIRE_DAYS_DEFAULT, ENABLE_SHARE_LINK_REPORT_ABUSE
@@ -564,7 +567,9 @@ def view_lib_file(request, repo_id, path):
'highlight_keyword': settings.HIGHLIGHT_KEYWORD, 'highlight_keyword': settings.HIGHLIGHT_KEYWORD,
'enable_file_comment': settings.ENABLE_FILE_COMMENT, 'enable_file_comment': settings.ENABLE_FILE_COMMENT,
'enable_watermark': ENABLE_WATERMARK, 'enable_watermark': ENABLE_WATERMARK,
'share_link_force_use_password': SHARE_LINK_FORCE_USE_PASSWORD,
'share_link_password_min_length': SHARE_LINK_PASSWORD_MIN_LENGTH, 'share_link_password_min_length': SHARE_LINK_PASSWORD_MIN_LENGTH,
'share_link_password_strength_level': SHARE_LINK_PASSWORD_STRENGTH_LEVEL,
'share_link_expire_days_default': SHARE_LINK_EXPIRE_DAYS_DEFAULT, 'share_link_expire_days_default': SHARE_LINK_EXPIRE_DAYS_DEFAULT,
'share_link_expire_days_min': SHARE_LINK_EXPIRE_DAYS_MIN, 'share_link_expire_days_min': SHARE_LINK_EXPIRE_DAYS_MIN,
'share_link_expire_days_max': SHARE_LINK_EXPIRE_DAYS_MAX, 'share_link_expire_days_max': SHARE_LINK_EXPIRE_DAYS_MAX,