mirror of
https://github.com/haiwen/seahub.git
synced 2025-05-10 00:47:19 +00:00
Ocm (#4640)
* ocm share local and inter-server api, ocm share page (#4335) * ocm share * optimize code * ocm repo (#4341) * ocm repo * hide share if not repo owner * optimize code * fix All refresh page, hide upload if is r perm * update permission check * update return status code * update code * add receive user drop share
This commit is contained in:
parent
f6efead60b
commit
267e9f525c
frontend/src
seahub
@ -19,6 +19,8 @@ import ShareAdminFolders from './pages/share-admin/folders';
|
||||
import ShareAdminShareLinks from './pages/share-admin/share-links';
|
||||
import ShareAdminUploadLinks from './pages/share-admin/upload-links';
|
||||
import SharedLibraries from './pages/shared-libs/shared-libs';
|
||||
import ShareWithOCM from './pages/share-with-ocm/shared-with-ocm';
|
||||
import OCMRepoDir from './pages/share-with-ocm/remote-dir-view';
|
||||
import MyLibraries from './pages/my-libs/my-libs';
|
||||
import MyLibDeleted from './pages/my-libs/my-libs-deleted';
|
||||
import PublicSharedView from './pages/shared-with-all/public-shared-view';
|
||||
@ -38,6 +40,7 @@ const DraftsViewWrapper = MainContentWrapper(DraftsView);
|
||||
const StarredWrapper = MainContentWrapper(Starred);
|
||||
const LinkedDevicesWrapper = MainContentWrapper(LinkedDevices);
|
||||
const SharedLibrariesWrapper = MainContentWrapper(SharedLibraries);
|
||||
const SharedWithOCMWrapper = MainContentWrapper(ShareWithOCM);
|
||||
const ShareAdminLibrariesWrapper = MainContentWrapper(ShareAdminLibraries);
|
||||
const ShareAdminFoldersWrapper = MainContentWrapper(ShareAdminFolders);
|
||||
const ShareAdminShareLinksWrapper = MainContentWrapper(ShareAdminShareLinks);
|
||||
@ -257,9 +260,11 @@ class App extends Component {
|
||||
<ShareAdminShareLinksWrapper path={siteRoot + 'share-admin-share-links'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<ShareAdminUploadLinksWrapper path={siteRoot + 'share-admin-upload-links'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<SharedLibrariesWrapper path={siteRoot + 'shared-libs'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<SharedWithOCMWrapper path={siteRoot + 'shared-with-ocm'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<MyLibraries path={siteRoot + 'my-libs'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<MyLibDeleted path={siteRoot + 'my-libs/deleted/'} onSearchedClick={this.onSearchedClick} />
|
||||
<LibContentView path={siteRoot + 'library/:repoID/*'} pathPrefix={this.state.pathPrefix} onMenuClick={this.onShowSidePanel} onTabNavClick={this.tabItemClick}/>
|
||||
<OCMRepoDir path={siteRoot + 'remote-library/:providerID/:repoID/*'} pathPrefix={this.state.pathPrefix} onMenuClick={this.onShowSidePanel} onTabNavClick={this.tabItemClick}/>
|
||||
<Groups path={siteRoot + 'groups'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick}/>
|
||||
<Group
|
||||
path={siteRoot + 'group/:groupID'}
|
||||
|
@ -1,12 +1,13 @@
|
||||
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 } from '../../utils/constants';
|
||||
import { Modal, ModalHeader, ModalBody, TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
|
||||
import { gettext, username, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, additionalShareDialogNote, enableOCM } from '../../utils/constants';
|
||||
import ShareToUser from './share-to-user';
|
||||
import ShareToGroup from './share-to-group';
|
||||
import ShareToInvitePeople from './share-to-invite-people';
|
||||
import GenerateShareLink from './generate-share-link';
|
||||
import GenerateUploadLink from './generate-upload-link';
|
||||
import ShareToOtherServer from './share-to-other-server';
|
||||
import InternalLink from './internal-link';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import Loading from '../loading';
|
||||
@ -141,6 +142,13 @@ class ShareDialog extends React.Component {
|
||||
}
|
||||
</Fragment>
|
||||
}
|
||||
{enableOCM && itemType === 'library' && this.state.isRepoOwner &&
|
||||
<NavItem>
|
||||
<NavLink className={activeTab === 'shareToOtherServer' ? 'active' : ''} onClick={this.toggle.bind(this, 'shareToOtherServer')}>
|
||||
{gettext('Share to other server')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
}
|
||||
</Nav>
|
||||
</div>
|
||||
<div className="share-dialog-main">
|
||||
@ -190,6 +198,11 @@ class ShareDialog extends React.Component {
|
||||
}
|
||||
</Fragment>
|
||||
}
|
||||
{enableOCM && itemType === 'library' && activeTab === 'shareToOtherServer' &&
|
||||
<TabPane tabId="shareToOtherServer">
|
||||
<ShareToOtherServer itemType={this.props.itemType} isGroupOwnedRepo={this.props.isGroupOwnedRepo} itemPath={this.props.itemPath} repoID={this.props.repoID} isRepoOwner={this.state.isRepoOwner} />
|
||||
</TabPane>
|
||||
}
|
||||
</TabContent>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
234
frontend/src/components/dialog/share-to-other-server.js
Normal file
234
frontend/src/components/dialog/share-to-other-server.js
Normal file
@ -0,0 +1,234 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Input } from 'reactstrap';
|
||||
import { Button } from 'reactstrap';
|
||||
import { seafileAPI } from '../../utils/seafile-api.js';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../toast';
|
||||
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
||||
|
||||
class ShareItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOperationShow: false
|
||||
};
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
this.setState({isOperationShow: true});
|
||||
}
|
||||
|
||||
onMouseLeave = () => {
|
||||
this.setState({isOperationShow: false});
|
||||
}
|
||||
|
||||
deleteShareItem = () => {
|
||||
let item = this.props.item;
|
||||
this.props.deleteShareItem(item);
|
||||
}
|
||||
|
||||
render() {
|
||||
let item = this.props.item;
|
||||
return (
|
||||
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<td>{item.to_sever_url}</td>
|
||||
<td className="name">{item.to_user}</td>
|
||||
<td>{Utils.sharePerms(item.permission)}</td>
|
||||
{/* <td>
|
||||
<SharePermissionEditor
|
||||
isTextMode={true}
|
||||
isEditIconShow={this.state.isOperationShow}
|
||||
currentPermission={currentPermission}
|
||||
permissions={this.props.permissions}
|
||||
onPermissionChanged={this.onChangeUserPermission}
|
||||
/>
|
||||
</td> */}
|
||||
<td>
|
||||
<span
|
||||
className={`sf2-icon-x3 action-icon ${this.state.isOperationShow ? '' : 'hide'}`}
|
||||
onClick={this.deleteShareItem}
|
||||
title={gettext('Delete')}
|
||||
>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ShareList extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="share-list-container">
|
||||
<table className="table-thead-hidden">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%">{gettext('Server URL')}</th>
|
||||
<th width="25%">{gettext('User Email')}</th>
|
||||
<th width="20%">{gettext('Permission')}</th>
|
||||
<th width="15%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.props.items.map((item, index) => {
|
||||
return (
|
||||
<ShareItem
|
||||
key={index}
|
||||
item={item}
|
||||
deleteShareItem={this.props.deleteShareItem}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
isGroupOwnedRepo: PropTypes.bool,
|
||||
itemPath: PropTypes.string.isRequired,
|
||||
itemType: PropTypes.string.isRequired,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
isRepoOwner: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
class ShareToOtherServer extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOption: null,
|
||||
errorMsg: [],
|
||||
permission: 'rw',
|
||||
ocmShares: [],
|
||||
toUser: '',
|
||||
toServerURL: '',
|
||||
};
|
||||
this.options = [];
|
||||
this.permissions = ['rw', 'r'];
|
||||
this.UnshareMessage = 'File was unshared';
|
||||
|
||||
}
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({selectedOption: option});
|
||||
this.options = [];
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.listOCMSharesPrepare(this.props.repoID).then((res) => {
|
||||
this.setState({ocmShares: res.data.ocm_share_list});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
startOCMShare = () => {
|
||||
let { repoID, itemPath } = this.props;
|
||||
let { toServerURL, toUser, permission } = this.state;
|
||||
if (!toServerURL.endsWith('/')) {
|
||||
toServerURL += '/';
|
||||
}
|
||||
seafileAPI.addOCMSharePrepare(toUser, toServerURL, repoID, itemPath, permission).then((res) => {
|
||||
toaster.success(gettext('share success.'));
|
||||
let ocmShares = this.state.ocmShares;
|
||||
ocmShares.push(res.data);
|
||||
this.setState({ocmShares: ocmShares});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
handleToUserChange = (e) => {
|
||||
this.setState({
|
||||
toUser: e.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
handleURLChange = (e) => {
|
||||
this.setState({
|
||||
toServerURL: e.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
deleteShareItem = (deletedItem) => {
|
||||
let { id } = deletedItem;
|
||||
seafileAPI.deleteOCMSharePrepare(id).then((res) => {
|
||||
toaster.success(gettext('delete success.'));
|
||||
let ocmShares = this.state.ocmShares.filter(item => {
|
||||
return item.id != id;
|
||||
});
|
||||
this.setState({ocmShares: ocmShares});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
setPermission = (permission) => {
|
||||
this.setState({permission: permission});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let { ocmShares, toUser, toServerURL, permission } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%">{gettext('Server URL')}</th>
|
||||
<th width="25%">{gettext('User Email')}</th>
|
||||
<th width="20%">{gettext('Permission')}</th>
|
||||
<th width="15%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Input
|
||||
value={toServerURL}
|
||||
onChange={this.handleURLChange}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Input
|
||||
value={toUser}
|
||||
onChange={this.handleToUserChange}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SharePermissionEditor
|
||||
isTextMode={false}
|
||||
isEditIconShow={false}
|
||||
currentPermission={permission}
|
||||
permissions={this.permissions}
|
||||
onPermissionChanged={this.setPermission}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Button onClick={this.startOCMShare}>{gettext('Submit')}</Button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ShareList
|
||||
items={ocmShares}
|
||||
deleteShareItem={this.deleteShareItem}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ShareToOtherServer.propTypes = propTypes;
|
||||
|
||||
export default ShareToOtherServer;
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '@reach/router';
|
||||
import { Badge } from 'reactstrap';
|
||||
import { gettext, siteRoot, canPublishRepo, canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, dtableWebServer } from '../utils/constants';
|
||||
import { gettext, siteRoot, canPublishRepo, canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, dtableWebServer, enableOCM } from '../utils/constants';
|
||||
import { seafileAPI } from '../utils/seafile-api';
|
||||
import { Utils } from '../utils/utils';
|
||||
import toaster from './toast';
|
||||
@ -216,6 +216,14 @@ class MainSideNav extends React.Component {
|
||||
</a>
|
||||
{this.renderSharedGroups()}
|
||||
</li>
|
||||
{enableOCM &&
|
||||
<li className="nav-item">
|
||||
<Link to={siteRoot + 'shared-with-ocm/'} className={`nav-link ellipsis ${this.getActiveClass('shared-with-ocm')}`} title={gettext('Shared from other servers')} onClick={(e) => this.tabItemClick(e, 'shared-with-ocm')}>
|
||||
<span className="sf2-icon-share" aria-hidden="true"></span>
|
||||
<span className="nav-text">{gettext('Shared from other servers')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
|
||||
|
121
frontend/src/pages/share-with-ocm/remote-dir-content.js
Normal file
121
frontend/src/pages/share-with-ocm/remote-dir-content.js
Normal file
@ -0,0 +1,121 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '@reach/router';
|
||||
import moment from 'moment';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import Loading from '../../components/loading';
|
||||
|
||||
class DirentItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpIconShown: false
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({
|
||||
isOpIconShown: true
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({
|
||||
isOpIconShown: false
|
||||
});
|
||||
}
|
||||
|
||||
openFolder = () => {
|
||||
this.props.openFolder(this.props.dirent);
|
||||
}
|
||||
|
||||
downloadDirent = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.downloadDirent(this.props.dirent);
|
||||
}
|
||||
|
||||
render () {
|
||||
let { isOpIconShown } = this.state;
|
||||
let { dirent } = this.props;
|
||||
let iconUrl = Utils.getDirentIcon(dirent);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<td className="text-center"><img src={iconUrl} width="24" alt='' /></td>
|
||||
<td>
|
||||
{dirent.is_file ?
|
||||
dirent.name :
|
||||
<Link to="#" onClick={this.openFolder}>{dirent.name}</Link>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
{isOpIconShown && dirent.is_file &&
|
||||
<a href="#" className="op-icon sf2-icon-download" title={gettext('Download')} onClick={this.downloadDirent}></a>
|
||||
}
|
||||
</td>
|
||||
<td>{Utils.bytesToSize(dirent.size)}</td>
|
||||
<td>{moment(dirent.mtime).fromNow()}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const propTypes = {
|
||||
direntList: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
class DirContent extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { loading, errorMsg, direntList } = this.props;
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (errorMsg) {
|
||||
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<table className="table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%">{/*icon*/}</th>
|
||||
<th width="55%">{gettext('Name')}</th>
|
||||
<th width="10%">{/*operation*/}</th>
|
||||
<th width="15%">{gettext('Size')}</th>
|
||||
<th width="15%">{gettext('Last Update')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{direntList.map((dirent, index) => {
|
||||
return <DirentItem
|
||||
key={index}
|
||||
dirent={dirent}
|
||||
openFolder={this.props.openFolder}
|
||||
deleteDirent={this.props.deleteDirent}
|
||||
downloadDirent={this.props.downloadDirent}
|
||||
fromSystemRepo={this.props.fromSystemRepo}
|
||||
/>;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DirContent.propTypes = propTypes;
|
||||
|
||||
export default DirContent;
|
70
frontend/src/pages/share-with-ocm/remote-dir-path.js
Normal file
70
frontend/src/pages/share-with-ocm/remote-dir-path.js
Normal file
@ -0,0 +1,70 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '@reach/router';
|
||||
import { siteRoot, gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
|
||||
const propTypes = {
|
||||
repoName: PropTypes.string.isRequired,
|
||||
currentPath: PropTypes.string.isRequired,
|
||||
onPathClick: PropTypes.func.isRequired,
|
||||
onTabNavClick: PropTypes.func.isRequired,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
class DirPath extends React.Component {
|
||||
|
||||
onPathClick = (e) => {
|
||||
let path = Utils.getEventData(e, 'path');
|
||||
this.props.onPathClick(path);
|
||||
}
|
||||
|
||||
turnPathToLink = (path) => {
|
||||
path = path[path.length - 1] === '/' ? path.slice(0, path.length - 1) : path;
|
||||
let pathList = path.split('/');
|
||||
let nodePath = '';
|
||||
let pathElem = pathList.map((item, index) => {
|
||||
if (item === '') {
|
||||
return;
|
||||
}
|
||||
if (index === (pathList.length - 1)) {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<span className="path-split">/</span>
|
||||
<span className="path-file-name">{item}</span>
|
||||
</Fragment>
|
||||
);
|
||||
} else {
|
||||
nodePath += '/' + item;
|
||||
return (
|
||||
<Fragment key={index} >
|
||||
<span className="path-split">/</span>
|
||||
<a className="path-link" data-path={nodePath} onClick={this.onPathClick}>{item}</a>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
});
|
||||
return pathElem;
|
||||
}
|
||||
|
||||
render() {
|
||||
let { currentPath, repoName } = this.props;
|
||||
let pathElem = this.turnPathToLink(currentPath);
|
||||
|
||||
return (
|
||||
<div className="path-container">
|
||||
<Link to={siteRoot + 'shared-with-ocm/'} className="normal" onClick={(e) => this.props.onTabNavClick('shared-with-ocm')}>{gettext('All')}</Link>
|
||||
<span className="path-split">/</span>
|
||||
{(currentPath === '/' || currentPath === '') ?
|
||||
<span className="path-repo-name">{repoName}</span>:
|
||||
<a className="path-link" data-path="/" onClick={this.onPathClick}>{repoName}</a>
|
||||
}
|
||||
{pathElem}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DirPath.propTypes = propTypes;
|
||||
|
||||
export default DirPath;
|
30
frontend/src/pages/share-with-ocm/remote-dir-topbar.js
Normal file
30
frontend/src/pages/share-with-ocm/remote-dir-topbar.js
Normal file
@ -0,0 +1,30 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Account from '../../components/common/account';
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.object
|
||||
};
|
||||
|
||||
class MainPanelTopbar extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={`main-panel-north ${this.props.children ? 'border-left-show' : ''}`}>
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
||||
<div className="operation">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
<div className="common-toolbar">
|
||||
<Account isAdminPanel={false} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MainPanelTopbar.propTypes = propTypes;
|
||||
|
||||
export default MainPanelTopbar;
|
188
frontend/src/pages/share-with-ocm/remote-dir-view.js
Normal file
188
frontend/src/pages/share-with-ocm/remote-dir-view.js
Normal file
@ -0,0 +1,188 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Button } from 'reactstrap';
|
||||
import { post } from 'axios';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { loginUrl, siteRoot, gettext } from '../../utils/constants';
|
||||
import toaster from '../../components/toast';
|
||||
import MainPanelTopbar from './remote-dir-topbar';
|
||||
import DirPathBar from './remote-dir-path';
|
||||
import DirContent from './remote-dir-content';
|
||||
|
||||
class Dirent {
|
||||
constructor(obj) {
|
||||
this.name = obj.name;
|
||||
this.mtime = obj.mtime;
|
||||
this.size = obj.size;
|
||||
this.is_file = obj.type === 'file';
|
||||
}
|
||||
|
||||
isDir() {
|
||||
return !this.is_file;
|
||||
}
|
||||
}
|
||||
|
||||
class DirView extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.fileInput = React.createRef();
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
repoName: '',
|
||||
path: '',
|
||||
direntList: [],
|
||||
isNewFolderDialogOpen: false,
|
||||
userPerm: '',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.loadDirentList('/');
|
||||
}
|
||||
|
||||
onPathClick = (path) => {
|
||||
this.loadDirentList(path);
|
||||
}
|
||||
|
||||
openFolder = (dirent) => {
|
||||
let direntPath = Utils.joinPath(this.state.path, dirent.name);
|
||||
if (!dirent.is_file) {
|
||||
this.loadDirentList(direntPath);
|
||||
}
|
||||
}
|
||||
|
||||
loadDirentList = (path) => {
|
||||
const { providerID, repoID } = this.props;
|
||||
seafileAPI.listOCMRepoDir(providerID, repoID, path).then(res => {
|
||||
const { repo_name: repoName, dirent_list, user_perm } = res.data;
|
||||
let direntList = [];
|
||||
dirent_list.forEach(item => {
|
||||
let dirent = new Dirent(item);
|
||||
direntList.push(dirent);
|
||||
});
|
||||
this.setState({
|
||||
loading: false,
|
||||
repoName: repoName,
|
||||
direntList: direntList,
|
||||
path: path,
|
||||
userPerm: user_perm,
|
||||
}, () => {
|
||||
let url =`${siteRoot}remote-library/${providerID}/${repoID}/${repoName}${Utils.encodePath(path)}`;
|
||||
window.history.replaceState({url: url, path: path}, path, url);
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
downloadDirent = (dirent) => {
|
||||
let path = Utils.joinPath(this.state.path, dirent.name);
|
||||
seafileAPI.getOCMRepoDownloadURL(this.props.providerID, this.props.repoID, path).then(res => {
|
||||
location.href = res.data;
|
||||
}).catch((err) => {
|
||||
let errMessage = Utils.getErrorMsg(err);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
openFileInput = () => {
|
||||
this.fileInput.current.click();
|
||||
}
|
||||
|
||||
onFileInputChange = () => {
|
||||
if (!this.fileInput.current.files.length) {
|
||||
return;
|
||||
}
|
||||
const file = this.fileInput.current.files[0];
|
||||
|
||||
let { path } = this.state;
|
||||
let { providerID, repoID } = this.props;
|
||||
seafileAPI.getOCMRepoUploadURL(providerID, repoID, path).then(res => {
|
||||
let formData = new FormData();
|
||||
formData.append('parent_dir', path);
|
||||
formData.append('file', file);
|
||||
post(res.data, formData).then(res => {
|
||||
const fileObj = res.data[0];
|
||||
let newDirent = new Dirent({
|
||||
'type': 'file',
|
||||
'name': fileObj.name,
|
||||
'size': fileObj.size,
|
||||
'mtime': (new Date()).getTime()
|
||||
});
|
||||
let direntList = this.state.direntList;
|
||||
const dirs = direntList.filter(item => { return !item.is_file; });
|
||||
direntList.splice(dirs.length, 0, newDirent);
|
||||
this.setState({
|
||||
direntList: direntList
|
||||
});
|
||||
});
|
||||
}).catch((err) => {
|
||||
let errMessage = Utils.getErrorMsg(err);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg,
|
||||
repoName, direntList, path, userPerm } = this.state;
|
||||
const { repoID } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar>
|
||||
<Fragment>
|
||||
<input className="d-none" type="file" onChange={this.onFileInputChange} ref={this.fileInput} />
|
||||
{userPerm === 'rw' &&
|
||||
<Button className="operation-item" onClick={this.openFileInput}>{gettext('Upload')}</Button>
|
||||
}
|
||||
</Fragment>
|
||||
</MainPanelTopbar>
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path align-items-center">
|
||||
<DirPathBar
|
||||
repoID={repoID}
|
||||
repoName={repoName}
|
||||
currentPath={path}
|
||||
onPathClick={this.onPathClick}
|
||||
onTabNavClick={this.props.onTabNavClick}
|
||||
/>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
<DirContent
|
||||
loading={loading}
|
||||
errorMsg={errorMsg}
|
||||
direntList={direntList}
|
||||
openFolder={this.openFolder}
|
||||
deleteDirent={this.deleteDirent}
|
||||
downloadDirent={this.downloadDirent}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DirView;
|
203
frontend/src/pages/share-with-ocm/shared-with-ocm.js
Normal file
203
frontend/src/pages/share-with-ocm/shared-with-ocm.js
Normal file
@ -0,0 +1,203 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { Link } from '@reach/router';
|
||||
import { gettext, siteRoot } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../../components/toast';
|
||||
import Loading from '../../components/loading';
|
||||
import EmptyTip from '../../components/empty-tip';
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items } = this.props;
|
||||
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('No libraries have been shared with you')}</h2>
|
||||
<p>{gettext('No libraries have been shared directly with you. You can find more shared libraries at "Shared with groups".')}</p>
|
||||
</EmptyTip>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
} else {
|
||||
const table = (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="4%"></th>
|
||||
<th width="20%">{gettext('Name')}</th>
|
||||
<th width="20%">{gettext('Shared from')}</th>
|
||||
<th width="26%">{gettext('At site')}</th>
|
||||
<th width="20%">{gettext('Time')}</th>
|
||||
<th width="10%">{/* operations */}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return <Item
|
||||
key={index}
|
||||
item={item}
|
||||
deleteShare={this.props.deleteShare}
|
||||
/>;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
return items.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
loading: PropTypes.bool.isRequired,
|
||||
errorMsg: PropTypes.string.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showOpIcon: false,
|
||||
isOpMenuOpen: false // for mobile
|
||||
};
|
||||
}
|
||||
|
||||
toggleOpMenu = () => {
|
||||
this.setState({
|
||||
isOpMenuOpen: !this.state.isOpMenuOpen
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({
|
||||
showOpIcon: true
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({
|
||||
showOpIcon: false
|
||||
});
|
||||
}
|
||||
|
||||
deleteShare = () => {
|
||||
this.props.deleteShare(this.props.item);
|
||||
}
|
||||
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
|
||||
item.icon_url = Utils.getLibIconUrl(item);
|
||||
item.icon_title = Utils.getLibIconTitle(item);
|
||||
item.url = `${siteRoot}#shared-libs/lib/${item.repo_id}/`;
|
||||
|
||||
let shareRepoUrl =`${siteRoot}remote-library/${this.props.item.provider_id}/${this.props.item.repo_id}/${Utils.encodePath(this.props.item.repo_name)}/`;
|
||||
let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
|
||||
let deleteIcon = `action-icon sf2-icon-x3 ${iconVisibility ? 'invisible' : ''}`;
|
||||
return (
|
||||
<Fragment>
|
||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||
<td><img src={item.icon_url} title={item.icon_title} alt={item.icon_title} width="24" /></td>
|
||||
<td><Link to={shareRepoUrl}>{item.repo_name}</Link></td>
|
||||
<td>{item.from_user}</td>
|
||||
<td>{item.from_server_url}</td>
|
||||
<td title={moment(item.last_modified).format('llll')}>{moment(item.ctime).fromNow()}</td>
|
||||
<td>
|
||||
<a href="#" className={deleteIcon} title={gettext('Remove')} onClick={this.deleteShare}></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Item.propTypes = {
|
||||
item: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
class SharedWithOCM extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.listOCMSharesReceived().then((res) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
items: res.data.ocm_share_received_list
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteShare = (item) => {
|
||||
let { id } = item;
|
||||
seafileAPI.deleteOCMShareReceived(id).then((res) => {
|
||||
toaster.success(gettext('delete success.'));
|
||||
let items = this.state.items.filter(item => {
|
||||
return item.id != id;
|
||||
});
|
||||
this.setState({items: items});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading m-0">{gettext('Shared from other servers')}</h3>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.items}
|
||||
deleteShare={this.deleteShare}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SharedWithOCM;
|
@ -65,6 +65,7 @@ export const customNavItems = window.app.pageOptions.customNavItems;
|
||||
export const enableShowContactEmailWhenSearchUser = window.app.pageOptions.enableShowContactEmailWhenSearchUser;
|
||||
export const maxUploadFileSize = window.app.pageOptions.maxUploadFileSize;
|
||||
export const maxNumberOfFilesForFileupload = window.app.pageOptions.maxNumberOfFilesForFileupload;
|
||||
export const enableOCM = window.app.pageOptions.enableOCM;
|
||||
|
||||
export const curNoteMsg = window.app.pageOptions.curNoteMsg;
|
||||
export const curNoteID = window.app.pageOptions.curNoteID;
|
||||
|
@ -12,6 +12,7 @@ from seahub.base.accounts import User
|
||||
from seahub.api2.models import Token, TokenV2
|
||||
from seahub.api2.utils import get_client_ip
|
||||
from seahub.repo_api_tokens.models import RepoAPITokens
|
||||
from seahub.ocm.models import OCMShare
|
||||
from seahub.utils import within_time_range
|
||||
try:
|
||||
from seahub.settings import MULTI_TENANCY
|
||||
@ -176,7 +177,11 @@ class RepoAPITokenAuthentication(BaseAuthentication):
|
||||
|
||||
rat = RepoAPITokens.objects.filter(token=auth[1]).first()
|
||||
if not rat:
|
||||
raise AuthenticationFailed('Token inactive or deleted')
|
||||
rat = OCMShare.objects.filter(shared_secret=auth[1]).first()
|
||||
if not rat:
|
||||
raise AuthenticationFailed('Token inactive or deleted')
|
||||
# if is request by remote server through ocm, use from_user instead of app_name
|
||||
rat.app_name = rat.from_user
|
||||
request.repo_api_token_obj = rat
|
||||
|
||||
return AnonymousUser(), auth[1]
|
||||
|
517
seahub/api2/endpoints/ocm.py
Normal file
517
seahub/api2/endpoints/ocm.py
Normal file
@ -0,0 +1,517 @@
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
import requests
|
||||
import json
|
||||
from constance import config
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.utils import api_error
|
||||
|
||||
from seaserv import seafile_api, ccnet_api
|
||||
|
||||
from seahub.utils.repo import get_available_repo_perms, get_repo_owner
|
||||
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE
|
||||
from seahub.ocm.models import OCMShareReceived, OCMShare
|
||||
from seahub.ocm.settings import ENABLE_OCM, SUPPORTED_OCM_PROTOCOLS, \
|
||||
OCM_SEAFILE_PROTOCOL, OCM_RESOURCE_TYPE_LIBRARY, OCM_API_VERSION, \
|
||||
OCM_SHARE_TYPES, OCM_ENDPOINT, OCM_PROVIDER_ID, OCM_NOTIFICATION_TYPE_LIST, \
|
||||
OCM_NOTIFICATION_SHARE_UNSHARED, OCM_NOTIFICATION_SHARE_DECLINED, OCM_PROTOCOL_URL, \
|
||||
OCM_NOTIFICATION_URL, OCM_CREATE_SHARE_URL
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Convert seafile permission to ocm protocol standard permission
|
||||
SEAFILE_PERMISSION2OCM_PERMISSION = {
|
||||
PERMISSION_READ: ['read'],
|
||||
PERMISSION_READ_WRITE: ['read', 'write'],
|
||||
}
|
||||
|
||||
|
||||
def gen_shared_secret(length=23):
|
||||
return ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(length))
|
||||
|
||||
|
||||
def get_remote_protocol(url):
|
||||
response = requests.get(url)
|
||||
return json.loads(response.text)
|
||||
|
||||
|
||||
def is_valid_url(url):
|
||||
if not url.startswith('https://') and not url.startswith('http://'):
|
||||
return False
|
||||
if not url.endswith('/'):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def check_url_slash(url):
|
||||
if not url.endswith('/'):
|
||||
url += '/'
|
||||
return url
|
||||
|
||||
|
||||
class OCMProtocolView(APIView):
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def get(self, request):
|
||||
"""
|
||||
return ocm protocol info to remote server
|
||||
"""
|
||||
# TODO
|
||||
# currently if ENABLE_OCM is False, return 404 as if ocm protocol is not implemented
|
||||
# ocm protocol is not clear about this, https://github.com/GEANT/OCM-API/pull/37
|
||||
if not ENABLE_OCM:
|
||||
error_msg = 'feature not enabled.'
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
result = {
|
||||
'enabled': True,
|
||||
'apiVersion': OCM_API_VERSION,
|
||||
'endPoint': config.SERVICE_URL + '/' + OCM_ENDPOINT,
|
||||
'resourceTypes': {
|
||||
'name': OCM_RESOURCE_TYPE_LIBRARY,
|
||||
'shareTypes': OCM_SHARE_TYPES,
|
||||
'protocols': {
|
||||
OCM_SEAFILE_PROTOCOL: OCM_SEAFILE_PROTOCOL,
|
||||
}
|
||||
}
|
||||
}
|
||||
return Response(result)
|
||||
|
||||
|
||||
class OCMSharesView(APIView):
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
create ocm in consumer server
|
||||
"""
|
||||
|
||||
# argument check
|
||||
share_with = request.data.get('shareWith', '')
|
||||
if not share_with:
|
||||
error_msg = 'shareWith invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
# curently only support repo share
|
||||
repo_name = request.data.get('name', '')
|
||||
if not repo_name:
|
||||
error_msg = 'name invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
sender = request.data.get('sender', '')
|
||||
if not sender:
|
||||
error_msg = 'sender invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
share_type = request.data.get('shareType', '')
|
||||
if share_type not in OCM_SHARE_TYPES:
|
||||
error_msg = 'shareType %s invalid.' % share_type
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
resource_type = request.data.get('resourceType', '')
|
||||
if resource_type != OCM_RESOURCE_TYPE_LIBRARY:
|
||||
error_msg = 'resourceType %s invalid.' % resource_type
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
provider_id = request.data.get('providerId', '')
|
||||
if not provider_id:
|
||||
error_msg = 'providerId invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
|
||||
"""
|
||||
other ocm protocol fields currently not used
|
||||
|
||||
description = request.data.get('description', '')
|
||||
owner = request.data.get('owner', '')
|
||||
ownerDisplayName = request.data.get('ownerDisplayName', '')
|
||||
senderDisplayName = request.data.get('senderDisplayName', '')
|
||||
"""
|
||||
|
||||
protocol = request.data.get('protocol', '')
|
||||
if not protocol:
|
||||
error_msg = 'protocol invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if 'name' not in protocol.keys():
|
||||
error_msg = 'protocol.name invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if protocol['name'] not in SUPPORTED_OCM_PROTOCOLS:
|
||||
error_msg = 'protocol %s not support.' % protocol['name']
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if 'options' not in protocol.keys():
|
||||
error_msg = 'protocol.options invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if 'sharedSecret' not in protocol['options'].keys():
|
||||
error_msg = 'protocol.options.sharedSecret invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if 'permissions' not in protocol['options'].keys():
|
||||
error_msg = 'protocol.options.permissions invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if protocol['name'] == OCM_SEAFILE_PROTOCOL:
|
||||
if 'repoId' not in protocol['options'].keys():
|
||||
error_msg = 'protocol.options.repoId invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if 'seafileServiceURL' not in protocol['options'].keys():
|
||||
error_msg = 'protocol.options.seafileServiceURL invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if protocol['name'] == OCM_SEAFILE_PROTOCOL:
|
||||
shared_secret = protocol['options']['sharedSecret']
|
||||
permissions = protocol['options']['permissions']
|
||||
repo_id = protocol['options']['repoId']
|
||||
from_server_url = protocol['options']['seafileServiceURL']
|
||||
|
||||
if OCMShareReceived.objects.filter(
|
||||
from_user=sender,
|
||||
to_user=share_with,
|
||||
from_server_url=from_server_url,
|
||||
repo_id=repo_id,
|
||||
repo_name=repo_name,
|
||||
provider_id=provider_id,
|
||||
).exists():
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'same share already exists.')
|
||||
|
||||
if 'write' in permissions:
|
||||
permission = PERMISSION_READ_WRITE
|
||||
else:
|
||||
permission = PERMISSION_READ
|
||||
|
||||
OCMShareReceived.objects.add(
|
||||
shared_secret=shared_secret,
|
||||
from_user=sender,
|
||||
to_user=share_with,
|
||||
from_server_url=from_server_url,
|
||||
repo_id=repo_id,
|
||||
repo_name=repo_name,
|
||||
permission=permission,
|
||||
provider_id=provider_id,
|
||||
)
|
||||
|
||||
return Response(request.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class OCMNotificationsView(APIView):
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def post(self, request):
|
||||
""" Handle notifications from remote server
|
||||
"""
|
||||
notification_type = request.data.get('notificationType', '')
|
||||
if not notification_type:
|
||||
error_msg = 'notificationType %s invalid.' % notification_type
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if notification_type not in OCM_NOTIFICATION_TYPE_LIST:
|
||||
error_msg = 'notificationType %s not supportd.' % notification_type
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
resource_type = request.data.get('resourceType', '')
|
||||
if resource_type != OCM_RESOURCE_TYPE_LIBRARY:
|
||||
error_msg = 'resourceType %s invalid.' % resource_type
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
notification = request.data.get('notification', '')
|
||||
if not notification:
|
||||
error_msg = 'notification invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
shared_secret = notification.get('sharedSecret', '')
|
||||
if not shared_secret:
|
||||
error_msg = 'sharedSecret invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if notification_type == OCM_NOTIFICATION_SHARE_UNSHARED:
|
||||
"""
|
||||
Provider unshared, then delete ocm_share_received record on Consumer
|
||||
"""
|
||||
try:
|
||||
ocm_share_received = OCMShareReceived.objects.get(shared_secret=shared_secret)
|
||||
except OCMShareReceived.DoesNotExist:
|
||||
return Response(request.data)
|
||||
|
||||
if ocm_share_received:
|
||||
try:
|
||||
ocm_share_received.delete()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Invernal Server Error')
|
||||
|
||||
elif notification_type == OCM_NOTIFICATION_SHARE_DECLINED:
|
||||
"""
|
||||
Consumer declined share, then delete ocm_share record on Provider
|
||||
"""
|
||||
try:
|
||||
ocm_share = OCMShare.objects.get(shared_secret=shared_secret)
|
||||
except OCMShareReceived.DoesNotExist:
|
||||
return Response(request.data)
|
||||
|
||||
if ocm_share:
|
||||
try:
|
||||
ocm_share.delete()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Invernal Server Error')
|
||||
|
||||
return Response(request.data)
|
||||
|
||||
|
||||
class OCMSharesPrepareView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def get(self, request):
|
||||
"""
|
||||
list ocm shares of request user, filt by repo_id
|
||||
"""
|
||||
repo_id = request.GET.get('repo_id', '')
|
||||
if repo_id:
|
||||
ocm_shares = OCMShare.objects.filter(repo_id=repo_id, from_user=request.user.username)
|
||||
else:
|
||||
ocm_shares = OCMShare.objects.filter(from_user=request.user.username)
|
||||
|
||||
ocm_share_list = []
|
||||
for ocm_share in ocm_shares:
|
||||
ocm_share_list.append(ocm_share.to_dict())
|
||||
return Response({'ocm_share_list': ocm_share_list})
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
prepare provider server info for ocm, and send post request to consumer
|
||||
three step:
|
||||
1. send get request to remote server, ask if support ocm, and get other info
|
||||
2. send post request to remote server, remote server create a recored in remote
|
||||
ocm_share_received table
|
||||
3. store a recored in local ocm_share table
|
||||
"""
|
||||
|
||||
# argument check
|
||||
to_user = request.data.get('to_user', '')
|
||||
if not to_user:
|
||||
error_msg = 'to_user invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
to_server_url = request.data.get('to_server_url', '').lower().strip()
|
||||
if not to_server_url or not is_valid_url(to_server_url):
|
||||
error_msg = 'to_server_url %s invalid.' % to_server_url
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
repo_id = request.data.get('repo_id', '')
|
||||
if not repo_id:
|
||||
error_msg = 'repo_id invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
if not repo:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'Library %s not found.' % repo_id)
|
||||
|
||||
path = request.data.get('path', '/')
|
||||
|
||||
# TODO
|
||||
# 1. folder check
|
||||
# 2. encrypted repo check
|
||||
#
|
||||
# if seafile_api.get_dir_id_by_path(repo.id, path) is None:
|
||||
# return api_error(status.HTTP_404_NOT_FOUND, 'Folder %s not found.' % path)
|
||||
#
|
||||
# if repo.encrypted and path != '/':
|
||||
# return api_error(status.HTTP_400_BAD_REQUEST, 'Folder invalid.')
|
||||
|
||||
permission = request.data.get('permission', PERMISSION_READ)
|
||||
if permission not in get_available_repo_perms():
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'permission invalid.')
|
||||
|
||||
username = request.user.username
|
||||
repo_owner = get_repo_owner(request, repo_id)
|
||||
if repo_owner != username:
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||
|
||||
if OCMShare.objects.filter(
|
||||
from_user=request.user.username,
|
||||
to_user=to_user,
|
||||
to_server_url=to_server_url,
|
||||
repo_id=repo_id,
|
||||
repo_name=repo.repo_name,
|
||||
path=path,
|
||||
).exists():
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'same share already exists.')
|
||||
|
||||
consumer_protocol = get_remote_protocol(to_server_url + OCM_PROTOCOL_URL)
|
||||
|
||||
shared_secret = gen_shared_secret()
|
||||
from_user = username
|
||||
post_data = {
|
||||
'shareWith': to_user,
|
||||
'name': repo.repo_name,
|
||||
'description': '',
|
||||
'providerId': OCM_PROVIDER_ID,
|
||||
'owner': repo_owner,
|
||||
'sender': from_user,
|
||||
'ownerDisplayName': email2nickname(repo_owner),
|
||||
'senderDisplayName': email2nickname(from_user),
|
||||
'shareType': consumer_protocol['resourceTypes']['shareTypes'][0], # currently only support user type
|
||||
'resourceType': consumer_protocol['resourceTypes']['name'], # currently only support repo
|
||||
'protocol': {
|
||||
'name': OCM_SEAFILE_PROTOCOL,
|
||||
'options': {
|
||||
'sharedSecret': shared_secret,
|
||||
'permissions': SEAFILE_PERMISSION2OCM_PERMISSION[permission],
|
||||
'repoId': repo_id,
|
||||
'seafileServiceURL': check_url_slash(config.SERVICE_URL),
|
||||
},
|
||||
},
|
||||
}
|
||||
url = consumer_protocol['endPoint'] + OCM_CREATE_SHARE_URL
|
||||
try:
|
||||
requests.post(url, json=post_data)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
ocm_share = OCMShare.objects.add(
|
||||
shared_secret=shared_secret,
|
||||
from_user=request.user.username,
|
||||
to_user=to_user,
|
||||
to_server_url=to_server_url,
|
||||
repo_id=repo_id,
|
||||
repo_name=repo.repo_name,
|
||||
path=path,
|
||||
permission=permission,
|
||||
)
|
||||
|
||||
return Response(ocm_share.to_dict())
|
||||
|
||||
|
||||
class OCMSharePrepareView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def delete(self, request, pk):
|
||||
"""
|
||||
delete an share received record
|
||||
"""
|
||||
try:
|
||||
ocm_share = OCMShare.objects.get(pk=pk)
|
||||
except OCMShareReceived.DoesNotExist:
|
||||
error_msg = 'OCMShare %s not found.' % pk
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
if ocm_share.from_user != request.user.username:
|
||||
error_msg = 'permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
to_server_url = ocm_share.to_server_url
|
||||
shared_secret = ocm_share.shared_secret
|
||||
|
||||
consumer_protocol = get_remote_protocol(to_server_url + OCM_PROTOCOL_URL)
|
||||
|
||||
# send unshare notification to consumer
|
||||
post_data = {
|
||||
'notificationType': OCM_NOTIFICATION_SHARE_UNSHARED,
|
||||
'resourceType': OCM_RESOURCE_TYPE_LIBRARY,
|
||||
'providerId': OCM_PROVIDER_ID,
|
||||
'notification': {
|
||||
'sharedSecret': shared_secret,
|
||||
'message': '',
|
||||
},
|
||||
}
|
||||
|
||||
url = consumer_protocol['endPoint'] + OCM_NOTIFICATION_URL
|
||||
try:
|
||||
requests.post(url, json=post_data)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
try:
|
||||
ocm_share.delete()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
return Response({'success': True})
|
||||
|
||||
|
||||
class OCMSharesReceivedView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def get(self, request):
|
||||
"""
|
||||
list ocm shares received
|
||||
"""
|
||||
ocm_share_received_list = []
|
||||
ocm_shares_received = OCMShareReceived.objects.filter(to_user=request.user.username)
|
||||
for ocm_share_received in ocm_shares_received:
|
||||
ocm_share_received_list.append(ocm_share_received.to_dict())
|
||||
return Response({'ocm_share_received_list': ocm_share_received_list})
|
||||
|
||||
|
||||
class OCMShareReceivedView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def delete(self, request, pk):
|
||||
"""
|
||||
delete an share received record
|
||||
"""
|
||||
try:
|
||||
ocm_share_received = OCMShareReceived.objects.get(pk=pk)
|
||||
except OCMShareReceived.DoesNotExist:
|
||||
error_msg = 'OCMShareReceived %s not found.' % pk
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
if ocm_share_received.to_user != request.user.username:
|
||||
error_msg = 'permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
from_server_url = ocm_share_received.from_server_url
|
||||
shared_secret = ocm_share_received.shared_secret
|
||||
|
||||
provider_protocol = get_remote_protocol(from_server_url + OCM_PROTOCOL_URL)
|
||||
|
||||
# send unshare notification to consumer
|
||||
post_data = {
|
||||
'notificationType': OCM_NOTIFICATION_SHARE_DECLINED,
|
||||
'resourceType': OCM_RESOURCE_TYPE_LIBRARY,
|
||||
'providerId': OCM_PROVIDER_ID,
|
||||
'notification': {
|
||||
'sharedSecret': shared_secret,
|
||||
'message': '',
|
||||
},
|
||||
}
|
||||
|
||||
url = provider_protocol['endPoint'] + OCM_NOTIFICATION_URL
|
||||
try:
|
||||
requests.post(url, json=post_data)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
try:
|
||||
ocm_share_received.delete()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
return Response({'success': True})
|
140
seahub/api2/endpoints/ocm_repos.py
Normal file
140
seahub/api2/endpoints/ocm_repos.py
Normal file
@ -0,0 +1,140 @@
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.utils import api_error
|
||||
from seahub.ocm.models import OCMShareReceived
|
||||
from seahub.ocm.settings import VIA_REPO_TOKEN_URL
|
||||
from seahub.constants import PERMISSION_READ_WRITE
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def send_get_request(url, params=None, headers=None):
|
||||
response = requests.get(url, params=params, headers=headers)
|
||||
return json.loads(response.text)
|
||||
|
||||
|
||||
class OCMReposDirView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def get(self, request, provider_id, repo_id):
|
||||
"""
|
||||
Send request to Provider to get repo item list
|
||||
"""
|
||||
|
||||
path = request.GET.get('path', '/')
|
||||
|
||||
with_thumbnail = request.GET.get('with_thumbnail', 'false')
|
||||
if with_thumbnail not in ('true', 'false'):
|
||||
error_msg = 'with_thumbnail invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
ocm_share_received = OCMShareReceived.objects.filter(provider_id=provider_id, repo_id=repo_id).first()
|
||||
if not ocm_share_received:
|
||||
error_msg = 'Library %s not found.' % repo_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
if ocm_share_received.to_user != request.user.username:
|
||||
error_msg = 'permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
url = ocm_share_received.from_server_url + VIA_REPO_TOKEN_URL['DIR']
|
||||
params = {
|
||||
'path': path,
|
||||
'with_thumbnail': with_thumbnail,
|
||||
}
|
||||
headers = {'Authorization': 'token ' + ocm_share_received.shared_secret}
|
||||
try:
|
||||
resp = send_get_request(url, params=params, headers=headers)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
return Response(resp)
|
||||
|
||||
|
||||
class OCMReposDownloadLinkView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def get(self, request, provider_id, repo_id):
|
||||
"""
|
||||
Send request to Provider to get download link
|
||||
"""
|
||||
|
||||
path = request.GET.get('path', '/')
|
||||
|
||||
ocm_share_received = OCMShareReceived.objects.filter(provider_id=provider_id, repo_id=repo_id).first()
|
||||
if not ocm_share_received:
|
||||
error_msg = 'Library %s not found.' % repo_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
if ocm_share_received.to_user != request.user.username:
|
||||
error_msg = 'permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
url = ocm_share_received.from_server_url + VIA_REPO_TOKEN_URL['DOWNLOAD_LINK']
|
||||
params = {
|
||||
'path': path,
|
||||
}
|
||||
headers = {'Authorization': 'token ' + ocm_share_received.shared_secret}
|
||||
try:
|
||||
resp = send_get_request(url, params=params, headers=headers)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
return Response(resp)
|
||||
|
||||
|
||||
class OCMReposUploadLinkView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def get(self, request, provider_id, repo_id):
|
||||
"""
|
||||
Send request to Provider to get upload link
|
||||
"""
|
||||
|
||||
path = request.GET.get('path', '/')
|
||||
|
||||
ocm_share_received = OCMShareReceived.objects.filter(provider_id=provider_id, repo_id=repo_id).first()
|
||||
if not ocm_share_received:
|
||||
error_msg = 'Library %s not found.' % repo_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
if ocm_share_received.to_user != request.user.username:
|
||||
error_msg = 'permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
if ocm_share_received.permission != PERMISSION_READ_WRITE:
|
||||
error_msg = 'permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
url = ocm_share_received.from_server_url + VIA_REPO_TOKEN_URL['UPLOAD_LINK']
|
||||
params = {
|
||||
'path': path,
|
||||
'from': 'web',
|
||||
}
|
||||
headers = {'Authorization': 'token ' + ocm_share_received.shared_secret}
|
||||
try:
|
||||
resp = send_get_request(url, params=params, headers=headers)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
return Response(resp)
|
@ -186,6 +186,7 @@ class ViaRepoDirView(APIView):
|
||||
response_dict = {}
|
||||
response_dict["user_perm"] = permission
|
||||
response_dict["dir_id"] = dir_id
|
||||
response_dict["repo_name"] = repo.repo_name
|
||||
|
||||
if request_type == 'f':
|
||||
response_dict['dirent_list'] = all_file_info_list
|
||||
|
0
seahub/ocm/__init__.py
Normal file
0
seahub/ocm/__init__.py
Normal file
53
seahub/ocm/migrations/0001_initial.py
Normal file
53
seahub/ocm/migrations/0001_initial.py
Normal file
@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.23 on 2019-12-06 01:43
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OCMShare',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('shared_secret', models.CharField(db_index=True, max_length=36, unique=True)),
|
||||
('from_user', models.CharField(db_index=True, max_length=255)),
|
||||
('to_user', models.CharField(db_index=True, max_length=255)),
|
||||
('to_server_url', models.URLField(db_index=True)),
|
||||
('repo_id', models.CharField(db_index=True, max_length=36)),
|
||||
('repo_name', models.CharField(max_length=255)),
|
||||
('permission', models.CharField(choices=[('rw', 'read, write'), ('r', 'read')], max_length=50)),
|
||||
('path', models.TextField()),
|
||||
('ctime', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'ocm_share',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OCMShareReceived',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('shared_secret', models.CharField(db_index=True, max_length=36, unique=True)),
|
||||
('from_user', models.CharField(db_index=True, max_length=255)),
|
||||
('to_user', models.CharField(db_index=True, max_length=255)),
|
||||
('from_server_url', models.URLField(db_index=True)),
|
||||
('repo_id', models.CharField(db_index=True, max_length=36)),
|
||||
('repo_name', models.CharField(max_length=255)),
|
||||
('permission', models.CharField(choices=[('rw', 'read, write'), ('r', 'read')], max_length=50)),
|
||||
('path', models.TextField()),
|
||||
('provider_id', models.CharField(db_index=True, max_length=40)),
|
||||
('ctime', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'ocm_share_received',
|
||||
},
|
||||
),
|
||||
]
|
0
seahub/ocm/migrations/__init__.py
Normal file
0
seahub/ocm/migrations/__init__.py
Normal file
107
seahub/ocm/models.py
Normal file
107
seahub/ocm/models.py
Normal file
@ -0,0 +1,107 @@
|
||||
from _sha1 import sha1
|
||||
|
||||
import hmac
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from seahub.constants import PERMISSION_READ_WRITE, PERMISSION_READ
|
||||
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
||||
|
||||
PERMISSION_CHOICES = (
|
||||
(PERMISSION_READ_WRITE, 'read, write'),
|
||||
(PERMISSION_READ, 'read'),
|
||||
)
|
||||
|
||||
|
||||
class OCMShareManager(models.Manager):
|
||||
|
||||
def add(self, shared_secret, from_user, to_user, to_server_url, repo_id, repo_name, permission, path='/'):
|
||||
ocm_share = super(OCMShareManager, self).create(shared_secret=shared_secret,
|
||||
from_user=from_user,
|
||||
to_user=to_user,
|
||||
to_server_url=to_server_url,
|
||||
repo_id=repo_id,
|
||||
repo_name=repo_name,
|
||||
path=path,
|
||||
permission=permission)
|
||||
return ocm_share
|
||||
|
||||
|
||||
class OCMShare(models.Model):
|
||||
shared_secret = models.CharField(max_length=36, db_index=True, unique=True)
|
||||
from_user = models.CharField(max_length=255, db_index=True)
|
||||
to_user = models.CharField(max_length=255, db_index=True)
|
||||
to_server_url = models.URLField(db_index=True)
|
||||
repo_id = models.CharField(max_length=36, db_index=True)
|
||||
repo_name = models.CharField(max_length=settings.MAX_FILE_NAME)
|
||||
permission = models.CharField(max_length=50, choices=PERMISSION_CHOICES)
|
||||
path = models.TextField()
|
||||
ctime = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
objects = OCMShareManager()
|
||||
|
||||
class Meta:
|
||||
db_table = 'ocm_share'
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.pk,
|
||||
'shared_secret': self.shared_secret,
|
||||
'from_user': self.from_user,
|
||||
'to_user': self.to_user,
|
||||
'to_sever_url': self.to_server_url,
|
||||
'repo_id': self.repo_id,
|
||||
'repo_name': self.repo_name,
|
||||
'path': self.path,
|
||||
'permission': self.permission,
|
||||
'ctime': datetime_to_isoformat_timestr(self.ctime),
|
||||
}
|
||||
|
||||
|
||||
class OCMShareReceivedManager(models.Manager):
|
||||
|
||||
def add(self, shared_secret, from_user, to_user, from_server_url, repo_id, repo_name, permission, provider_id, path='/'):
|
||||
ocm_share = super(OCMShareReceivedManager, self).create(shared_secret=shared_secret,
|
||||
from_user=from_user,
|
||||
to_user=to_user,
|
||||
from_server_url=from_server_url,
|
||||
repo_id=repo_id,
|
||||
repo_name=repo_name,
|
||||
path=path,
|
||||
permission=permission,
|
||||
provider_id=provider_id)
|
||||
return ocm_share
|
||||
|
||||
|
||||
class OCMShareReceived(models.Model):
|
||||
shared_secret = models.CharField(max_length=36, db_index=True, unique=True)
|
||||
from_user = models.CharField(max_length=255, db_index=True)
|
||||
to_user = models.CharField(max_length=255, db_index=True)
|
||||
from_server_url = models.URLField(db_index=True)
|
||||
repo_id = models.CharField(max_length=36, db_index=True)
|
||||
repo_name = models.CharField(max_length=settings.MAX_FILE_NAME)
|
||||
permission = models.CharField(max_length=50, choices=PERMISSION_CHOICES)
|
||||
path = models.TextField()
|
||||
provider_id = models.CharField(max_length=40, db_index=True)
|
||||
ctime = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
objects = OCMShareReceivedManager()
|
||||
|
||||
class Meta:
|
||||
db_table = 'ocm_share_received'
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.pk,
|
||||
'shared_secret': self.shared_secret,
|
||||
'from_user': self.from_user,
|
||||
'to_user': self.to_user,
|
||||
'from_server_url': self.from_server_url,
|
||||
'repo_id': self.repo_id,
|
||||
'repo_name': self.repo_name,
|
||||
'path': self.path,
|
||||
'permission': self.permission,
|
||||
'provider_id': self.provider_id,
|
||||
'ctime': datetime_to_isoformat_timestr(self.ctime),
|
||||
}
|
34
seahub/ocm/settings.py
Normal file
34
seahub/ocm/settings.py
Normal file
@ -0,0 +1,34 @@
|
||||
from django.conf import settings
|
||||
|
||||
ENABLE_OCM = getattr(settings, 'ENABLE_OCM', False)
|
||||
OCM_PROVIDER_ID = getattr(settings, 'OCM_PROVIDER_ID', '')
|
||||
OCM_SEAFILE_PROTOCOL = getattr(settings, 'OCM_SEAFILE_PROTOCOL', 'Seafile API')
|
||||
OCM_API_VERSION = getattr(settings, 'OCM_API_VERSION', '1.0-proposal1')
|
||||
OCM_ENDPOINT = getattr(settings, 'OCM_ENDPOINT', 'api/v2.1/ocm/')
|
||||
# consumer delete a share
|
||||
OCM_NOTIFICATION_SHARE_DECLINED = getattr(settings, 'OCM_NOTIFICATION_SHARE_DECLINED', 'SHARE_DECLINED')
|
||||
# provider delete a share
|
||||
OCM_NOTIFICATION_SHARE_UNSHARED = getattr(settings, 'OCM_NOTIFICATION_SHARE_UNSHARED', 'SHARE_UNSHARED')
|
||||
|
||||
# protocol urls
|
||||
OCM_PROTOCOL_URL = getattr(settings, 'OCM_PROTOCOL_URL', 'ocm-provider/')
|
||||
OCM_CREATE_SHARE_URL = getattr(settings, 'OCM_CREATE_SHARE_URL', 'shares/')
|
||||
OCM_NOTIFICATION_URL = getattr(settings, 'OCM_PROTOCOL_URL', 'notifications/')
|
||||
|
||||
# constants
|
||||
OCM_RESOURCE_TYPE_FILE = 'file'
|
||||
OCM_RESOURCE_TYPE_LIBRARY = 'library'
|
||||
OCM_SHARE_TYPES = ['user']
|
||||
SUPPORTED_OCM_PROTOCOLS = (
|
||||
OCM_SEAFILE_PROTOCOL,
|
||||
)
|
||||
OCM_NOTIFICATION_TYPE_LIST = [
|
||||
OCM_NOTIFICATION_SHARE_UNSHARED,
|
||||
OCM_NOTIFICATION_SHARE_DECLINED,
|
||||
]
|
||||
|
||||
VIA_REPO_TOKEN_URL = {
|
||||
'DIR': 'api/v2.1/via-repo-token/dir/',
|
||||
'UPLOAD_LINK': 'api/v2.1/via-repo-token/upload-link/',
|
||||
'DOWNLOAD_LINK': 'api/v2.1/via-repo-token/download-link/',
|
||||
}
|
@ -13,6 +13,7 @@ from seahub.thumbnail.utils import get_thumbnail_src
|
||||
from seahub.utils import is_pro_version, FILEEXT_TYPE_MAP, IMAGE, XMIND, VIDEO
|
||||
from seahub.utils.file_tags import get_files_tags_in_dir
|
||||
from seahub.utils.repo import is_group_repo_staff, is_repo_owner
|
||||
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
json_content_type = 'application/json; charset=utf-8'
|
||||
@ -59,7 +60,7 @@ def get_dir_file_recursively(repo_id, path, all_dirs):
|
||||
entry["parent_dir"] = path
|
||||
entry["id"] = dirent.obj_id
|
||||
entry["name"] = dirent.obj_name
|
||||
entry["mtime"] = dirent.mtime
|
||||
entry["mtime"] = timestamp_to_isoformat_timestr(dirent.mtime)
|
||||
|
||||
all_dirs.append(entry)
|
||||
|
||||
@ -114,7 +115,7 @@ def get_dir_file_info_list(username, request_type, repo_obj, parent_dir,
|
||||
dir_info["type"] = "dir"
|
||||
dir_info["id"] = dirent.obj_id
|
||||
dir_info["name"] = dirent.obj_name
|
||||
dir_info["mtime"] = dirent.mtime
|
||||
dir_info["mtime"] = timestamp_to_isoformat_timestr(dirent.mtime)
|
||||
dir_info["permission"] = dirent.permission
|
||||
dir_info["parent_dir"] = parent_dir
|
||||
dir_info_list.append(dir_info)
|
||||
@ -157,7 +158,7 @@ def get_dir_file_info_list(username, request_type, repo_obj, parent_dir,
|
||||
file_info["type"] = "file"
|
||||
file_info["id"] = file_obj_id
|
||||
file_info["name"] = file_name
|
||||
file_info["mtime"] = dirent.mtime
|
||||
file_info["mtime"] = timestamp_to_isoformat_timestr(dirent.mtime)
|
||||
file_info["permission"] = dirent.permission
|
||||
file_info["parent_dir"] = parent_dir
|
||||
file_info["size"] = dirent.size
|
||||
|
@ -127,7 +127,6 @@ MIDDLEWARE = [
|
||||
'seahub.trusted_ip.middleware.LimitIpMiddleware'
|
||||
]
|
||||
|
||||
|
||||
SITE_ROOT_URLCONF = 'seahub.urls'
|
||||
ROOT_URLCONF = 'seahub.utils.rooturl'
|
||||
SITE_ROOT = '/'
|
||||
@ -254,6 +253,7 @@ INSTALLED_APPS = [
|
||||
'seahub.repo_api_tokens',
|
||||
'seahub.abuse_reports',
|
||||
'seahub.repo_auto_delete',
|
||||
'seahub.ocm',
|
||||
]
|
||||
|
||||
|
||||
|
@ -92,6 +92,7 @@
|
||||
thumbnailSizeForOriginal: {{ thumbnail_size_for_original }},
|
||||
repoPasswordMinLength: {{repo_password_min_length}},
|
||||
canAddPublicRepo: {% if can_add_public_repo %} true {% else %} false {% endif %},
|
||||
enableOCM: {% if enable_ocm %} true {% else %} false {% endif %},
|
||||
canInvitePeople: {% if enable_guest_invitation and user.permissions.can_invite_guest %} true {% else %} false {% endif %},
|
||||
customNavItems: {% if custom_nav_items %} JSON.parse('{{ custom_nav_items | escapejs }}') {% else %} {{'[]'}} {% endif %},
|
||||
enableShowContactEmailWhenSearchUser: {% if enable_show_contact_email_when_search_user %} true {% else %} false {% endif %},
|
||||
|
@ -97,6 +97,10 @@ from seahub.api2.endpoints.repo_api_tokens import RepoAPITokensView, RepoAPIToke
|
||||
from seahub.api2.endpoints.via_repo_token import ViaRepoDirView, ViaRepoUploadLinkView, RepoInfoView, \
|
||||
ViaRepoDownloadLinkView
|
||||
from seahub.api2.endpoints.abuse_reports import AbuseReportsView
|
||||
from seahub.api2.endpoints.ocm import OCMProtocolView, OCMSharesView, OCMNotificationsView, \
|
||||
OCMSharesPrepareView, OCMSharePrepareView, OCMSharesReceivedView, OCMShareReceivedView
|
||||
from seahub.api2.endpoints.ocm_repos import OCMReposDirView, OCMReposDownloadLinkView, \
|
||||
OCMReposUploadLinkView
|
||||
|
||||
from seahub.api2.endpoints.repo_share_links import RepoShareLinks, RepoShareLink
|
||||
from seahub.api2.endpoints.repo_upload_links import RepoUploadLinks, RepoUploadLink
|
||||
@ -177,6 +181,8 @@ from seahub.api2.endpoints.file_participants import FileParticipantsView, FilePa
|
||||
from seahub.api2.endpoints.repo_related_users import RepoRelatedUsersView
|
||||
from seahub.api2.endpoints.repo_auto_delete import RepoAutoDeleteView
|
||||
|
||||
from seahub.ocm.settings import OCM_ENDPOINT
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^accounts/', include('seahub.base.registration_urls')),
|
||||
|
||||
@ -246,11 +252,13 @@ urlpatterns = [
|
||||
url(r'^share-admin-share-links/$', react_fake_view, name="share_admin_share_links"),
|
||||
url(r'^share-admin-upload-links/$', react_fake_view, name="share_admin_upload_links"),
|
||||
url(r'^shared-libs/$', react_fake_view, name="shared_libs"),
|
||||
url(r'^shared-with-ocm/$', react_fake_view, name="shared_with_ocm"),
|
||||
url(r'^my-libs/$', react_fake_view, name="my_libs"),
|
||||
url(r'^groups/$', react_fake_view, name="groups"),
|
||||
url(r'^group/(?P<group_id>\d+)/$', react_fake_view, name="group"),
|
||||
url(r'^library/(?P<repo_id>[-0-9a-f]{36})/$', react_fake_view, name="library_view"),
|
||||
url(r'^library/(?P<repo_id>[-0-9a-f]{36})/(?P<repo_name>[^/]+)/(?P<path>.*)$', react_fake_view, name="lib_view"),
|
||||
url(r'^remote-library/(?P<provider_id>[-0-9a-f]{36})/(?P<repo_id>[-0-9a-f]{36})/(?P<repo_name>[^/]+)/(?P<path>.*)$', react_fake_view, name="remote_lib_view"),
|
||||
url(r'^my-libs/deleted/$', react_fake_view, name="my_libs_deleted"),
|
||||
url(r'^org/$', react_fake_view, name="org"),
|
||||
url(r'^invitations/$', react_fake_view, name="invitations"),
|
||||
@ -447,6 +455,22 @@ urlpatterns = [
|
||||
## user::activities
|
||||
url(r'^api/v2.1/activities/$', ActivitiesView.as_view(), name='api-v2.1-acitvity'),
|
||||
|
||||
## user::ocm
|
||||
# ocm inter-server api, interact with other server
|
||||
url(r'ocm-provider/$', OCMProtocolView.as_view(), name='api-v2.1-ocm-protocol'),
|
||||
url(r'' + OCM_ENDPOINT + 'shares/$', OCMSharesView.as_view(), name='api-v2.1-ocm-shares'),
|
||||
url(r'' + OCM_ENDPOINT + 'notifications/$', OCMNotificationsView.as_view(), name='api-v2.1-ocm-notifications'),
|
||||
|
||||
# ocm local api, no interaction with other server
|
||||
url(r'api/v2.1/ocm/shares-prepare/$', OCMSharesPrepareView.as_view(), name='api-v2.1-ocm-shares-prepare'),
|
||||
url(r'api/v2.1/ocm/shares-prepare/(?P<pk>\d+)/$', OCMSharePrepareView.as_view(), name='api-v2.1-ocm-share-prepare'),
|
||||
url(r'api/v2.1/ocm/shares-received/$', OCMSharesReceivedView.as_view(), name='api-v2.1-ocm-shares-received'),
|
||||
url(r'api/v2.1/ocm/shares-received/(?P<pk>\d+)/$', OCMShareReceivedView.as_view(), name='api-v2.1-ocm-share-received'),
|
||||
# ocm local api, repo related operations
|
||||
url(r'api/v2.1/ocm/providers/(?P<provider_id>[-0-9a-f]{36})/repos/(?P<repo_id>[-0-9a-f]{36})/dir/$', OCMReposDirView.as_view(), name='api-v2.1-ocm-repos-dir'),
|
||||
url(r'api/v2.1/ocm/providers/(?P<provider_id>[-0-9a-f]{36})/repos/(?P<repo_id>[-0-9a-f]{36})/download-link/$', OCMReposDownloadLinkView.as_view(), name='api-v2.1-ocm-repos-dir'),
|
||||
url(r'api/v2.1/ocm/providers/(?P<provider_id>[-0-9a-f]{36})/repos/(?P<repo_id>[-0-9a-f]{36})/upload-link/$', OCMReposUploadLinkView.as_view(), name='api-v2.1-ocm-repos-dir'),
|
||||
|
||||
# admin: activities
|
||||
url(r'^api/v2.1/admin/user-activities/$', UserActivitiesView.as_view(), name='api-v2.1-admin-user-activity'),
|
||||
|
||||
|
@ -61,6 +61,7 @@ from seahub.settings import AVATAR_FILE_STORAGE, \
|
||||
|
||||
from seahub.wopi.settings import ENABLE_OFFICE_WEB_APP
|
||||
from seahub.onlyoffice.settings import ENABLE_ONLYOFFICE
|
||||
from seahub.ocm.settings import ENABLE_OCM
|
||||
from seahub.constants import HASH_URLS, PERMISSION_READ
|
||||
|
||||
from seahub.weixin.settings import ENABLE_WEIXIN
|
||||
@ -1169,5 +1170,6 @@ def react_fake_view(request, **kwargs):
|
||||
'enable_show_contact_email_when_search_user' : settings.ENABLE_SHOW_CONTACT_EMAIL_WHEN_SEARCH_USER,
|
||||
'additional_share_dialog_note': ADDITIONAL_SHARE_DIALOG_NOTE,
|
||||
'additional_app_bottom_links': ADDITIONAL_APP_BOTTOM_LINKS,
|
||||
'additional_about_dialog_links': ADDITIONAL_ABOUT_DIALOG_LINKS
|
||||
'additional_about_dialog_links': ADDITIONAL_ABOUT_DIALOG_LINKS,
|
||||
'enable_ocm': ENABLE_OCM,
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user