1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-05-11 09:24:38 +00:00

Merge branch 'master' into 12.0

This commit is contained in:
lian 2024-06-24 12:00:15 +08:00
commit 8bcc05a6cd
34 changed files with 523 additions and 116 deletions

View File

@ -25,8 +25,8 @@ jobs:
- name: clone and build
run: |
git clone --depth=1 --branch=master https://github.com/haiwen/seafile-test-deploy /tmp/seafile-test-deploy
cd /tmp/seafile-test-deploy && git fetch origin master:master && git checkout master
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 11.0:11.0 && git checkout 11.0
./bootstrap.sh
- name: pip install

View File

@ -1,7 +1,7 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
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 GenerateUploadLink from './generate-upload-link';
import ShareToUser from './share-to-user';
@ -131,17 +131,21 @@ class ShareDialog extends React.Component {
}
{enableDirPrivateShare &&
<Fragment>
<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}>
{gettext('Share to user')}
</NavLink>
</NavItem>
<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}>
{gettext('Share to group')}
</NavLink>
</NavItem>
{isPro && !isCustomPermission && (
{ canShareRepo && (
<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}>
{gettext('Share to user')}
</NavLink>
</NavItem>
)}
{ canShareRepo && (
<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}>
{gettext('Share to group')}
</NavLink>
</NavItem>
)}
{isPro && !isCustomPermission && canShareRepo && (
<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}>
{gettext('Custom sharing permissions')}
@ -307,7 +311,7 @@ class ShareDialog extends React.Component {
return (
<div className="external-share-message mt-2">
<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>
);
}
@ -319,8 +323,8 @@ class ShareDialog extends React.Component {
return (
<div>
<Modal isOpen={true} style={{maxWidth: '760px'}} className="share-dialog" toggle={this.props.toggleDialog}>
<ModalHeader toggle={this.props.toggleDialog}>
{gettext('Share')} <span className="op-target" title={itemName}>{itemName}</span>
<ModalHeader toggle={this.props.toggleDialog} tag="div">
<h5 className="text-truncate">{gettext('Share')} <span className="op-target" title={itemName}>{itemName}</span></h5>
{this.renderExternalShareMessage()}
</ModalHeader>
<ModalBody className="share-dialog-content" role="tablist">

View File

@ -2,8 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Link } from '@gatsbyjs/reach-router';
import {
gettext, siteRoot, canAddGroup,
canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople,
gettext, siteRoot, canAddGroup, canAddRepo, canShareRepo,
canGenerateShareLink, canGenerateUploadLink, canInvitePeople,
enableTC, sideNavFooterCustomHtml, additionalAppBottomLinks,
canViewOrg, isDocs, isPro, isDBSqlite3, customNavItems
} 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'}`}
style={style}
>
{canAddRepo && (
{canAddRepo && canShareRepo && (
<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')}>
<span aria-hidden="true" className="sharp">#</span>
@ -174,12 +174,14 @@ class MainSideNav extends React.Component {
</Link>
</li>
)}
<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')}>
<span aria-hidden="true" className="sharp">#</span>
<span className="nav-text">{gettext('Folders')}</span>
</Link>
</li>
{canShareRepo && (
<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')}>
<span aria-hidden="true" className="sharp">#</span>
<span className="nav-text">{gettext('Folders')}</span>
</Link>
</li>
)}
{linksNavItem}
</ul>
);

View File

@ -16,7 +16,7 @@ class TrafficTableBody extends React.Component {
case 'user':
if (userTrafficItem.name) {
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>);

View File

@ -16,7 +16,7 @@ class TrafficTableBody extends React.Component {
case 'user':
if (userTrafficItem.name) {
return (
<a href={siteRoot + 'useradmin/info/' + userTrafficItem.email + '/'}>{userTrafficItem.name}</a>
<a href={siteRoot + 'sys/users/' + userTrafficItem.email + '/'}>{userTrafficItem.name}</a>
);
}
return(<span>{'--'}</span>);

View File

@ -37,6 +37,7 @@ export const name = window.app.pageOptions.name;
export const contactEmail = window.app.pageOptions.contactEmail;
export const username = window.app.pageOptions.username;
export const canAddRepo = window.app.pageOptions.canAddRepo;
export const canShareRepo = window.app.pageOptions.canShareRepo;
export const canAddGroup = window.app.pageOptions.canAddGroup;
export const groupImportMembersExtraMsg = window.app.pageOptions.groupImportMembersExtraMsg;
export const canGenerateShareLink = window.app.pageOptions.canGenerateShareLink;

View File

@ -112,7 +112,11 @@ log "Start Monitor"
while [ 1 ]; do
monitor_seafevents
if [ $CLUSTER_MODE ] && [ $CLUSTER_MODE = "backend" ]; then
:
else
monitor_seafevents
fi
if [ $ENABLE_NOTIFICATION_SERVER ] && [ $ENABLE_NOTIFICATION_SERVER = "true" ]; then
monitor_notification_server

View File

@ -20,6 +20,7 @@ from collections import OrderedDict
from django.conf import settings
from django.contrib.auth.backends import ModelBackend
from django.core.cache import cache
from seaserv import ccnet_api, seafile_api
@ -34,6 +35,7 @@ logger = logging.getLogger(__name__)
SAML_PROVIDER_IDENTIFIER = getattr(settings, 'SAML_PROVIDER_IDENTIFIER', 'saml')
SHIBBOLETH_AFFILIATION_ROLE_MAP = getattr(settings, 'SHIBBOLETH_AFFILIATION_ROLE_MAP', {})
CACHE_KEY_GROUPS = "all_groups_cache"
class Saml2Backend(ModelBackend):
@ -196,9 +198,27 @@ class Saml2Backend(ModelBackend):
# support a list of comma-separated IDs as seafile_groups claim
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(',')]
saml_group_ids = [int(group_id) for group_id in seafile_groups]
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]
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_group_ids = [g.id for g in joined_groups]

View File

@ -22,9 +22,12 @@ from ldap.controls.libldap import SimplePagedResultsControl
from seaserv import seafile_api, ccnet_api
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.endpoints.utils import is_org_user
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error, to_python_boolean, get_user_common_info
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
import seahub.settings as settings
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_EMAIL_CONFIGURED, send_html_email, get_site_name, \
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.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.ccnet_db import CcnetDB
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.utils import get_available_roles
from seahub.utils.licenseparse import user_number_over_limit
@ -2139,3 +2144,61 @@ class AdminUserList(APIView):
user_list.append(user_info)
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})

View File

@ -29,6 +29,9 @@ class CustomSharePermissionsView(APIView):
"""List custom share permissions
"""
# 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, '/'):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
@ -53,6 +56,9 @@ class CustomSharePermissionsView(APIView):
def post(self, request, repo_id):
"""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
# argument check
permission = request.data.get('permission', None)
@ -98,6 +104,9 @@ class CustomSharePermissionView(APIView):
"""get a custom share permission
"""
# 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, '/'):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
@ -122,6 +131,9 @@ class CustomSharePermissionView(APIView):
"""Update a custom share permission
"""
# 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)
if not permission:
error_msg = 'permission invalid.'
@ -170,6 +182,9 @@ class CustomSharePermissionView(APIView):
def delete(self, request, repo_id, permission_id):
"""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
# permission check

View File

@ -61,10 +61,13 @@ class DirSharedItemsEndpoint(APIView):
org_id = request.user.org.org_id
if path == '/':
share_items = seafile_api.list_org_repo_shared_to(org_id,
repo_owner, repo_id)
repo_owner,
repo_id)
else:
share_items = seafile_api.get_org_shared_users_for_subdir(org_id,
repo_id, path, repo_owner)
repo_id,
path,
repo_owner)
else:
repo_owner = seafile_api.get_repo_owner(repo_id)
if path == '/':
@ -98,10 +101,13 @@ class DirSharedItemsEndpoint(APIView):
org_id = request.user.org.org_id
if path == '/':
share_items = seafile_api.list_org_repo_shared_group(org_id,
repo_owner, repo_id)
repo_owner,
repo_id)
else:
share_items = seafile_api.get_org_shared_groups_for_subdir(org_id,
repo_id, path, repo_owner)
repo_id,
path,
repo_owner)
else:
repo_owner = seafile_api.get_repo_owner(repo_id)
if path == '/':
@ -129,11 +135,10 @@ class DirSharedItemsEndpoint(APIView):
org_id, repo_id, path, repo_owner, group_id)
else:
if path == '/':
seafile_api.unset_group_repo(repo_id, group_id,
repo_owner)
seafile_api.unset_group_repo(repo_id, group_id, repo_owner)
else:
seafile_api.unshare_subdir_for_group(
repo_id, path, repo_owner, group_id)
seafile_api.unshare_subdir_for_group(repo_id, path,
repo_owner, group_id)
continue
ret.append({
@ -197,6 +202,9 @@ class DirSharedItemsEndpoint(APIView):
def get(self, request, repo_id, format=None):
"""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)
if not repo:
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):
"""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
repo = seafile_api.get_repo(repo_id)
if not repo:
@ -305,6 +316,10 @@ class DirSharedItemsEndpoint(APIView):
content_type=json_content_type)
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
repo = seafile_api.get_repo(repo_id)
if not repo:
@ -368,8 +383,7 @@ class DirSharedItemsEndpoint(APIView):
if not is_org_user(to_user, int(org_id)):
org_name = request.user.org.org_name
error_msg = 'User %s is not member of organization %s.' \
% (to_user, org_name)
error_msg = f'User {to_user} is not member of organization {org_name}.'
result['failed'].append({
'email': to_user,
@ -377,7 +391,8 @@ class DirSharedItemsEndpoint(APIView):
})
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)
# can't share to owner
if to_user == repo_owner:
@ -477,7 +492,8 @@ class DirSharedItemsEndpoint(APIView):
try:
org_id = None
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)
org_id = request.user.org.org_id
@ -513,9 +529,14 @@ class DirSharedItemsEndpoint(APIView):
continue
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):
if not request.user.permissions.can_share_repo():
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
username = request.user.username
repo = seafile_api.get_repo(repo_id)
if not repo:
@ -556,7 +577,7 @@ class DirSharedItemsEndpoint(APIView):
# Delete share permission at ExtraSharePermission table.
if path == '/':
ExtraSharePermission.objects.delete_share_permission(repo_id,
ExtraSharePermission.objects.delete_share_permission(repo_id,
shared_to)
org_id = None
@ -601,8 +622,8 @@ class DirSharedItemsEndpoint(APIView):
# delete share permission if repo is deleted
if path == '/':
ExtraGroupsSharePermission.objects.delete_share_permission(repo_id,
group_id)
ExtraGroupsSharePermission.objects.delete_share_permission(repo_id,
group_id)
send_perm_audit_msg('delete-repo-perm', username, group_id,
repo_id, path, permission)

View File

@ -33,6 +33,8 @@ class SharedFolders(APIView):
Permission checking:
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 = []
username = request.user.username

View File

@ -38,6 +38,8 @@ class SharedRepos(APIView):
Permission checking:
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 = []
username = request.user.username
@ -128,6 +130,9 @@ class SharedRepo(APIView):
Permission checking:
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
permission = request.data.get('permission', None)
@ -244,7 +249,9 @@ class SharedRepo(APIView):
Permission checking:
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
share_type = request.GET.get('share_type', None)
if not share_type:

View File

@ -8,6 +8,10 @@ from rest_framework.response import Response
from rest_framework.views import APIView
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.api2.authentication import TokenAuthentication
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, \
email2contact_email
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__)
json_content_type = 'application/json; charset=utf-8'
@ -138,3 +147,54 @@ class User(APIView):
# get user info and return
info = self._get_user_info(email)
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})

View File

@ -1,17 +1,12 @@
# Copyright (c) 2012-2016 Seafile Ltd.
import datetime
import hashlib
import urllib.request, urllib.parse, urllib.error
import logging
# import auth
from django.core.exceptions import ImproperlyConfigured
from registration.signals import user_deleted
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.dispatch import receiver
from django.utils.encoding import smart_str
from django.db.models.manager import EmptyManager
logger = logging.getLogger(__name__)
UNUSABLE_PASSWORD = '!' # This will never be a valid hash
@ -130,15 +125,26 @@ class AnonymousUser(object):
class SocialAuthUserManager(models.Manager):
def add(self, username, provider, uid, extra_data=''):
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()
return social_auth_user
except Exception as e:
logger.error(e)
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):
try:
social_auth_user = self.get(provider=provider, uid=uid)
@ -186,11 +192,7 @@ class ExternalDepartment(models.Model):
db_table = 'external_department'
# # handle signals
from django.dispatch import receiver
from registration.signals import user_deleted
# handle signals
@receiver(user_deleted)
def user_deleted_cb(sender, **kwargs):
username = kwargs['username']

View File

@ -35,6 +35,7 @@ from seahub.options.models import UserOptions
from seahub.profile.models import Profile
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.http import rate_limit
from seahub.utils.ip import get_remote_ip
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
@ -195,10 +196,8 @@ def login(request, template_name='registration/login.html',
getattr(settings, 'ENABLE_KRB5_LOGIN', False) or \
getattr(settings, 'ENABLE_ADFS_LOGIN', False) or \
getattr(settings, 'ENABLE_OAUTH', False) or \
getattr(settings, 'ENABLE_DINGTALK', False) or \
getattr(settings, 'ENABLE_CAS', False) or \
getattr(settings, 'ENABLE_REMOTE_USER_AUTHENTICATION', False) or \
getattr(settings, 'ENABLE_WORK_WEIXIN', False)
getattr(settings, 'ENABLE_REMOTE_USER_AUTHENTICATION', False)
login_bg_image_path = get_login_bg_image_path()
@ -348,11 +347,15 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N
# prompts for a new password
# - password_reset_complete shows a success message for the above
@csrf_protect
def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
email_template_name='registration/password_reset_email.html',
password_reset_form=PasswordResetForm, token_generator=default_token_generator,
post_reset_redirect=None):
@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',
password_reset_form=PasswordResetForm,
token_generator=default_token_generator,
post_reset_redirect=None):
has_bind_social_auth = False
if SocialAuthUser.objects.filter(username=request.user.username).exists():

View File

@ -359,6 +359,9 @@ class UserPermissions(object):
def can_add_repo(self):
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):
return self._get_perm_by_roles('can_add_group')

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -27,6 +27,7 @@ def merge_roles(default, custom):
DEFAULT_ENABLED_ROLE_PERMISSIONS = {
DEFAULT_USER: {
'can_add_repo': True,
'can_share_repo': True,
'can_add_group': True,
'can_view_org': True,
'can_add_public_repo': False,
@ -48,6 +49,7 @@ DEFAULT_ENABLED_ROLE_PERMISSIONS = {
},
GUEST_USER: {
'can_add_repo': False,
'can_share_repo': False,
'can_add_group': False,
'can_view_org': False,
'can_add_public_repo': False,

View File

@ -349,6 +349,9 @@ LOGOUT_REDIRECT_URL = None
ACCOUNT_ACTIVATION_DAYS = 7
REQUEST_RATE_LIMIT_NUMBER = 3
REQUEST_RATE_LIMIT_PERIOD = 60 # seconds
# allow seafile admin view user's repo
ENABLE_SYS_ADMIN_VIEW_REPO = False
@ -489,6 +492,8 @@ ENABLE_SEAFILE_DOCS = False
# enable integration seatbale
ENABLE_SEATABLE_INTEGRATION = False
ENABLE_CONVERT_TO_TEAM_ACCOUNT = False
# File preview
FILE_PREVIEW_MAX_SIZE = 30 * 1024 * 1024
FILE_ENCODING_LIST = ['auto', 'utf-8', 'gbk', 'ISO-8859-1', 'ISO-8859-5']

View File

@ -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),
),
]

View File

@ -66,6 +66,7 @@
guideEnabled: {% if guide_enabled %} true {% else %} false {% 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 %},
canShareRepo: {% if user.permissions.can_share_repo %} true {% else %} false {% endif %},
canAddGroup: {% if user.permissions.can_add_group %} true {% else %} false {% endif %},
groupImportMembersExtraMsg: "{{group_import_members_extra_msg}}",
canGenerateShareLink: {% if user.permissions.can_generate_share_link %} true {% else %} false {% endif %},

View File

@ -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.wiki_pages import WikiPagesDirView, WikiPageContentView
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.repo_tags import RepoTagsView, RepoTagView
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.users import AdminUsers, AdminUser, AdminUserResetPassword, AdminAdminUsers, \
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.libraries import AdminLibraries, AdminLibrary, \
AdminSearchLibrary
@ -316,6 +316,9 @@ urlpatterns = [
## 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
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/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-convert-to-team/$', AdminUserConvertToTeamView.as_view(), name='api-v2.1-admin-user-list'),
# [^...] Matches any single character not in brackets
# + Matches between one and unlimited times, as many times as possible

View File

@ -348,3 +348,12 @@ class SeafileDB:
repo_ids.append(repo_id)
del_repo_trash(cursor, repo_ids)
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)

View File

@ -1,10 +1,18 @@
# Copyright (c) 2012-2016 Seafile Ltd.
import time
import json
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):
def __init__(self, message=''):
@ -13,13 +21,15 @@ class _HTTPException(Exception):
def __str__(self):
return '%s: %s' % (self.__class__.__name__, self.message)
class BadRequestException(_HTTPException):
pass
class RequestForbbiddenException(_HTTPException):
pass
JSON_CONTENT_TYPE = 'application/json; charset=utf-8'
def json_response(func):
@wraps(func)
def wrapped(*a, **kw):
@ -36,6 +46,7 @@ def json_response(func):
content_type=JSON_CONTENT_TYPE)
return wrapped
def int_param(request, key):
v = request.GET.get(key, None)
if not v:
@ -44,3 +55,37 @@ def int_param(request, key):
return int(v)
except ValueError:
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

View File

@ -2,8 +2,9 @@
import os
import re
import time
import urllib.request, urllib.parse, urllib.error
import urllib.request
import urllib.parse
import urllib.error
import requests
import hashlib
import logging
@ -30,12 +31,14 @@ from seahub.settings import ENABLE_WATERMARK
logger = logging.getLogger(__name__)
def generate_access_token_cache_key(token):
""" Generate cache key for WOPI access token
"""
return 'wopi_access_token_' + str(token)
def get_file_info_by_token(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
def generate_discovery_cache_key(name, ext):
""" Generate cache key for office web app hosting discovery
@ -59,9 +63,10 @@ def generate_discovery_cache_key(name, ext):
return 'wopi_' + name + '_' + ext
def get_wopi_dict(request_user, repo_id, file_path,
action_name='view', can_download=True,
language_code='en', obj_id=''):
action_name='view', can_download=True,
language_code='en', obj_id=''):
""" Prepare dict data for WOPI host page
"""
@ -82,6 +87,14 @@ def get_wopi_dict(request_user, repo_id, file_path,
file_ext = 'xlsx'
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)
if not action_url:
@ -90,12 +103,13 @@ def get_wopi_dict(request_user, repo_id, file_path,
try:
if OFFICE_WEB_APP_CLIENT_CERT and OFFICE_WEB_APP_CLIENT_KEY:
xml = requests.get(OFFICE_WEB_APP_BASE_URL,
cert=(OFFICE_WEB_APP_CLIENT_CERT, OFFICE_WEB_APP_CLIENT_KEY),
verify=OFFICE_WEB_APP_SERVER_CA)
cert=(OFFICE_WEB_APP_CLIENT_CERT,
OFFICE_WEB_APP_CLIENT_KEY),
verify=OFFICE_WEB_APP_SERVER_CA)
elif OFFICE_WEB_APP_CLIENT_PEM:
xml = requests.get(OFFICE_WEB_APP_BASE_URL,
cert=OFFICE_WEB_APP_CLIENT_PEM,
verify=OFFICE_WEB_APP_SERVER_CA)
cert=OFFICE_WEB_APP_CLIENT_PEM,
verify=OFFICE_WEB_APP_SERVER_CA)
else:
xml = requests.get(OFFICE_WEB_APP_BASE_URL, verify=OFFICE_WEB_APP_SERVER_CA)
except Exception as e:
@ -119,7 +133,7 @@ def get_wopi_dict(request_user, repo_id, file_path,
tmp_action_url = re.sub(r'<.*>', '', urlsrc)
tmp_wopi_key = generate_discovery_cache_key(name, ext)
cache.set(tmp_wopi_key, tmp_action_url,
OFFICE_WEB_APP_DISCOVERY_EXPIRATION)
OFFICE_WEB_APP_DISCOVERY_EXPIRATION)
if wopi_key == tmp_wopi_key:
action_url = tmp_action_url

View File

@ -261,7 +261,7 @@ class WOPIFilesView(APIView):
result['ReadOnly'] = True if not can_edit else False
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)
# hide save as button on view/edit file page

View File

@ -11,4 +11,4 @@ class UtilsTest(BaseTestCase):
assert DEFAULT_USER in get_available_roles()
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

View File

@ -48,7 +48,7 @@ class ShibbolethRemoteUserMiddlewareTest(BaseTestCase):
self.request.META = {}
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['surname'] = 'test_sname'
self.request.META['Shibboleth-displayName'] = 'Sample Developer'
@ -68,6 +68,12 @@ class ShibbolethRemoteUserMiddlewareTest(BaseTestCase):
def test_can_process(self):
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)
shib_user = SocialAuthUser.objects.get_by_provider_and_uid(
SHIBBOLETH_PROVIDER_IDENTIFIER, 'sampledeveloper@school.edu')
@ -95,6 +101,12 @@ class ShibbolethRemoteUserMiddlewareTest(BaseTestCase):
def test_can_process_user_role(self):
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)
shib_user = SocialAuthUser.objects.get_by_provider_and_uid(
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('a@x.edu') == 'aaa'
assert obj._get_role_by_affiliation('a@x.com') == 'guest'

View File

@ -2,30 +2,31 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
#At a minimum you will need username,
# At a minimum you will need username,
default_shib_attributes = {
"Shibboleth-eppn": (True, "username"),
}
}
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_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)
if not LOGIN_URL:
raise ImproperlyConfigured("A LOGIN_URL is required. Specify in settings.py")
#Optional logout parameters
#This should look like: https://sso.school.edu/idp/logout.jsp?return=%s
#The return url variable will be replaced in the LogoutView.
# Optional logout parameters
# This should look like: https://sso.school.edu/idp/logout.jsp?return=%s
# The return url variable will be replaced in the LogoutView.
LOGOUT_URL = getattr(settings, 'SHIBBOLETH_LOGOUT_URL', None)
#LOGOUT_REDIRECT_URL specifies a default logout page that will always be used when
#users logout from Shibboleth.
# LOGOUT_REDIRECT_URL specifies a default logout page that will always be used when
# users logout from Shibboleth.
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')

View File

@ -38,7 +38,7 @@ class ShibbolethRemoteUserBackend(RemoteUserBackend):
user = None
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
method simply returns the ``User`` object with the given username,
@ -69,10 +69,21 @@ class ShibbolethRemoteUserBackend(RemoteUserBackend):
except User.DoesNotExist:
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:
try:
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:
logger.error('create shib user failed: %s' % e)
return None

View File

@ -11,7 +11,8 @@ from django.urls import reverse
from django.http import HttpResponseRedirect
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.base.accounts import User
@ -40,8 +41,7 @@ SHIBBOLETH_PROVIDER_IDENTIFIER = getattr(settings, 'SHIBBOLETH_PROVIDER_IDENTIFI
class ShibbolethRemoteUserMiddleware(RemoteUserMiddleware):
"""
Authentication Middleware for use with Shibboleth. Uses the recommended pattern
for remote authentication from: http://code.djangoproject.com/svn/django/tags/releases/1.3/django/contrib/auth/middleware.py
Authentication Middleware for use with Shibboleth.
"""
def process_request(self, request):
if request.path.rstrip('/') != settings.SITE_ROOT + 'sso':
@ -74,19 +74,13 @@ class ShibbolethRemoteUserMiddleware(RemoteUserMiddleware):
# AuthenticationMiddleware).
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
# getting passed in the headers, then the correct user is already
# persisted in the session and we don't need to continue.
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.
shib_meta, error = self.parse_attributes(request)
@ -98,7 +92,9 @@ class ShibbolethRemoteUserMiddleware(RemoteUserMiddleware):
# We are seeing this user for the first time in this session, attempt
# 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 not user.is_active:
return HttpResponseRedirect(reverse('shib_complete'))