mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-27 11:10:10 +00:00
Merge branch 'master' into 12.0
This commit is contained in:
commit
8bcc05a6cd
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -25,8 +25,8 @@ jobs:
|
|||||||
|
|
||||||
- name: clone and build
|
- name: clone and build
|
||||||
run: |
|
run: |
|
||||||
git clone --depth=1 --branch=master https://github.com/haiwen/seafile-test-deploy /tmp/seafile-test-deploy
|
git clone --depth=1 --branch=11.0 https://github.com/haiwen/seafile-test-deploy /tmp/seafile-test-deploy
|
||||||
cd /tmp/seafile-test-deploy && git fetch origin master:master && git checkout master
|
cd /tmp/seafile-test-deploy && git fetch origin 11.0:11.0 && git checkout 11.0
|
||||||
./bootstrap.sh
|
./bootstrap.sh
|
||||||
|
|
||||||
- name: pip install
|
- name: pip install
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Modal, ModalHeader, ModalBody, TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
|
import { Modal, ModalHeader, ModalBody, TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
|
||||||
import { gettext, username, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, additionalShareDialogNote, enableOCM, isPro } from '../../utils/constants';
|
import { gettext, username, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, additionalShareDialogNote, enableOCM, isPro, canShareRepo } from '../../utils/constants';
|
||||||
import ShareLinkPanel from '../share-link-panel';
|
import ShareLinkPanel from '../share-link-panel';
|
||||||
import GenerateUploadLink from './generate-upload-link';
|
import GenerateUploadLink from './generate-upload-link';
|
||||||
import ShareToUser from './share-to-user';
|
import ShareToUser from './share-to-user';
|
||||||
@ -131,17 +131,21 @@ class ShareDialog extends React.Component {
|
|||||||
}
|
}
|
||||||
{enableDirPrivateShare &&
|
{enableDirPrivateShare &&
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
{ canShareRepo && (
|
||||||
<NavItem role="tab" aria-selected={activeTab === 'shareToUser'} aria-controls="share-to-user-panel">
|
<NavItem role="tab" aria-selected={activeTab === 'shareToUser'} aria-controls="share-to-user-panel">
|
||||||
<NavLink className={activeTab === 'shareToUser' ? 'active' : ''} onClick={this.toggle.bind(this, 'shareToUser')} tabIndex="0" onKeyDown={this.onTabKeyDown}>
|
<NavLink className={activeTab === 'shareToUser' ? 'active' : ''} onClick={this.toggle.bind(this, 'shareToUser')} tabIndex="0" onKeyDown={this.onTabKeyDown}>
|
||||||
{gettext('Share to user')}
|
{gettext('Share to user')}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
)}
|
||||||
|
{ canShareRepo && (
|
||||||
<NavItem role="tab" aria-selected={activeTab === 'shareToGroup'} aria-controls="share-to-group-panel">
|
<NavItem role="tab" aria-selected={activeTab === 'shareToGroup'} aria-controls="share-to-group-panel">
|
||||||
<NavLink className={activeTab === 'shareToGroup' ? 'active' : ''} onClick={this.toggle.bind(this, 'shareToGroup')} tabIndex="0" onKeyDown={this.onTabKeyDown}>
|
<NavLink className={activeTab === 'shareToGroup' ? 'active' : ''} onClick={this.toggle.bind(this, 'shareToGroup')} tabIndex="0" onKeyDown={this.onTabKeyDown}>
|
||||||
{gettext('Share to group')}
|
{gettext('Share to group')}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
{isPro && !isCustomPermission && (
|
)}
|
||||||
|
{isPro && !isCustomPermission && canShareRepo && (
|
||||||
<NavItem role="tab" aria-selected={activeTab === 'customSharePermission'} aria-controls="custom-share-perm-panel">
|
<NavItem role="tab" aria-selected={activeTab === 'customSharePermission'} aria-controls="custom-share-perm-panel">
|
||||||
<NavLink className={activeTab === 'customSharePermission' ? 'active' : ''} onClick={this.toggle.bind(this, 'customSharePermission')} tabIndex="0" onKeyDown={this.onTabKeyDown}>
|
<NavLink className={activeTab === 'customSharePermission' ? 'active' : ''} onClick={this.toggle.bind(this, 'customSharePermission')} tabIndex="0" onKeyDown={this.onTabKeyDown}>
|
||||||
{gettext('Custom sharing permissions')}
|
{gettext('Custom sharing permissions')}
|
||||||
@ -307,7 +311,7 @@ class ShareDialog extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="external-share-message mt-2">
|
<div className="external-share-message mt-2">
|
||||||
<h6>{additionalShareDialogNote.title}</h6>
|
<h6>{additionalShareDialogNote.title}</h6>
|
||||||
<div style={{fontSize: '14px', color: '#666'}}>{additionalShareDialogNote.content}</div>
|
<p style={{fontSize: '14px', color: '#666'}} className="text-wrap m-0">{additionalShareDialogNote.content}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -319,8 +323,8 @@ class ShareDialog extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Modal isOpen={true} style={{maxWidth: '760px'}} className="share-dialog" toggle={this.props.toggleDialog}>
|
<Modal isOpen={true} style={{maxWidth: '760px'}} className="share-dialog" toggle={this.props.toggleDialog}>
|
||||||
<ModalHeader toggle={this.props.toggleDialog}>
|
<ModalHeader toggle={this.props.toggleDialog} tag="div">
|
||||||
{gettext('Share')} <span className="op-target" title={itemName}>{itemName}</span>
|
<h5 className="text-truncate">{gettext('Share')} <span className="op-target" title={itemName}>{itemName}</span></h5>
|
||||||
{this.renderExternalShareMessage()}
|
{this.renderExternalShareMessage()}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody className="share-dialog-content" role="tablist">
|
<ModalBody className="share-dialog-content" role="tablist">
|
||||||
|
@ -2,8 +2,8 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Link } from '@gatsbyjs/reach-router';
|
import { Link } from '@gatsbyjs/reach-router';
|
||||||
import {
|
import {
|
||||||
gettext, siteRoot, canAddGroup,
|
gettext, siteRoot, canAddGroup, canAddRepo, canShareRepo,
|
||||||
canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople,
|
canGenerateShareLink, canGenerateUploadLink, canInvitePeople,
|
||||||
enableTC, sideNavFooterCustomHtml, additionalAppBottomLinks,
|
enableTC, sideNavFooterCustomHtml, additionalAppBottomLinks,
|
||||||
canViewOrg, isDocs, isPro, isDBSqlite3, customNavItems
|
canViewOrg, isDocs, isPro, isDBSqlite3, customNavItems
|
||||||
} from '../utils/constants';
|
} from '../utils/constants';
|
||||||
@ -166,7 +166,7 @@ class MainSideNav extends React.Component {
|
|||||||
className={`nav sub-nav nav-pills flex-column ${this.state.sharedExtended ? 'side-panel-slide-share-admin' : 'side-panel-slide-up-share-admin'}`}
|
className={`nav sub-nav nav-pills flex-column ${this.state.sharedExtended ? 'side-panel-slide-share-admin' : 'side-panel-slide-up-share-admin'}`}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{canAddRepo && (
|
{canAddRepo && canShareRepo && (
|
||||||
<li className={`nav-item ${this.getActiveClass('share-admin-libs')}`}>
|
<li className={`nav-item ${this.getActiveClass('share-admin-libs')}`}>
|
||||||
<Link to={siteRoot + 'share-admin-libs/'} className={`nav-link ellipsis ${this.getActiveClass('share-admin-libs')}`} title={gettext('Libraries')} onClick={(e) => this.tabItemClick(e, 'share-admin-libs')}>
|
<Link to={siteRoot + 'share-admin-libs/'} className={`nav-link ellipsis ${this.getActiveClass('share-admin-libs')}`} title={gettext('Libraries')} onClick={(e) => this.tabItemClick(e, 'share-admin-libs')}>
|
||||||
<span aria-hidden="true" className="sharp">#</span>
|
<span aria-hidden="true" className="sharp">#</span>
|
||||||
@ -174,12 +174,14 @@ class MainSideNav extends React.Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
{canShareRepo && (
|
||||||
<li className={`nav-item ${this.getActiveClass('share-admin-folders')}`}>
|
<li className={`nav-item ${this.getActiveClass('share-admin-folders')}`}>
|
||||||
<Link to={siteRoot + 'share-admin-folders/'} className={`nav-link ellipsis ${this.getActiveClass('share-admin-folders')}`} title={gettext('Folders')} onClick={(e) => this.tabItemClick(e, 'share-admin-folders')}>
|
<Link to={siteRoot + 'share-admin-folders/'} className={`nav-link ellipsis ${this.getActiveClass('share-admin-folders')}`} title={gettext('Folders')} onClick={(e) => this.tabItemClick(e, 'share-admin-folders')}>
|
||||||
<span aria-hidden="true" className="sharp">#</span>
|
<span aria-hidden="true" className="sharp">#</span>
|
||||||
<span className="nav-text">{gettext('Folders')}</span>
|
<span className="nav-text">{gettext('Folders')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
)}
|
||||||
{linksNavItem}
|
{linksNavItem}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
@ -16,7 +16,7 @@ class TrafficTableBody extends React.Component {
|
|||||||
case 'user':
|
case 'user':
|
||||||
if (userTrafficItem.name) {
|
if (userTrafficItem.name) {
|
||||||
return (
|
return (
|
||||||
<a href={siteRoot + 'useradmin/info/' + userTrafficItem.email + '/'}>{userTrafficItem.name}</a>
|
<a href={siteRoot + 'org/useradmin/info/' + userTrafficItem.email + '/'}>{userTrafficItem.name}</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return(<span>{'--'}</span>);
|
return(<span>{'--'}</span>);
|
||||||
|
@ -16,7 +16,7 @@ class TrafficTableBody extends React.Component {
|
|||||||
case 'user':
|
case 'user':
|
||||||
if (userTrafficItem.name) {
|
if (userTrafficItem.name) {
|
||||||
return (
|
return (
|
||||||
<a href={siteRoot + 'useradmin/info/' + userTrafficItem.email + '/'}>{userTrafficItem.name}</a>
|
<a href={siteRoot + 'sys/users/' + userTrafficItem.email + '/'}>{userTrafficItem.name}</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return(<span>{'--'}</span>);
|
return(<span>{'--'}</span>);
|
||||||
|
@ -37,6 +37,7 @@ export const name = window.app.pageOptions.name;
|
|||||||
export const contactEmail = window.app.pageOptions.contactEmail;
|
export const contactEmail = window.app.pageOptions.contactEmail;
|
||||||
export const username = window.app.pageOptions.username;
|
export const username = window.app.pageOptions.username;
|
||||||
export const canAddRepo = window.app.pageOptions.canAddRepo;
|
export const canAddRepo = window.app.pageOptions.canAddRepo;
|
||||||
|
export const canShareRepo = window.app.pageOptions.canShareRepo;
|
||||||
export const canAddGroup = window.app.pageOptions.canAddGroup;
|
export const canAddGroup = window.app.pageOptions.canAddGroup;
|
||||||
export const groupImportMembersExtraMsg = window.app.pageOptions.groupImportMembersExtraMsg;
|
export const groupImportMembersExtraMsg = window.app.pageOptions.groupImportMembersExtraMsg;
|
||||||
export const canGenerateShareLink = window.app.pageOptions.canGenerateShareLink;
|
export const canGenerateShareLink = window.app.pageOptions.canGenerateShareLink;
|
||||||
|
@ -112,7 +112,11 @@ log "Start Monitor"
|
|||||||
|
|
||||||
while [ 1 ]; do
|
while [ 1 ]; do
|
||||||
|
|
||||||
|
if [ $CLUSTER_MODE ] && [ $CLUSTER_MODE = "backend" ]; then
|
||||||
|
:
|
||||||
|
else
|
||||||
monitor_seafevents
|
monitor_seafevents
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $ENABLE_NOTIFICATION_SERVER ] && [ $ENABLE_NOTIFICATION_SERVER = "true" ]; then
|
if [ $ENABLE_NOTIFICATION_SERVER ] && [ $ENABLE_NOTIFICATION_SERVER = "true" ]; then
|
||||||
monitor_notification_server
|
monitor_notification_server
|
||||||
|
@ -20,6 +20,7 @@ from collections import OrderedDict
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.backends import ModelBackend
|
from django.contrib.auth.backends import ModelBackend
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
from seaserv import ccnet_api, seafile_api
|
from seaserv import ccnet_api, seafile_api
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
SAML_PROVIDER_IDENTIFIER = getattr(settings, 'SAML_PROVIDER_IDENTIFIER', 'saml')
|
SAML_PROVIDER_IDENTIFIER = getattr(settings, 'SAML_PROVIDER_IDENTIFIER', 'saml')
|
||||||
SHIBBOLETH_AFFILIATION_ROLE_MAP = getattr(settings, 'SHIBBOLETH_AFFILIATION_ROLE_MAP', {})
|
SHIBBOLETH_AFFILIATION_ROLE_MAP = getattr(settings, 'SHIBBOLETH_AFFILIATION_ROLE_MAP', {})
|
||||||
|
CACHE_KEY_GROUPS = "all_groups_cache"
|
||||||
|
|
||||||
|
|
||||||
class Saml2Backend(ModelBackend):
|
class Saml2Backend(ModelBackend):
|
||||||
@ -196,9 +198,27 @@ class Saml2Backend(ModelBackend):
|
|||||||
|
|
||||||
# support a list of comma-separated IDs as seafile_groups claim
|
# support a list of comma-separated IDs as seafile_groups claim
|
||||||
if len(seafile_groups) == 1 and ',' in seafile_groups[0]:
|
if len(seafile_groups) == 1 and ',' in seafile_groups[0]:
|
||||||
seafile_groups = seafile_groups[0].split(',')
|
seafile_groups = [group.strip() for group in seafile_groups[0].split(',')]
|
||||||
|
|
||||||
|
if all(str(group_id).isdigit() for group_id in seafile_groups):
|
||||||
|
# all groups are provided as numeric IDs
|
||||||
saml_group_ids = [int(group_id) for group_id in seafile_groups]
|
saml_group_ids = [int(group_id) for group_id in seafile_groups]
|
||||||
|
else:
|
||||||
|
# groups are provided as names, try to get current group information from cache
|
||||||
|
all_groups = cache.get(CACHE_KEY_GROUPS)
|
||||||
|
if not all_groups or any(group not in all_groups for group in seafile_groups):
|
||||||
|
# groups not yet cached or missing entry, reload groups from API
|
||||||
|
all_groups = {group.group_name: group.id for group in ccnet_api.get_all_groups(-1, -1)}
|
||||||
|
cache.set(CACHE_KEY_GROUPS, all_groups, 3600) # cache for 1 hour
|
||||||
|
# create groups which are not yet existing
|
||||||
|
for group in [group_name for group_name in seafile_groups if group_name not in all_groups]:
|
||||||
|
new_group = ccnet_api.create_group(group, 'system admin') # we are not operating in user context here
|
||||||
|
if new_group < 0:
|
||||||
|
logger.error('failed to create group %s' % group)
|
||||||
|
return
|
||||||
|
all_groups[group] = new_group
|
||||||
|
# generate numeric IDs from group names
|
||||||
|
saml_group_ids = [id for group, id in all_groups.items() if group in seafile_groups]
|
||||||
|
|
||||||
joined_groups = ccnet_api.get_groups(user.username)
|
joined_groups = ccnet_api.get_groups(user.username)
|
||||||
joined_group_ids = [g.id for g in joined_groups]
|
joined_group_ids = [g.id for g in joined_groups]
|
||||||
|
@ -22,9 +22,12 @@ from ldap.controls.libldap import SimplePagedResultsControl
|
|||||||
from seaserv import seafile_api, ccnet_api
|
from seaserv import seafile_api, ccnet_api
|
||||||
|
|
||||||
from seahub.api2.authentication import TokenAuthentication
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
|
from seahub.api2.endpoints.utils import is_org_user
|
||||||
from seahub.api2.throttling import UserRateThrottle
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
from seahub.api2.utils import api_error, to_python_boolean, get_user_common_info
|
from seahub.api2.utils import api_error, to_python_boolean, get_user_common_info
|
||||||
from seahub.api2.models import TokenV2
|
from seahub.api2.models import TokenV2
|
||||||
|
from seahub.organizations.models import OrgSettings
|
||||||
|
from seahub.organizations.views import gen_org_url_prefix
|
||||||
from seahub.utils.ccnet_db import get_ccnet_db_name
|
from seahub.utils.ccnet_db import get_ccnet_db_name
|
||||||
import seahub.settings as settings
|
import seahub.settings as settings
|
||||||
from seahub.settings import SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER, INIT_PASSWD, \
|
from seahub.settings import SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER, INIT_PASSWD, \
|
||||||
@ -40,6 +43,7 @@ from seahub.utils import is_valid_username2, is_org_context, \
|
|||||||
is_pro_version, normalize_cache_key, is_valid_email, \
|
is_pro_version, normalize_cache_key, is_valid_email, \
|
||||||
IS_EMAIL_CONFIGURED, send_html_email, get_site_name, \
|
IS_EMAIL_CONFIGURED, send_html_email, get_site_name, \
|
||||||
gen_shared_link, gen_shared_upload_link
|
gen_shared_link, gen_shared_upload_link
|
||||||
|
from seahub.utils.db_api import SeafileDB
|
||||||
|
|
||||||
from seahub.utils.file_size import get_file_size_unit, byte_to_kb
|
from seahub.utils.file_size import get_file_size_unit, byte_to_kb
|
||||||
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \
|
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \
|
||||||
@ -48,6 +52,7 @@ from seahub.utils.user_permissions import get_user_role
|
|||||||
from seahub.utils.repo import normalize_repo_status_code
|
from seahub.utils.repo import normalize_repo_status_code
|
||||||
from seahub.utils.ccnet_db import CcnetDB
|
from seahub.utils.ccnet_db import CcnetDB
|
||||||
from seahub.constants import DEFAULT_ADMIN
|
from seahub.constants import DEFAULT_ADMIN
|
||||||
|
from seahub.constants import DEFAULT_ADMIN, DEFAULT_ORG
|
||||||
from seahub.role_permissions.models import AdminRole
|
from seahub.role_permissions.models import AdminRole
|
||||||
from seahub.role_permissions.utils import get_available_roles
|
from seahub.role_permissions.utils import get_available_roles
|
||||||
from seahub.utils.licenseparse import user_number_over_limit
|
from seahub.utils.licenseparse import user_number_over_limit
|
||||||
@ -2139,3 +2144,61 @@ class AdminUserList(APIView):
|
|||||||
user_list.append(user_info)
|
user_list.append(user_info)
|
||||||
|
|
||||||
return Response({'user_list': user_list})
|
return Response({'user_list': user_list})
|
||||||
|
|
||||||
|
|
||||||
|
class AdminUserConvertToTeamView(APIView):
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAdminUser,)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
username = request.data.get('email')
|
||||||
|
if not username:
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, 'email invalid.')
|
||||||
|
|
||||||
|
# resource check
|
||||||
|
try:
|
||||||
|
user = User.objects.get(email=username)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
error_msg = 'User %s not found.' % username
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
if is_org_user(username):
|
||||||
|
error_msg = 'User is already in team.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
org_role = DEFAULT_ORG
|
||||||
|
|
||||||
|
url_prefix = gen_org_url_prefix(3)
|
||||||
|
if url_prefix is None:
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
# Use the nickname as the org_name, but the org_name does not support emoji
|
||||||
|
nickname = email2nickname(username)
|
||||||
|
nickname_characters = []
|
||||||
|
for character in nickname:
|
||||||
|
if len(character.encode('utf-8')) > 3:
|
||||||
|
nickname_characters.append('_')
|
||||||
|
else:
|
||||||
|
nickname_characters.append(character)
|
||||||
|
org_name = ''.join(nickname_characters)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. Create a new org, and add the user(username) to org as a team admin
|
||||||
|
# by ccnet_api.create_org
|
||||||
|
org_id = ccnet_api.create_org(org_name, url_prefix, username)
|
||||||
|
# 2. Update org-settings
|
||||||
|
new_org = ccnet_api.get_org_by_id(org_id)
|
||||||
|
OrgSettings.objects.add_or_update(new_org, org_role)
|
||||||
|
# 3. Add user's repo to OrgRepo
|
||||||
|
owned_repos = seafile_api.get_owned_repo_list(username, ret_corrupted=True)
|
||||||
|
owned_repo_ids = [item.repo_id for item in owned_repos]
|
||||||
|
seafile_db = SeafileDB()
|
||||||
|
seafile_db.add_repos_to_org_user(org_id, username, owned_repo_ids)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
return Response({'success': True})
|
||||||
|
@ -29,6 +29,9 @@ class CustomSharePermissionsView(APIView):
|
|||||||
"""List custom share permissions
|
"""List custom share permissions
|
||||||
"""
|
"""
|
||||||
# permission check
|
# permission check
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
if not check_folder_permission(request, repo_id, '/'):
|
if not check_folder_permission(request, repo_id, '/'):
|
||||||
error_msg = 'Permission denied.'
|
error_msg = 'Permission denied.'
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
@ -53,6 +56,9 @@ class CustomSharePermissionsView(APIView):
|
|||||||
def post(self, request, repo_id):
|
def post(self, request, repo_id):
|
||||||
"""Add a custom share permission
|
"""Add a custom share permission
|
||||||
"""
|
"""
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
username = request.user.username
|
username = request.user.username
|
||||||
# argument check
|
# argument check
|
||||||
permission = request.data.get('permission', None)
|
permission = request.data.get('permission', None)
|
||||||
@ -98,6 +104,9 @@ class CustomSharePermissionView(APIView):
|
|||||||
"""get a custom share permission
|
"""get a custom share permission
|
||||||
"""
|
"""
|
||||||
# permission check
|
# permission check
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
if not check_folder_permission(request, repo_id, '/'):
|
if not check_folder_permission(request, repo_id, '/'):
|
||||||
error_msg = 'Permission denied.'
|
error_msg = 'Permission denied.'
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
@ -122,6 +131,9 @@ class CustomSharePermissionView(APIView):
|
|||||||
"""Update a custom share permission
|
"""Update a custom share permission
|
||||||
"""
|
"""
|
||||||
# argument check
|
# argument check
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
permission = request.data.get('permission', None)
|
permission = request.data.get('permission', None)
|
||||||
if not permission:
|
if not permission:
|
||||||
error_msg = 'permission invalid.'
|
error_msg = 'permission invalid.'
|
||||||
@ -170,6 +182,9 @@ class CustomSharePermissionView(APIView):
|
|||||||
def delete(self, request, repo_id, permission_id):
|
def delete(self, request, repo_id, permission_id):
|
||||||
"""Delete a custom share permission
|
"""Delete a custom share permission
|
||||||
"""
|
"""
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
username = request.user.username
|
username = request.user.username
|
||||||
|
|
||||||
# permission check
|
# permission check
|
||||||
|
@ -61,10 +61,13 @@ class DirSharedItemsEndpoint(APIView):
|
|||||||
org_id = request.user.org.org_id
|
org_id = request.user.org.org_id
|
||||||
if path == '/':
|
if path == '/':
|
||||||
share_items = seafile_api.list_org_repo_shared_to(org_id,
|
share_items = seafile_api.list_org_repo_shared_to(org_id,
|
||||||
repo_owner, repo_id)
|
repo_owner,
|
||||||
|
repo_id)
|
||||||
else:
|
else:
|
||||||
share_items = seafile_api.get_org_shared_users_for_subdir(org_id,
|
share_items = seafile_api.get_org_shared_users_for_subdir(org_id,
|
||||||
repo_id, path, repo_owner)
|
repo_id,
|
||||||
|
path,
|
||||||
|
repo_owner)
|
||||||
else:
|
else:
|
||||||
repo_owner = seafile_api.get_repo_owner(repo_id)
|
repo_owner = seafile_api.get_repo_owner(repo_id)
|
||||||
if path == '/':
|
if path == '/':
|
||||||
@ -98,10 +101,13 @@ class DirSharedItemsEndpoint(APIView):
|
|||||||
org_id = request.user.org.org_id
|
org_id = request.user.org.org_id
|
||||||
if path == '/':
|
if path == '/':
|
||||||
share_items = seafile_api.list_org_repo_shared_group(org_id,
|
share_items = seafile_api.list_org_repo_shared_group(org_id,
|
||||||
repo_owner, repo_id)
|
repo_owner,
|
||||||
|
repo_id)
|
||||||
else:
|
else:
|
||||||
share_items = seafile_api.get_org_shared_groups_for_subdir(org_id,
|
share_items = seafile_api.get_org_shared_groups_for_subdir(org_id,
|
||||||
repo_id, path, repo_owner)
|
repo_id,
|
||||||
|
path,
|
||||||
|
repo_owner)
|
||||||
else:
|
else:
|
||||||
repo_owner = seafile_api.get_repo_owner(repo_id)
|
repo_owner = seafile_api.get_repo_owner(repo_id)
|
||||||
if path == '/':
|
if path == '/':
|
||||||
@ -129,11 +135,10 @@ class DirSharedItemsEndpoint(APIView):
|
|||||||
org_id, repo_id, path, repo_owner, group_id)
|
org_id, repo_id, path, repo_owner, group_id)
|
||||||
else:
|
else:
|
||||||
if path == '/':
|
if path == '/':
|
||||||
seafile_api.unset_group_repo(repo_id, group_id,
|
seafile_api.unset_group_repo(repo_id, group_id, repo_owner)
|
||||||
repo_owner)
|
|
||||||
else:
|
else:
|
||||||
seafile_api.unshare_subdir_for_group(
|
seafile_api.unshare_subdir_for_group(repo_id, path,
|
||||||
repo_id, path, repo_owner, group_id)
|
repo_owner, group_id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ret.append({
|
ret.append({
|
||||||
@ -197,6 +202,9 @@ class DirSharedItemsEndpoint(APIView):
|
|||||||
def get(self, request, repo_id, format=None):
|
def get(self, request, repo_id, format=None):
|
||||||
"""List shared items(shared to users/groups) for a folder/library.
|
"""List shared items(shared to users/groups) for a folder/library.
|
||||||
"""
|
"""
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
repo = seafile_api.get_repo(repo_id)
|
repo = seafile_api.get_repo(repo_id)
|
||||||
if not repo:
|
if not repo:
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, 'Library %s not found.' % repo_id)
|
return api_error(status.HTTP_404_NOT_FOUND, 'Library %s not found.' % repo_id)
|
||||||
@ -220,6 +228,9 @@ class DirSharedItemsEndpoint(APIView):
|
|||||||
def post(self, request, repo_id, format=None):
|
def post(self, request, repo_id, format=None):
|
||||||
"""Update shared item permission.
|
"""Update shared item permission.
|
||||||
"""
|
"""
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
username = request.user.username
|
username = request.user.username
|
||||||
repo = seafile_api.get_repo(repo_id)
|
repo = seafile_api.get_repo(repo_id)
|
||||||
if not repo:
|
if not repo:
|
||||||
@ -305,6 +316,10 @@ class DirSharedItemsEndpoint(APIView):
|
|||||||
content_type=json_content_type)
|
content_type=json_content_type)
|
||||||
|
|
||||||
def put(self, request, repo_id, format=None):
|
def put(self, request, repo_id, format=None):
|
||||||
|
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
username = request.user.username
|
username = request.user.username
|
||||||
repo = seafile_api.get_repo(repo_id)
|
repo = seafile_api.get_repo(repo_id)
|
||||||
if not repo:
|
if not repo:
|
||||||
@ -368,8 +383,7 @@ class DirSharedItemsEndpoint(APIView):
|
|||||||
|
|
||||||
if not is_org_user(to_user, int(org_id)):
|
if not is_org_user(to_user, int(org_id)):
|
||||||
org_name = request.user.org.org_name
|
org_name = request.user.org.org_name
|
||||||
error_msg = 'User %s is not member of organization %s.' \
|
error_msg = f'User {to_user} is not member of organization {org_name}.'
|
||||||
% (to_user, org_name)
|
|
||||||
|
|
||||||
result['failed'].append({
|
result['failed'].append({
|
||||||
'email': to_user,
|
'email': to_user,
|
||||||
@ -377,7 +391,8 @@ class DirSharedItemsEndpoint(APIView):
|
|||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# when calling seafile API to share authority related functions, change the uesrname to repo owner.
|
# when calling seafile API to share authority related functions,
|
||||||
|
# change the uesrname to repo owner.
|
||||||
repo_owner = seafile_api.get_org_repo_owner(repo_id)
|
repo_owner = seafile_api.get_org_repo_owner(repo_id)
|
||||||
# can't share to owner
|
# can't share to owner
|
||||||
if to_user == repo_owner:
|
if to_user == repo_owner:
|
||||||
@ -477,7 +492,8 @@ class DirSharedItemsEndpoint(APIView):
|
|||||||
try:
|
try:
|
||||||
org_id = None
|
org_id = None
|
||||||
if is_org_context(request):
|
if is_org_context(request):
|
||||||
# when calling seafile API to share authority related functions, change the uesrname to repo owner.
|
# when calling seafile API to share authority related functions,
|
||||||
|
# change the uesrname to repo owner.
|
||||||
repo_owner = seafile_api.get_org_repo_owner(repo_id)
|
repo_owner = seafile_api.get_org_repo_owner(repo_id)
|
||||||
org_id = request.user.org.org_id
|
org_id = request.user.org.org_id
|
||||||
|
|
||||||
@ -513,9 +529,14 @@ class DirSharedItemsEndpoint(APIView):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
return HttpResponse(json.dumps(result),
|
return HttpResponse(json.dumps(result),
|
||||||
status=200, content_type=json_content_type)
|
status=200,
|
||||||
|
content_type=json_content_type)
|
||||||
|
|
||||||
def delete(self, request, repo_id, format=None):
|
def delete(self, request, repo_id, format=None):
|
||||||
|
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
username = request.user.username
|
username = request.user.username
|
||||||
repo = seafile_api.get_repo(repo_id)
|
repo = seafile_api.get_repo(repo_id)
|
||||||
if not repo:
|
if not repo:
|
||||||
|
@ -33,6 +33,8 @@ class SharedFolders(APIView):
|
|||||||
Permission checking:
|
Permission checking:
|
||||||
1. all authenticated user can perform this action.
|
1. all authenticated user can perform this action.
|
||||||
"""
|
"""
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
shared_repos = []
|
shared_repos = []
|
||||||
username = request.user.username
|
username = request.user.username
|
||||||
|
@ -38,6 +38,8 @@ class SharedRepos(APIView):
|
|||||||
Permission checking:
|
Permission checking:
|
||||||
1. all authenticated user can perform this action.
|
1. all authenticated user can perform this action.
|
||||||
"""
|
"""
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
shared_repos = []
|
shared_repos = []
|
||||||
username = request.user.username
|
username = request.user.username
|
||||||
@ -129,6 +131,9 @@ class SharedRepo(APIView):
|
|||||||
1. Only repo owner can update.
|
1. Only repo owner can update.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
# argument check
|
# argument check
|
||||||
permission = request.data.get('permission', None)
|
permission = request.data.get('permission', None)
|
||||||
if permission not in get_available_repo_perms():
|
if permission not in get_available_repo_perms():
|
||||||
@ -244,6 +249,8 @@ class SharedRepo(APIView):
|
|||||||
Permission checking:
|
Permission checking:
|
||||||
1. Only repo owner and system admin can unshare a publib library.
|
1. Only repo owner and system admin can unshare a publib library.
|
||||||
"""
|
"""
|
||||||
|
if not request.user.permissions.can_share_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
# argument check
|
# argument check
|
||||||
share_type = request.GET.get('share_type', None)
|
share_type = request.GET.get('share_type', None)
|
||||||
|
@ -8,6 +8,10 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from seahub.constants import DEFAULT_ORG
|
||||||
|
from seahub.organizations.models import OrgSettings
|
||||||
|
from seahub.organizations.settings import ORG_AUTO_URL_PREFIX
|
||||||
|
from seahub.organizations.views import gen_org_url_prefix
|
||||||
from seahub.utils import is_valid_email
|
from seahub.utils import is_valid_email
|
||||||
from seahub.api2.authentication import TokenAuthentication
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
from seahub.api2.throttling import UserRateThrottle
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
@ -15,7 +19,12 @@ from seahub.api2.utils import api_error
|
|||||||
from seahub.base.templatetags.seahub_tags import email2nickname, \
|
from seahub.base.templatetags.seahub_tags import email2nickname, \
|
||||||
email2contact_email
|
email2contact_email
|
||||||
from seahub.profile.models import Profile, DetailedProfile
|
from seahub.profile.models import Profile, DetailedProfile
|
||||||
from seahub.settings import ENABLE_UPDATE_USER_INFO, ENABLE_USER_SET_CONTACT_EMAIL
|
from seahub.settings import ENABLE_UPDATE_USER_INFO, ENABLE_USER_SET_CONTACT_EMAIL, ENABLE_CONVERT_TO_TEAM_ACCOUNT
|
||||||
|
|
||||||
|
import seaserv
|
||||||
|
from seaserv import ccnet_api, seafile_api
|
||||||
|
|
||||||
|
from seahub.utils.db_api import SeafileDB
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
json_content_type = 'application/json; charset=utf-8'
|
json_content_type = 'application/json; charset=utf-8'
|
||||||
@ -138,3 +147,54 @@ class User(APIView):
|
|||||||
# get user info and return
|
# get user info and return
|
||||||
info = self._get_user_info(email)
|
info = self._get_user_info(email)
|
||||||
return Response(info)
|
return Response(info)
|
||||||
|
|
||||||
|
class UserConvertToTeamView(APIView):
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
if not ENABLE_CONVERT_TO_TEAM_ACCOUNT:
|
||||||
|
error_msg = 'Feature is not enabled.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
org_role = DEFAULT_ORG
|
||||||
|
|
||||||
|
if request.user.org:
|
||||||
|
error_msg = 'User is already in team.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
url_prefix = gen_org_url_prefix(3)
|
||||||
|
if url_prefix is None:
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
username = request.user.username
|
||||||
|
# Use the nickname as the org_name, but the org_name does not support emoji
|
||||||
|
nickname = email2nickname(username)
|
||||||
|
nickname_characters = []
|
||||||
|
for character in nickname:
|
||||||
|
if len(character.encode('utf-8')) > 3:
|
||||||
|
nickname_characters.append('_')
|
||||||
|
else:
|
||||||
|
nickname_characters.append(character)
|
||||||
|
org_name = ''.join(nickname_characters)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. Create a new org, and add the current to org as a team admin
|
||||||
|
# by ccnet_api.create_org
|
||||||
|
org_id = ccnet_api.create_org(org_name, url_prefix, username)
|
||||||
|
# 2. Update org-settings
|
||||||
|
new_org = ccnet_api.get_org_by_id(org_id)
|
||||||
|
OrgSettings.objects.add_or_update(new_org, org_role)
|
||||||
|
# 3. Add user's repo to OrgRepo
|
||||||
|
owned_repos = seafile_api.get_owned_repo_list(username, ret_corrupted=True)
|
||||||
|
owned_repo_ids = [item.repo_id for item in owned_repos]
|
||||||
|
seafile_db = SeafileDB()
|
||||||
|
seafile_db.add_repos_to_org_user(org_id, username, owned_repo_ids)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
return Response({'success': True})
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
# Copyright (c) 2012-2016 Seafile Ltd.
|
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||||
import datetime
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import urllib.request, urllib.parse, urllib.error
|
|
||||||
import logging
|
import logging
|
||||||
|
from registration.signals import user_deleted
|
||||||
# import auth
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.manager import EmptyManager
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.utils.encoding import smart_str
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from django.utils.encoding import smart_str
|
||||||
|
from django.db.models.manager import EmptyManager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
UNUSABLE_PASSWORD = '!' # This will never be a valid hash
|
UNUSABLE_PASSWORD = '!' # This will never be a valid hash
|
||||||
@ -130,15 +125,26 @@ class AnonymousUser(object):
|
|||||||
|
|
||||||
|
|
||||||
class SocialAuthUserManager(models.Manager):
|
class SocialAuthUserManager(models.Manager):
|
||||||
|
|
||||||
def add(self, username, provider, uid, extra_data=''):
|
def add(self, username, provider, uid, extra_data=''):
|
||||||
try:
|
try:
|
||||||
social_auth_user = self.model(username=username, provider=provider, uid=uid, extra_data=extra_data)
|
social_auth_user = self.model(username=username, provider=provider,
|
||||||
|
uid=uid, extra_data=extra_data)
|
||||||
social_auth_user.save()
|
social_auth_user.save()
|
||||||
return social_auth_user
|
return social_auth_user
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def add_if_not_exists(self, username, provider, uid, extra_data=''):
|
||||||
|
|
||||||
|
social_auth_user = self.get_by_provider_and_uid(provider, uid)
|
||||||
|
if not social_auth_user:
|
||||||
|
social_auth_user = self.add(username, provider,
|
||||||
|
uid, extra_data=extra_data)
|
||||||
|
|
||||||
|
return social_auth_user
|
||||||
|
|
||||||
def get_by_provider_and_uid(self, provider, uid):
|
def get_by_provider_and_uid(self, provider, uid):
|
||||||
try:
|
try:
|
||||||
social_auth_user = self.get(provider=provider, uid=uid)
|
social_auth_user = self.get(provider=provider, uid=uid)
|
||||||
@ -186,11 +192,7 @@ class ExternalDepartment(models.Model):
|
|||||||
db_table = 'external_department'
|
db_table = 'external_department'
|
||||||
|
|
||||||
|
|
||||||
# # handle signals
|
# handle signals
|
||||||
from django.dispatch import receiver
|
|
||||||
from registration.signals import user_deleted
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_deleted)
|
@receiver(user_deleted)
|
||||||
def user_deleted_cb(sender, **kwargs):
|
def user_deleted_cb(sender, **kwargs):
|
||||||
username = kwargs['username']
|
username = kwargs['username']
|
||||||
|
@ -35,6 +35,7 @@ from seahub.options.models import UserOptions
|
|||||||
from seahub.profile.models import Profile
|
from seahub.profile.models import Profile
|
||||||
from seahub.two_factor.views.login import is_device_remembered
|
from seahub.two_factor.views.login import is_device_remembered
|
||||||
from seahub.utils import render_error, get_site_name, is_valid_email
|
from seahub.utils import render_error, get_site_name, is_valid_email
|
||||||
|
from seahub.utils.http import rate_limit
|
||||||
from seahub.utils.ip import get_remote_ip
|
from seahub.utils.ip import get_remote_ip
|
||||||
from seahub.utils.file_size import get_quota_from_string
|
from seahub.utils.file_size import get_quota_from_string
|
||||||
from seahub.utils.two_factor_auth import two_factor_auth_enabled, handle_two_factor_auth
|
from seahub.utils.two_factor_auth import two_factor_auth_enabled, handle_two_factor_auth
|
||||||
@ -195,10 +196,8 @@ def login(request, template_name='registration/login.html',
|
|||||||
getattr(settings, 'ENABLE_KRB5_LOGIN', False) or \
|
getattr(settings, 'ENABLE_KRB5_LOGIN', False) or \
|
||||||
getattr(settings, 'ENABLE_ADFS_LOGIN', False) or \
|
getattr(settings, 'ENABLE_ADFS_LOGIN', False) or \
|
||||||
getattr(settings, 'ENABLE_OAUTH', False) or \
|
getattr(settings, 'ENABLE_OAUTH', False) or \
|
||||||
getattr(settings, 'ENABLE_DINGTALK', False) or \
|
|
||||||
getattr(settings, 'ENABLE_CAS', False) or \
|
getattr(settings, 'ENABLE_CAS', False) or \
|
||||||
getattr(settings, 'ENABLE_REMOTE_USER_AUTHENTICATION', False) or \
|
getattr(settings, 'ENABLE_REMOTE_USER_AUTHENTICATION', False)
|
||||||
getattr(settings, 'ENABLE_WORK_WEIXIN', False)
|
|
||||||
|
|
||||||
login_bg_image_path = get_login_bg_image_path()
|
login_bg_image_path = get_login_bg_image_path()
|
||||||
|
|
||||||
@ -348,10 +347,14 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N
|
|||||||
# prompts for a new password
|
# prompts for a new password
|
||||||
# - password_reset_complete shows a success message for the above
|
# - password_reset_complete shows a success message for the above
|
||||||
|
|
||||||
|
|
||||||
@csrf_protect
|
@csrf_protect
|
||||||
def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
|
@rate_limit()
|
||||||
|
def password_reset(request, is_admin_site=False,
|
||||||
|
template_name='registration/password_reset_form.html',
|
||||||
email_template_name='registration/password_reset_email.html',
|
email_template_name='registration/password_reset_email.html',
|
||||||
password_reset_form=PasswordResetForm, token_generator=default_token_generator,
|
password_reset_form=PasswordResetForm,
|
||||||
|
token_generator=default_token_generator,
|
||||||
post_reset_redirect=None):
|
post_reset_redirect=None):
|
||||||
|
|
||||||
has_bind_social_auth = False
|
has_bind_social_auth = False
|
||||||
|
@ -359,6 +359,9 @@ class UserPermissions(object):
|
|||||||
def can_add_repo(self):
|
def can_add_repo(self):
|
||||||
return self._get_perm_by_roles('can_add_repo')
|
return self._get_perm_by_roles('can_add_repo')
|
||||||
|
|
||||||
|
def can_share_repo(self):
|
||||||
|
return self._get_perm_by_roles('can_share_repo')
|
||||||
|
|
||||||
def can_add_group(self):
|
def can_add_group(self):
|
||||||
return self._get_perm_by_roles('can_add_group')
|
return self._get_perm_by_roles('can_add_group')
|
||||||
|
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
# Generated by Django 4.2.2 on 2024-06-12 10:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import seahub.base.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('base', '0004_externaldepartment_socialauthuser_usermonitoredrepos_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ClientSSOToken',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('token', models.CharField(max_length=100, unique=True)),
|
||||||
|
('username', seahub.base.fields.LowerCaseCharField(blank=True, db_index=True, max_length=255, null=True)),
|
||||||
|
('status', models.CharField(choices=[('waiting', 'waiting'), ('success', 'success'), ('error', 'error')], default='waiting', max_length=10)),
|
||||||
|
('api_key', models.CharField(blank=True, max_length=40, null=True)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||||
|
('updated_at', models.DateTimeField(blank=True, db_index=True, null=True)),
|
||||||
|
('accessed_at', models.DateTimeField(blank=True, db_index=True, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='socialauthuser',
|
||||||
|
name='extra_data',
|
||||||
|
field=models.TextField(null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.2 on 2024-06-12 10:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('invitations', '0006_reposhareinvitation'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='invitation',
|
||||||
|
name='invite_type',
|
||||||
|
field=models.CharField(choices=[('guest', 'guest'), ('default', 'default')], default='guest', max_length=20),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 4.2.2 on 2024-06-12 10:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('organizations', '0004_orgsamlconfig_orgadminsettings'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='orgsamlconfig',
|
||||||
|
name='dns_txt',
|
||||||
|
field=models.CharField(blank=True, max_length=64, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='orgsamlconfig',
|
||||||
|
name='domain_verified',
|
||||||
|
field=models.BooleanField(db_index=True, default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='orgsamlconfig',
|
||||||
|
name='idp_certificate',
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='orgsamlconfig',
|
||||||
|
name='domain',
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True, unique=True),
|
||||||
|
),
|
||||||
|
]
|
@ -27,6 +27,7 @@ def merge_roles(default, custom):
|
|||||||
DEFAULT_ENABLED_ROLE_PERMISSIONS = {
|
DEFAULT_ENABLED_ROLE_PERMISSIONS = {
|
||||||
DEFAULT_USER: {
|
DEFAULT_USER: {
|
||||||
'can_add_repo': True,
|
'can_add_repo': True,
|
||||||
|
'can_share_repo': True,
|
||||||
'can_add_group': True,
|
'can_add_group': True,
|
||||||
'can_view_org': True,
|
'can_view_org': True,
|
||||||
'can_add_public_repo': False,
|
'can_add_public_repo': False,
|
||||||
@ -48,6 +49,7 @@ DEFAULT_ENABLED_ROLE_PERMISSIONS = {
|
|||||||
},
|
},
|
||||||
GUEST_USER: {
|
GUEST_USER: {
|
||||||
'can_add_repo': False,
|
'can_add_repo': False,
|
||||||
|
'can_share_repo': False,
|
||||||
'can_add_group': False,
|
'can_add_group': False,
|
||||||
'can_view_org': False,
|
'can_view_org': False,
|
||||||
'can_add_public_repo': False,
|
'can_add_public_repo': False,
|
||||||
|
@ -349,6 +349,9 @@ LOGOUT_REDIRECT_URL = None
|
|||||||
|
|
||||||
ACCOUNT_ACTIVATION_DAYS = 7
|
ACCOUNT_ACTIVATION_DAYS = 7
|
||||||
|
|
||||||
|
REQUEST_RATE_LIMIT_NUMBER = 3
|
||||||
|
REQUEST_RATE_LIMIT_PERIOD = 60 # seconds
|
||||||
|
|
||||||
# allow seafile admin view user's repo
|
# allow seafile admin view user's repo
|
||||||
ENABLE_SYS_ADMIN_VIEW_REPO = False
|
ENABLE_SYS_ADMIN_VIEW_REPO = False
|
||||||
|
|
||||||
@ -489,6 +492,8 @@ ENABLE_SEAFILE_DOCS = False
|
|||||||
# enable integration seatbale
|
# enable integration seatbale
|
||||||
ENABLE_SEATABLE_INTEGRATION = False
|
ENABLE_SEATABLE_INTEGRATION = False
|
||||||
|
|
||||||
|
ENABLE_CONVERT_TO_TEAM_ACCOUNT = False
|
||||||
|
|
||||||
# File preview
|
# File preview
|
||||||
FILE_PREVIEW_MAX_SIZE = 30 * 1024 * 1024
|
FILE_PREVIEW_MAX_SIZE = 30 * 1024 * 1024
|
||||||
FILE_ENCODING_LIST = ['auto', 'utf-8', 'gbk', 'ISO-8859-1', 'ISO-8859-5']
|
FILE_ENCODING_LIST = ['auto', 'utf-8', 'gbk', 'ISO-8859-1', 'ISO-8859-5']
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.2 on 2024-06-12 10:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('share', '0002_customsharepermissions_alter_fileshare_permission_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='uploadlinkshare',
|
||||||
|
name='expire_date',
|
||||||
|
field=models.DateTimeField(db_index=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -66,6 +66,7 @@
|
|||||||
guideEnabled: {% if guide_enabled %} true {% else %} false {% endif %},
|
guideEnabled: {% if guide_enabled %} true {% else %} false {% endif %},
|
||||||
trashReposExpireDays: {% if trash_repos_expire_days >= 0 %} {{ trash_repos_expire_days }} {% else %} null {% endif %},
|
trashReposExpireDays: {% if trash_repos_expire_days >= 0 %} {{ trash_repos_expire_days }} {% else %} null {% endif %},
|
||||||
canAddRepo: {% if user.permissions.can_add_repo %} true {% else %} false {% endif %},
|
canAddRepo: {% if user.permissions.can_add_repo %} true {% else %} false {% endif %},
|
||||||
|
canShareRepo: {% if user.permissions.can_share_repo %} true {% else %} false {% endif %},
|
||||||
canAddGroup: {% if user.permissions.can_add_group %} true {% else %} false {% endif %},
|
canAddGroup: {% if user.permissions.can_add_group %} true {% else %} false {% endif %},
|
||||||
groupImportMembersExtraMsg: "{{group_import_members_extra_msg}}",
|
groupImportMembersExtraMsg: "{{group_import_members_extra_msg}}",
|
||||||
canGenerateShareLink: {% if user.permissions.can_generate_share_link %} true {% else %} false {% endif %},
|
canGenerateShareLink: {% if user.permissions.can_generate_share_link %} true {% else %} false {% endif %},
|
||||||
|
@ -93,7 +93,7 @@ from seahub.api2.endpoints.repo_draft_info import RepoDraftInfo, RepoDraftCounts
|
|||||||
from seahub.api2.endpoints.activities import ActivitiesView
|
from seahub.api2.endpoints.activities import ActivitiesView
|
||||||
from seahub.api2.endpoints.wiki_pages import WikiPagesDirView, WikiPageContentView
|
from seahub.api2.endpoints.wiki_pages import WikiPagesDirView, WikiPageContentView
|
||||||
from seahub.api2.endpoints.revision_tag import TaggedItemsView, TagNamesView
|
from seahub.api2.endpoints.revision_tag import TaggedItemsView, TagNamesView
|
||||||
from seahub.api2.endpoints.user import User
|
from seahub.api2.endpoints.user import User, UserConvertToTeamView
|
||||||
from seahub.api2.endpoints.auth_token_by_session import AuthTokenBySession
|
from seahub.api2.endpoints.auth_token_by_session import AuthTokenBySession
|
||||||
from seahub.api2.endpoints.repo_tags import RepoTagsView, RepoTagView
|
from seahub.api2.endpoints.repo_tags import RepoTagsView, RepoTagView
|
||||||
from seahub.api2.endpoints.file_tag import RepoFileTagsView, RepoFileTagView
|
from seahub.api2.endpoints.file_tag import RepoFileTagsView, RepoFileTagView
|
||||||
@ -141,7 +141,7 @@ from seahub.api2.endpoints.admin.devices import AdminDevices
|
|||||||
from seahub.api2.endpoints.admin.device_errors import AdminDeviceErrors
|
from seahub.api2.endpoints.admin.device_errors import AdminDeviceErrors
|
||||||
from seahub.api2.endpoints.admin.users import AdminUsers, AdminUser, AdminUserResetPassword, AdminAdminUsers, \
|
from seahub.api2.endpoints.admin.users import AdminUsers, AdminUser, AdminUserResetPassword, AdminAdminUsers, \
|
||||||
AdminUserGroups, AdminUserShareLinks, AdminUserUploadLinks, AdminUserBeSharedRepos, \
|
AdminUserGroups, AdminUserShareLinks, AdminUserUploadLinks, AdminUserBeSharedRepos, \
|
||||||
AdminLDAPUsers, AdminSearchUser, AdminUpdateUserCcnetEmail, AdminUserList
|
AdminLDAPUsers, AdminSearchUser, AdminUpdateUserCcnetEmail, AdminUserList, AdminUserConvertToTeamView
|
||||||
from seahub.api2.endpoints.admin.device_trusted_ip import AdminDeviceTrustedIP
|
from seahub.api2.endpoints.admin.device_trusted_ip import AdminDeviceTrustedIP
|
||||||
from seahub.api2.endpoints.admin.libraries import AdminLibraries, AdminLibrary, \
|
from seahub.api2.endpoints.admin.libraries import AdminLibraries, AdminLibrary, \
|
||||||
AdminSearchLibrary
|
AdminSearchLibrary
|
||||||
@ -317,6 +317,9 @@ urlpatterns = [
|
|||||||
## user
|
## user
|
||||||
re_path(r'^api/v2.1/user/$', User.as_view(), name="api-v2.1-user"),
|
re_path(r'^api/v2.1/user/$', User.as_view(), name="api-v2.1-user"),
|
||||||
|
|
||||||
|
# user:convert to team account
|
||||||
|
re_path(r'^api/v2.1/user/convert-to-team/$', UserConvertToTeamView.as_view(), name="api-v2.1-user-convert-to-team"),
|
||||||
|
|
||||||
## obtain auth token by login session
|
## obtain auth token by login session
|
||||||
re_path(r'^api/v2.1/auth-token-by-session/$', AuthTokenBySession.as_view(), name="api-v2.1-auth-token-by-session"),
|
re_path(r'^api/v2.1/auth-token-by-session/$', AuthTokenBySession.as_view(), name="api-v2.1-auth-token-by-session"),
|
||||||
|
|
||||||
@ -596,6 +599,7 @@ urlpatterns = [
|
|||||||
re_path(r'^api/v2.1/admin/search-user/$', AdminSearchUser.as_view(), name='api-v2.1-admin-search-user'),
|
re_path(r'^api/v2.1/admin/search-user/$', AdminSearchUser.as_view(), name='api-v2.1-admin-search-user'),
|
||||||
re_path(r'^api/v2.1/admin/update-user-ccnet-email/$', AdminUpdateUserCcnetEmail.as_view(), name='api-v2.1-admin-update-user-ccnet-email'),
|
re_path(r'^api/v2.1/admin/update-user-ccnet-email/$', AdminUpdateUserCcnetEmail.as_view(), name='api-v2.1-admin-update-user-ccnet-email'),
|
||||||
re_path(r'^api/v2.1/admin/user-list/$', AdminUserList.as_view(), name='api-v2.1-admin-user-list'),
|
re_path(r'^api/v2.1/admin/user-list/$', AdminUserList.as_view(), name='api-v2.1-admin-user-list'),
|
||||||
|
re_path(r'^api/v2.1/admin/user-convert-to-team/$', AdminUserConvertToTeamView.as_view(), name='api-v2.1-admin-user-list'),
|
||||||
|
|
||||||
# [^...] Matches any single character not in brackets
|
# [^...] Matches any single character not in brackets
|
||||||
# + Matches between one and unlimited times, as many times as possible
|
# + Matches between one and unlimited times, as many times as possible
|
||||||
|
@ -348,3 +348,12 @@ class SeafileDB:
|
|||||||
repo_ids.append(repo_id)
|
repo_ids.append(repo_id)
|
||||||
del_repo_trash(cursor, repo_ids)
|
del_repo_trash(cursor, repo_ids)
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
def add_repos_to_org_user(self, org_id, username, repo_ids):
|
||||||
|
for repo_id in repo_ids:
|
||||||
|
sql = f"""
|
||||||
|
INSERT INTO `{self.db_name}`.`OrgRepo` (org_id, repo_id, user)
|
||||||
|
VALUES ({org_id}, "{repo_id}", "{username}");
|
||||||
|
"""
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(sql)
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
# Copyright (c) 2012-2016 Seafile Ltd.
|
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||||
|
|
||||||
|
import time
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
|
from django.core.cache import cache
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
from django.http import HttpResponse, HttpResponseBadRequest, \
|
||||||
|
HttpResponseForbidden
|
||||||
|
|
||||||
|
from seahub.settings import REQUEST_RATE_LIMIT_NUMBER, \
|
||||||
|
REQUEST_RATE_LIMIT_PERIOD
|
||||||
|
|
||||||
|
JSON_CONTENT_TYPE = 'application/json; charset=utf-8'
|
||||||
|
|
||||||
|
|
||||||
class _HTTPException(Exception):
|
class _HTTPException(Exception):
|
||||||
def __init__(self, message=''):
|
def __init__(self, message=''):
|
||||||
@ -13,13 +21,15 @@ class _HTTPException(Exception):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s: %s' % (self.__class__.__name__, self.message)
|
return '%s: %s' % (self.__class__.__name__, self.message)
|
||||||
|
|
||||||
|
|
||||||
class BadRequestException(_HTTPException):
|
class BadRequestException(_HTTPException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RequestForbbiddenException(_HTTPException):
|
class RequestForbbiddenException(_HTTPException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
JSON_CONTENT_TYPE = 'application/json; charset=utf-8'
|
|
||||||
def json_response(func):
|
def json_response(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapped(*a, **kw):
|
def wrapped(*a, **kw):
|
||||||
@ -36,6 +46,7 @@ def json_response(func):
|
|||||||
content_type=JSON_CONTENT_TYPE)
|
content_type=JSON_CONTENT_TYPE)
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
def int_param(request, key):
|
def int_param(request, key):
|
||||||
v = request.GET.get(key, None)
|
v = request.GET.get(key, None)
|
||||||
if not v:
|
if not v:
|
||||||
@ -44,3 +55,37 @@ def int_param(request, key):
|
|||||||
return int(v)
|
return int(v)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise BadRequestException()
|
raise BadRequestException()
|
||||||
|
|
||||||
|
|
||||||
|
def rate_limit(number=REQUEST_RATE_LIMIT_NUMBER,
|
||||||
|
period=REQUEST_RATE_LIMIT_PERIOD):
|
||||||
|
"""
|
||||||
|
:param number: number of requests
|
||||||
|
:param period: second
|
||||||
|
"""
|
||||||
|
def decorator(func):
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def wrapped(request, *args, **kwargs):
|
||||||
|
|
||||||
|
if REQUEST_RATE_LIMIT_NUMBER > 0 and \
|
||||||
|
REQUEST_RATE_LIMIT_PERIOD > 0:
|
||||||
|
|
||||||
|
ip = request.META.get('REMOTE_ADDR')
|
||||||
|
cache_key = f"rate_limit:{ip}"
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
data = cache.get(cache_key, {'count': 0, 'start_time': current_time})
|
||||||
|
if current_time - data['start_time'] > period:
|
||||||
|
data = {'count': 1, 'start_time': current_time}
|
||||||
|
else:
|
||||||
|
data['count'] += 1
|
||||||
|
|
||||||
|
cache.set(cache_key, data, timeout=period)
|
||||||
|
|
||||||
|
if data['count'] > number:
|
||||||
|
return HttpResponse(_("Too many requests"), status=429)
|
||||||
|
|
||||||
|
return func(request, *args, **kwargs)
|
||||||
|
return wrapped
|
||||||
|
return decorator
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import urllib.request, urllib.parse, urllib.error
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import urllib.error
|
||||||
import requests
|
import requests
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
@ -30,12 +31,14 @@ from seahub.settings import ENABLE_WATERMARK
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def generate_access_token_cache_key(token):
|
def generate_access_token_cache_key(token):
|
||||||
""" Generate cache key for WOPI access token
|
""" Generate cache key for WOPI access token
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return 'wopi_access_token_' + str(token)
|
return 'wopi_access_token_' + str(token)
|
||||||
|
|
||||||
|
|
||||||
def get_file_info_by_token(token):
|
def get_file_info_by_token(token):
|
||||||
""" Get file info from cache by access token
|
""" Get file info from cache by access token
|
||||||
|
|
||||||
@ -50,6 +53,7 @@ def get_file_info_by_token(token):
|
|||||||
|
|
||||||
return value if value else None
|
return value if value else None
|
||||||
|
|
||||||
|
|
||||||
def generate_discovery_cache_key(name, ext):
|
def generate_discovery_cache_key(name, ext):
|
||||||
""" Generate cache key for office web app hosting discovery
|
""" Generate cache key for office web app hosting discovery
|
||||||
|
|
||||||
@ -59,6 +63,7 @@ def generate_discovery_cache_key(name, ext):
|
|||||||
|
|
||||||
return 'wopi_' + name + '_' + ext
|
return 'wopi_' + name + '_' + ext
|
||||||
|
|
||||||
|
|
||||||
def get_wopi_dict(request_user, repo_id, file_path,
|
def get_wopi_dict(request_user, repo_id, file_path,
|
||||||
action_name='view', can_download=True,
|
action_name='view', can_download=True,
|
||||||
language_code='en', obj_id=''):
|
language_code='en', obj_id=''):
|
||||||
@ -82,6 +87,14 @@ def get_wopi_dict(request_user, repo_id, file_path,
|
|||||||
file_ext = 'xlsx'
|
file_ext = 'xlsx'
|
||||||
|
|
||||||
wopi_key = generate_discovery_cache_key(action_name, file_ext)
|
wopi_key = generate_discovery_cache_key(action_name, file_ext)
|
||||||
|
|
||||||
|
if OFFICE_SERVER_TYPE.lower() == 'collaboraoffice':
|
||||||
|
# Since the hosting discover page of Collabora Online does not provide
|
||||||
|
# a URL with the action set to "view" for some common file formats,
|
||||||
|
# we always use "edit" here.
|
||||||
|
# Preview file is achieved by setting `UserCanWrite` to `False` in `CheckFileInfo`.
|
||||||
|
wopi_key = generate_discovery_cache_key('edit', file_ext)
|
||||||
|
|
||||||
action_url = cache.get(wopi_key)
|
action_url = cache.get(wopi_key)
|
||||||
|
|
||||||
if not action_url:
|
if not action_url:
|
||||||
@ -90,7 +103,8 @@ def get_wopi_dict(request_user, repo_id, file_path,
|
|||||||
try:
|
try:
|
||||||
if OFFICE_WEB_APP_CLIENT_CERT and OFFICE_WEB_APP_CLIENT_KEY:
|
if OFFICE_WEB_APP_CLIENT_CERT and OFFICE_WEB_APP_CLIENT_KEY:
|
||||||
xml = requests.get(OFFICE_WEB_APP_BASE_URL,
|
xml = requests.get(OFFICE_WEB_APP_BASE_URL,
|
||||||
cert=(OFFICE_WEB_APP_CLIENT_CERT, OFFICE_WEB_APP_CLIENT_KEY),
|
cert=(OFFICE_WEB_APP_CLIENT_CERT,
|
||||||
|
OFFICE_WEB_APP_CLIENT_KEY),
|
||||||
verify=OFFICE_WEB_APP_SERVER_CA)
|
verify=OFFICE_WEB_APP_SERVER_CA)
|
||||||
elif OFFICE_WEB_APP_CLIENT_PEM:
|
elif OFFICE_WEB_APP_CLIENT_PEM:
|
||||||
xml = requests.get(OFFICE_WEB_APP_BASE_URL,
|
xml = requests.get(OFFICE_WEB_APP_BASE_URL,
|
||||||
|
@ -261,7 +261,7 @@ class WOPIFilesView(APIView):
|
|||||||
result['ReadOnly'] = True if not can_edit else False
|
result['ReadOnly'] = True if not can_edit else False
|
||||||
|
|
||||||
avatar_url, _, _ = api_avatar_url(request_user, int(72))
|
avatar_url, _, _ = api_avatar_url(request_user, int(72))
|
||||||
result['UserExtraInfo'] = { 'avatar': avatar_url, 'mail': request_user }
|
result['UserExtraInfo'] = {'avatar': avatar_url, 'mail': request_user}
|
||||||
|
|
||||||
# new file creation feature is not implemented on wopi host(seahub)
|
# new file creation feature is not implemented on wopi host(seahub)
|
||||||
# hide save as button on view/edit file page
|
# hide save as button on view/edit file page
|
||||||
|
@ -11,4 +11,4 @@ class UtilsTest(BaseTestCase):
|
|||||||
assert DEFAULT_USER in get_available_roles()
|
assert DEFAULT_USER in get_available_roles()
|
||||||
|
|
||||||
def test_get_enabled_role_permissions_by_role(self):
|
def test_get_enabled_role_permissions_by_role(self):
|
||||||
assert len(list(get_enabled_role_permissions_by_role(DEFAULT_USER).keys())) == 19
|
assert len(list(get_enabled_role_permissions_by_role(DEFAULT_USER).keys())) == 20
|
||||||
|
@ -48,7 +48,7 @@ class ShibbolethRemoteUserMiddlewareTest(BaseTestCase):
|
|||||||
|
|
||||||
self.request.META = {}
|
self.request.META = {}
|
||||||
self.request.META['Shibboleth-eppn'] = 'sampledeveloper@school.edu'
|
self.request.META['Shibboleth-eppn'] = 'sampledeveloper@school.edu'
|
||||||
self.request.META['REMOTE_USER'] = 'sampledeveloper@school.edu'
|
self.request.META['HTTP_REMOTE_USER'] = 'sampledeveloper@school.edu'
|
||||||
self.request.META['givenname'] = 'test_gname'
|
self.request.META['givenname'] = 'test_gname'
|
||||||
self.request.META['surname'] = 'test_sname'
|
self.request.META['surname'] = 'test_sname'
|
||||||
self.request.META['Shibboleth-displayName'] = 'Sample Developer'
|
self.request.META['Shibboleth-displayName'] = 'Sample Developer'
|
||||||
@ -68,6 +68,12 @@ class ShibbolethRemoteUserMiddlewareTest(BaseTestCase):
|
|||||||
def test_can_process(self):
|
def test_can_process(self):
|
||||||
assert len(Profile.objects.all()) == 0
|
assert len(Profile.objects.all()) == 0
|
||||||
|
|
||||||
|
# logout first
|
||||||
|
from seahub.auth.models import AnonymousUser
|
||||||
|
self.request.session.flush()
|
||||||
|
self.request.user = AnonymousUser()
|
||||||
|
|
||||||
|
# then login user via thibboleth
|
||||||
self.middleware.process_request(self.request)
|
self.middleware.process_request(self.request)
|
||||||
shib_user = SocialAuthUser.objects.get_by_provider_and_uid(
|
shib_user = SocialAuthUser.objects.get_by_provider_and_uid(
|
||||||
SHIBBOLETH_PROVIDER_IDENTIFIER, 'sampledeveloper@school.edu')
|
SHIBBOLETH_PROVIDER_IDENTIFIER, 'sampledeveloper@school.edu')
|
||||||
@ -95,6 +101,12 @@ class ShibbolethRemoteUserMiddlewareTest(BaseTestCase):
|
|||||||
def test_can_process_user_role(self):
|
def test_can_process_user_role(self):
|
||||||
assert len(Profile.objects.all()) == 0
|
assert len(Profile.objects.all()) == 0
|
||||||
|
|
||||||
|
# logout first
|
||||||
|
from seahub.auth.models import AnonymousUser
|
||||||
|
self.request.session.flush()
|
||||||
|
self.request.user = AnonymousUser()
|
||||||
|
|
||||||
|
# then login user via thibboleth
|
||||||
self.middleware.process_request(self.request)
|
self.middleware.process_request(self.request)
|
||||||
shib_user = SocialAuthUser.objects.get_by_provider_and_uid(
|
shib_user = SocialAuthUser.objects.get_by_provider_and_uid(
|
||||||
SHIBBOLETH_PROVIDER_IDENTIFIER, 'sampledeveloper@school.edu')
|
SHIBBOLETH_PROVIDER_IDENTIFIER, 'sampledeveloper@school.edu')
|
||||||
@ -191,4 +203,3 @@ class ShibbolethRemoteUserMiddlewareTest(BaseTestCase):
|
|||||||
assert obj._get_role_by_affiliation('student1@school.edu') == 'student'
|
assert obj._get_role_by_affiliation('student1@school.edu') == 'student'
|
||||||
assert obj._get_role_by_affiliation('a@x.edu') == 'aaa'
|
assert obj._get_role_by_affiliation('a@x.edu') == 'aaa'
|
||||||
assert obj._get_role_by_affiliation('a@x.com') == 'guest'
|
assert obj._get_role_by_affiliation('a@x.com') == 'guest'
|
||||||
|
|
||||||
|
@ -2,30 +2,31 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
#At a minimum you will need username,
|
# At a minimum you will need username,
|
||||||
default_shib_attributes = {
|
default_shib_attributes = {
|
||||||
"Shibboleth-eppn": (True, "username"),
|
"Shibboleth-eppn": (True, "username"),
|
||||||
}
|
}
|
||||||
|
|
||||||
SHIB_ATTRIBUTE_MAP = getattr(settings, 'SHIBBOLETH_ATTRIBUTE_MAP', default_shib_attributes)
|
SHIB_ATTRIBUTE_MAP = getattr(settings, 'SHIBBOLETH_ATTRIBUTE_MAP', default_shib_attributes)
|
||||||
#Set to true if you are testing and want to insert sample headers.
|
# Set to true if you are testing and want to insert sample headers.
|
||||||
SHIB_MOCK_HEADERS = getattr(settings, 'SHIBBOLETH_MOCK_HEADERS', False)
|
SHIB_MOCK_HEADERS = getattr(settings, 'SHIBBOLETH_MOCK_HEADERS', False)
|
||||||
|
|
||||||
SHIB_USER_HEADER = getattr(settings, 'SHIBBOLETH_USER_HEADER', "REMOTE_USER")
|
SHIB_USER_HEADER = getattr(settings, 'SHIBBOLETH_USER_HEADER', "HTTP_REMOTE_USER")
|
||||||
|
SHIB_USER_HEADER_SECOND_UID = getattr(settings,
|
||||||
|
'SHIBBOLETH_USER_HEADER_SECOND_UID',
|
||||||
|
'HTTP_REMOTE_USER_SUBJECT_ID')
|
||||||
|
|
||||||
LOGIN_URL = getattr(settings, 'LOGIN_URL', None)
|
LOGIN_URL = getattr(settings, 'LOGIN_URL', None)
|
||||||
|
|
||||||
if not LOGIN_URL:
|
if not LOGIN_URL:
|
||||||
raise ImproperlyConfigured("A LOGIN_URL is required. Specify in settings.py")
|
raise ImproperlyConfigured("A LOGIN_URL is required. Specify in settings.py")
|
||||||
|
|
||||||
#Optional logout parameters
|
# Optional logout parameters
|
||||||
#This should look like: https://sso.school.edu/idp/logout.jsp?return=%s
|
# This should look like: https://sso.school.edu/idp/logout.jsp?return=%s
|
||||||
#The return url variable will be replaced in the LogoutView.
|
# The return url variable will be replaced in the LogoutView.
|
||||||
LOGOUT_URL = getattr(settings, 'SHIBBOLETH_LOGOUT_URL', None)
|
LOGOUT_URL = getattr(settings, 'SHIBBOLETH_LOGOUT_URL', None)
|
||||||
#LOGOUT_REDIRECT_URL specifies a default logout page that will always be used when
|
# LOGOUT_REDIRECT_URL specifies a default logout page that will always be used when
|
||||||
#users logout from Shibboleth.
|
# users logout from Shibboleth.
|
||||||
LOGOUT_REDIRECT_URL = getattr(settings, 'SHIBBOLETH_LOGOUT_REDIRECT_URL', None)
|
LOGOUT_REDIRECT_URL = getattr(settings, 'SHIBBOLETH_LOGOUT_REDIRECT_URL', None)
|
||||||
#Name of key. Probably no need to change this.
|
# Name of key. Probably no need to change this.
|
||||||
LOGOUT_SESSION_KEY = getattr(settings, 'SHIBBOLETH_FORCE_REAUTH_SESSION_KEY', 'shib_force_reauth')
|
LOGOUT_SESSION_KEY = getattr(settings, 'SHIBBOLETH_FORCE_REAUTH_SESSION_KEY', 'shib_force_reauth')
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ class ShibbolethRemoteUserBackend(RemoteUserBackend):
|
|||||||
user = None
|
user = None
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def authenticate(self, remote_user, shib_meta):
|
def authenticate(self, remote_user, shib_meta, second_uid=''):
|
||||||
"""
|
"""
|
||||||
The username passed as ``remote_user`` is considered trusted. This
|
The username passed as ``remote_user`` is considered trusted. This
|
||||||
method simply returns the ``User`` object with the given username,
|
method simply returns the ``User`` object with the given username,
|
||||||
@ -69,10 +69,21 @@ class ShibbolethRemoteUserBackend(RemoteUserBackend):
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
user = None
|
user = None
|
||||||
|
|
||||||
|
if user and second_uid:
|
||||||
|
SocialAuthUser.objects.add_if_not_exists(user.username,
|
||||||
|
SHIBBOLETH_PROVIDER_IDENTIFIER,
|
||||||
|
second_uid)
|
||||||
|
|
||||||
if not user and self.create_unknown_user:
|
if not user and self.create_unknown_user:
|
||||||
try:
|
try:
|
||||||
user = User.objects.create_shib_user(is_active=self.activate_after_creation)
|
user = User.objects.create_shib_user(is_active=self.activate_after_creation)
|
||||||
SocialAuthUser.objects.add(user.username, SHIBBOLETH_PROVIDER_IDENTIFIER, remote_user)
|
SocialAuthUser.objects.add_if_not_exists(user.username,
|
||||||
|
SHIBBOLETH_PROVIDER_IDENTIFIER,
|
||||||
|
remote_user)
|
||||||
|
if second_uid:
|
||||||
|
SocialAuthUser.objects.add_if_not_exists(user.username,
|
||||||
|
SHIBBOLETH_PROVIDER_IDENTIFIER,
|
||||||
|
second_uid)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('create shib user failed: %s' % e)
|
logger.error('create shib user failed: %s' % e)
|
||||||
return None
|
return None
|
||||||
|
@ -11,7 +11,8 @@ from django.urls import reverse
|
|||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from seaserv import seafile_api, ccnet_api
|
from seaserv import seafile_api, ccnet_api
|
||||||
|
|
||||||
from shibboleth.app_settings import SHIB_ATTRIBUTE_MAP, LOGOUT_SESSION_KEY, SHIB_USER_HEADER
|
from shibboleth.app_settings import SHIB_ATTRIBUTE_MAP, LOGOUT_SESSION_KEY, \
|
||||||
|
SHIB_USER_HEADER, SHIB_USER_HEADER_SECOND_UID
|
||||||
|
|
||||||
from seahub import auth
|
from seahub import auth
|
||||||
from seahub.base.accounts import User
|
from seahub.base.accounts import User
|
||||||
@ -40,8 +41,7 @@ SHIBBOLETH_PROVIDER_IDENTIFIER = getattr(settings, 'SHIBBOLETH_PROVIDER_IDENTIFI
|
|||||||
|
|
||||||
class ShibbolethRemoteUserMiddleware(RemoteUserMiddleware):
|
class ShibbolethRemoteUserMiddleware(RemoteUserMiddleware):
|
||||||
"""
|
"""
|
||||||
Authentication Middleware for use with Shibboleth. Uses the recommended pattern
|
Authentication Middleware for use with Shibboleth.
|
||||||
for remote authentication from: http://code.djangoproject.com/svn/django/tags/releases/1.3/django/contrib/auth/middleware.py
|
|
||||||
"""
|
"""
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
if request.path.rstrip('/') != settings.SITE_ROOT + 'sso':
|
if request.path.rstrip('/') != settings.SITE_ROOT + 'sso':
|
||||||
@ -74,18 +74,12 @@ class ShibbolethRemoteUserMiddleware(RemoteUserMiddleware):
|
|||||||
# AuthenticationMiddleware).
|
# AuthenticationMiddleware).
|
||||||
return
|
return
|
||||||
|
|
||||||
|
second_uid = request.META.get(SHIB_USER_HEADER_SECOND_UID, '')
|
||||||
|
|
||||||
# If the user is already authenticated and that user is the user we are
|
# If the user is already authenticated and that user is the user we are
|
||||||
# getting passed in the headers, then the correct user is already
|
# getting passed in the headers, then the correct user is already
|
||||||
# persisted in the session and we don't need to continue.
|
# persisted in the session and we don't need to continue.
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
# If user is already authenticated, the value of request.user.username should be random ID of user,
|
|
||||||
# not the SHIB_USER_HEADER in the request header
|
|
||||||
shib_user = SocialAuthUser.objects.get_by_provider_and_uid(SHIBBOLETH_PROVIDER_IDENTIFIER, remote_user)
|
|
||||||
if shib_user:
|
|
||||||
remote_user = shib_user.username
|
|
||||||
if request.user.username == remote_user:
|
|
||||||
if request.user.is_staff:
|
|
||||||
update_sudo_mode_ts(request)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Make sure we have all required Shiboleth elements before proceeding.
|
# Make sure we have all required Shiboleth elements before proceeding.
|
||||||
@ -98,7 +92,9 @@ class ShibbolethRemoteUserMiddleware(RemoteUserMiddleware):
|
|||||||
|
|
||||||
# We are seeing this user for the first time in this session, attempt
|
# We are seeing this user for the first time in this session, attempt
|
||||||
# to authenticate the user.
|
# to authenticate the user.
|
||||||
user = auth.authenticate(remote_user=remote_user, shib_meta=shib_meta)
|
user = auth.authenticate(remote_user=remote_user,
|
||||||
|
shib_meta=shib_meta,
|
||||||
|
second_uid=second_uid)
|
||||||
if user:
|
if user:
|
||||||
if not user.is_active:
|
if not user.is_active:
|
||||||
return HttpResponseRedirect(reverse('shib_complete'))
|
return HttpResponseRedirect(reverse('shib_complete'))
|
||||||
|
Loading…
Reference in New Issue
Block a user