mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
Refactor bucket delete and bucket policy (#5580)
This commit adds the bucket delete and bucket policy functionalities to the browser. Part of rewriting the browser code to follow best practices and guidelines of React (issues #5409 and #5410) The backend code has been modified by @krishnasrinivas to prevent issue #4498 from occuring. The relevant changes have been made to the code according to the latest commit and the unit tests in the backend. This commit also addresses issue #5449.
This commit is contained in:
parent
416841869a
commit
a6adef0bdf
@ -19,6 +19,7 @@ import MobileHeader from "./MobileHeader"
|
|||||||
import Header from "./Header"
|
import Header from "./Header"
|
||||||
import ObjectsSection from "../objects/ObjectsSection"
|
import ObjectsSection from "../objects/ObjectsSection"
|
||||||
import MainActions from "./MainActions"
|
import MainActions from "./MainActions"
|
||||||
|
import BucketPolicyModal from "../buckets/BucketPolicyModal"
|
||||||
import MakeBucketModal from "../buckets/MakeBucketModal"
|
import MakeBucketModal from "../buckets/MakeBucketModal"
|
||||||
import UploadModal from "../uploads/UploadModal"
|
import UploadModal from "../uploads/UploadModal"
|
||||||
import ObjectsBulkActions from "../objects/ObjectsBulkActions"
|
import ObjectsBulkActions from "../objects/ObjectsBulkActions"
|
||||||
@ -30,6 +31,7 @@ export const MainContent = () => (
|
|||||||
<Header />
|
<Header />
|
||||||
<ObjectsSection />
|
<ObjectsSection />
|
||||||
<MainActions />
|
<MainActions />
|
||||||
|
<BucketPolicyModal />
|
||||||
<MakeBucketModal />
|
<MakeBucketModal />
|
||||||
<UploadModal />
|
<UploadModal />
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import classNames from "classnames"
|
import classNames from "classnames"
|
||||||
|
import BucketDropdown from "./BucketDropdown"
|
||||||
|
|
||||||
export const Bucket = ({ bucket, isActive, selectBucket }) => {
|
export const Bucket = ({ bucket, isActive, selectBucket }) => {
|
||||||
return (
|
return (
|
||||||
@ -36,6 +37,7 @@ export const Bucket = ({ bucket, isActive, selectBucket }) => {
|
|||||||
>
|
>
|
||||||
{bucket}
|
{bucket}
|
||||||
</a>
|
</a>
|
||||||
|
<BucketDropdown bucket={bucket}/>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
92
browser/app/js/buckets/BucketDropdown.js
Normal file
92
browser/app/js/buckets/BucketDropdown.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import classNames from "classnames"
|
||||||
|
import { connect } from "react-redux"
|
||||||
|
import * as actionsBuckets from "./actions"
|
||||||
|
import { getCurrentBucket } from "./selectors"
|
||||||
|
import Dropdown from "react-bootstrap/lib/Dropdown"
|
||||||
|
|
||||||
|
export class BucketDropdown extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
showBucketDropdown: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDropdown() {
|
||||||
|
if (this.state.showBucketDropdown) {
|
||||||
|
this.setState({
|
||||||
|
showBucketDropdown: false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
showBucketDropdown: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { bucket, showBucketPolicy, deleteBucket, currentBucket } = this.props
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
open = {this.state.showBucketDropdown}
|
||||||
|
onToggle = {this.toggleDropdown.bind(this)}
|
||||||
|
className="bucket-dropdown"
|
||||||
|
id="bucket-dropdown"
|
||||||
|
>
|
||||||
|
<Dropdown.Toggle noCaret>
|
||||||
|
<i className="zmdi zmdi-more-vert" />
|
||||||
|
</Dropdown.Toggle>
|
||||||
|
<Dropdown.Menu className="dropdown-menu-right">
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
this.toggleDropdown()
|
||||||
|
showBucketPolicy()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit policy
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
this.toggleDropdown()
|
||||||
|
deleteBucket(bucket)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
deleteBucket: bucket => dispatch(actionsBuckets.deleteBucket(bucket)),
|
||||||
|
showBucketPolicy: () => dispatch(actionsBuckets.showBucketPolicy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(state => state, mapDispatchToProps)(BucketDropdown)
|
@ -28,7 +28,7 @@ export class BucketList extends React.Component {
|
|||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
const { fetchBuckets, setBucketList, selectBucket } = this.props
|
const { fetchBuckets, setBucketList, selectBucket } = this.props
|
||||||
if (web.LoggedIn()) {
|
if (web.LoggedIn()) {
|
||||||
fetchBuckets()
|
fetchBuckets("list")
|
||||||
} else {
|
} else {
|
||||||
const { bucket, prefix } = pathSlice(history.location.pathname)
|
const { bucket, prefix } = pathSlice(history.location.pathname)
|
||||||
if (bucket) {
|
if (bucket) {
|
||||||
@ -63,7 +63,7 @@ const mapStateToProps = state => {
|
|||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
fetchBuckets: () => dispatch(actionsBuckets.fetchBuckets()),
|
fetchBuckets: action => dispatch(actionsBuckets.fetchBuckets(action)),
|
||||||
setBucketList: buckets => dispatch(actionsBuckets.setList(buckets)),
|
setBucketList: buckets => dispatch(actionsBuckets.setList(buckets)),
|
||||||
selectBucket: bucket => dispatch(actionsBuckets.selectBucket(bucket))
|
selectBucket: bucket => dispatch(actionsBuckets.selectBucket(bucket))
|
||||||
}
|
}
|
||||||
|
61
browser/app/js/buckets/BucketPolicyModal.js
Normal file
61
browser/app/js/buckets/BucketPolicyModal.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { connect } from "react-redux"
|
||||||
|
import { Modal, ModalHeader } from "react-bootstrap"
|
||||||
|
import * as actionsBuckets from "./actions"
|
||||||
|
import PolicyInput from "./PolicyInput"
|
||||||
|
import Policy from "./Policy"
|
||||||
|
|
||||||
|
export const BucketPolicyModal = ({ showBucketPolicy, currentBucket, hideBucketPolicy, policies }) => {
|
||||||
|
return (
|
||||||
|
<Modal className="modal-policy"
|
||||||
|
animation={ false }
|
||||||
|
show={ showBucketPolicy }
|
||||||
|
onHide={ hideBucketPolicy }
|
||||||
|
>
|
||||||
|
<ModalHeader>
|
||||||
|
Bucket Policy (
|
||||||
|
{ currentBucket })
|
||||||
|
<button className="close close-alt" onClick={ hideBucketPolicy }>
|
||||||
|
<span>×</span>
|
||||||
|
</button>
|
||||||
|
</ModalHeader>
|
||||||
|
<div className="pm-body">
|
||||||
|
<PolicyInput />
|
||||||
|
{ policies.map((policy, i) => <Policy key={ i } prefix={ policy.prefix } policy={ policy.policy } />
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
return {
|
||||||
|
currentBucket: state.buckets.currentBucket,
|
||||||
|
showBucketPolicy: state.buckets.showBucketPolicy,
|
||||||
|
policies: state.buckets.policies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
hideBucketPolicy: () => dispatch(actionsBuckets.hideBucketPolicy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(BucketPolicyModal)
|
93
browser/app/js/buckets/Policy.js
Normal file
93
browser/app/js/buckets/Policy.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from '../constants'
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { connect } from "react-redux"
|
||||||
|
import classnames from "classnames"
|
||||||
|
import * as actionsBuckets from "./actions"
|
||||||
|
import * as actionsAlert from "../alert/actions"
|
||||||
|
import web from "../web"
|
||||||
|
|
||||||
|
export class Policy extends React.Component {
|
||||||
|
removePolicy(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
const {currentBucket, prefix, fetchPolicies, showAlert} = this.props
|
||||||
|
web.
|
||||||
|
SetBucketPolicy({
|
||||||
|
bucketName: currentBucket,
|
||||||
|
prefix: prefix,
|
||||||
|
policy: 'none'
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
fetchPolicies(currentBucket)
|
||||||
|
})
|
||||||
|
.catch(e => showAlert('danger', e.message))
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {policy, prefix} = this.props
|
||||||
|
let newPrefix = prefix
|
||||||
|
|
||||||
|
if (newPrefix === '')
|
||||||
|
newPrefix = '*'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pmb-list">
|
||||||
|
<div className="pmbl-item">
|
||||||
|
{ newPrefix }
|
||||||
|
</div>
|
||||||
|
<div className="pmbl-item">
|
||||||
|
<select className="form-control"
|
||||||
|
disabled
|
||||||
|
value={ policy }>
|
||||||
|
<option value={ READ_ONLY }>
|
||||||
|
Read Only
|
||||||
|
</option>
|
||||||
|
<option value={ WRITE_ONLY }>
|
||||||
|
Write Only
|
||||||
|
</option>
|
||||||
|
<option value={ READ_WRITE }>
|
||||||
|
Read and Write
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="pmbl-item">
|
||||||
|
<button className="btn btn-block btn-danger" onClick={ this.removePolicy.bind(this) }>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
return {
|
||||||
|
currentBucket: state.buckets.currentBucket
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
fetchPolicies: bucket => dispatch(actionsBuckets.fetchPolicies(bucket)),
|
||||||
|
showAlert: (type, message) =>
|
||||||
|
dispatch(actionsAlert.set({ type: type, message: message }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Policy)
|
115
browser/app/js/buckets/PolicyInput.js
Normal file
115
browser/app/js/buckets/PolicyInput.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from '../constants'
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { connect } from "react-redux"
|
||||||
|
import classnames from "classnames"
|
||||||
|
import * as actionsBuckets from "./actions"
|
||||||
|
import * as actionsAlert from "../alert/actions"
|
||||||
|
import web from "../web"
|
||||||
|
|
||||||
|
export class PolicyInput extends React.Component {
|
||||||
|
componentDidMount() {
|
||||||
|
const { currentBucket, fetchPolicies } = this.props
|
||||||
|
fetchPolicies(currentBucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const { setPolicies } = this.props
|
||||||
|
setPolicies([])
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePolicySubmit(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
const { currentBucket, fetchPolicies, showAlert } = this.props
|
||||||
|
|
||||||
|
if (this.prefix.value === "*")
|
||||||
|
this.prefix.value = ""
|
||||||
|
|
||||||
|
let policyAlreadyExists = this.props.policies.some(
|
||||||
|
elem => this.prefix.value === elem.prefix && this.policy.value === elem.policy
|
||||||
|
)
|
||||||
|
if (policyAlreadyExists) {
|
||||||
|
showAlert("danger", "Policy for this prefix already exists.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
web.
|
||||||
|
SetBucketPolicy({
|
||||||
|
bucketName: currentBucket,
|
||||||
|
prefix: this.prefix.value,
|
||||||
|
policy: this.policy.value
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
fetchPolicies(currentBucket)
|
||||||
|
this.prefix.value = ''
|
||||||
|
})
|
||||||
|
.catch(e => showAlert("danger", e.message))
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<header className="pmb-list">
|
||||||
|
<div className="pmbl-item">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
ref={ prefix => this.prefix = prefix }
|
||||||
|
className="form-control"
|
||||||
|
placeholder="Prefix"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="pmbl-item">
|
||||||
|
<select ref={ policy => this.policy = policy } className="form-control">
|
||||||
|
<option value={ READ_ONLY }>
|
||||||
|
Read Only
|
||||||
|
</option>
|
||||||
|
<option value={ WRITE_ONLY }>
|
||||||
|
Write Only
|
||||||
|
</option>
|
||||||
|
<option value={ READ_WRITE }>
|
||||||
|
Read and Write
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="pmbl-item">
|
||||||
|
<button className="btn btn-block btn-primary" onClick={ this.handlePolicySubmit.bind(this) }>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
return {
|
||||||
|
currentBucket: state.buckets.currentBucket,
|
||||||
|
policies: state.buckets.policies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
fetchPolicies: bucket => dispatch(actionsBuckets.fetchPolicies(bucket)),
|
||||||
|
setPolicies: policies => dispatch(actionsBuckets.setPolicies(policies)),
|
||||||
|
showAlert: (type, message) =>
|
||||||
|
dispatch(actionsAlert.set({ type: type, message: message }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(PolicyInput)
|
62
browser/app/js/buckets/__tests__/BucketDropdown.test.js
Normal file
62
browser/app/js/buckets/__tests__/BucketDropdown.test.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { shallow, mount } from "enzyme"
|
||||||
|
import { BucketDropdown } from "../BucketDropdown"
|
||||||
|
|
||||||
|
describe("BucketDropdown", () => {
|
||||||
|
it("should render without crashing", () => {
|
||||||
|
shallow(<BucketDropdown />)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should call toggleDropdown on dropdown toggle", () => {
|
||||||
|
const spy = jest.spyOn(BucketDropdown.prototype, 'toggleDropdown')
|
||||||
|
const wrapper = shallow(
|
||||||
|
<BucketDropdown />
|
||||||
|
)
|
||||||
|
wrapper
|
||||||
|
.find("Uncontrolled(Dropdown)")
|
||||||
|
.simulate("toggle")
|
||||||
|
expect(spy).toHaveBeenCalled()
|
||||||
|
spy.mockReset()
|
||||||
|
spy.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should call showBucketPolicy when Edit Policy link is clicked", () => {
|
||||||
|
const showBucketPolicy = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<BucketDropdown showBucketPolicy={showBucketPolicy} />
|
||||||
|
)
|
||||||
|
wrapper
|
||||||
|
.find("li a")
|
||||||
|
.at(0)
|
||||||
|
.simulate("click", { stopPropagation: jest.fn() })
|
||||||
|
expect(showBucketPolicy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should call deleteBucket when Delete link is clicked", () => {
|
||||||
|
const deleteBucket = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<BucketDropdown bucket={"test"} deleteBucket={deleteBucket} />
|
||||||
|
)
|
||||||
|
wrapper
|
||||||
|
.find("li a")
|
||||||
|
.at(1)
|
||||||
|
.simulate("click", { stopPropagation: jest.fn() })
|
||||||
|
expect(deleteBucket).toHaveBeenCalledWith("test")
|
||||||
|
})
|
||||||
|
})
|
43
browser/app/js/buckets/__tests__/BucketPolicyModal.test.js
Normal file
43
browser/app/js/buckets/__tests__/BucketPolicyModal.test.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { shallow, mount } from "enzyme"
|
||||||
|
import { BucketPolicyModal } from "../BucketPolicyModal"
|
||||||
|
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from "../../constants"
|
||||||
|
|
||||||
|
describe("BucketPolicyModal", () => {
|
||||||
|
it("should render without crashing", () => {
|
||||||
|
shallow(<BucketPolicyModal policies={[]}/>)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should call hideBucketPolicy when close button is clicked", () => {
|
||||||
|
const hideBucketPolicy = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<BucketPolicyModal hideBucketPolicy={hideBucketPolicy} policies={[]} />
|
||||||
|
)
|
||||||
|
wrapper.find("button").simulate("click")
|
||||||
|
expect(hideBucketPolicy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should include the PolicyInput and Policy components when there are any policies", () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<BucketPolicyModal policies={ [{prefix: "test", policy: READ_ONLY}] } />
|
||||||
|
)
|
||||||
|
expect(wrapper.find("Connect(PolicyInput)").length).toBe(1)
|
||||||
|
expect(wrapper.find("Connect(Policy)").length).toBe(1)
|
||||||
|
})
|
||||||
|
})
|
63
browser/app/js/buckets/__tests__/Policy.test.js
Normal file
63
browser/app/js/buckets/__tests__/Policy.test.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { shallow, mount } from "enzyme"
|
||||||
|
import { Policy } from "../Policy"
|
||||||
|
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from "../../constants"
|
||||||
|
import web from "../../web"
|
||||||
|
|
||||||
|
jest.mock("../../web", () => ({
|
||||||
|
SetBucketPolicy: jest.fn(() => {
|
||||||
|
return Promise.resolve()
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe("Policy", () => {
|
||||||
|
it("should render without crashing", () => {
|
||||||
|
shallow(<Policy currentBucket={"bucket"} prefix={"foo"} policy={READ_ONLY} />)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should call web.setBucketPolicy and fetchPolicies on submit", () => {
|
||||||
|
const fetchPolicies = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<Policy
|
||||||
|
currentBucket={"bucket"}
|
||||||
|
prefix={"foo"}
|
||||||
|
policy={READ_ONLY}
|
||||||
|
fetchPolicies={fetchPolicies}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
wrapper.find("button").simulate("click", { preventDefault: jest.fn() })
|
||||||
|
|
||||||
|
expect(web.SetBucketPolicy).toHaveBeenCalledWith({
|
||||||
|
bucketName: "bucket",
|
||||||
|
prefix: "foo",
|
||||||
|
policy: "none"
|
||||||
|
})
|
||||||
|
|
||||||
|
setImmediate(() => {
|
||||||
|
expect(fetchPolicies).toHaveBeenCalledWith("bucket")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should change the empty string to '*' while displaying prefixes", () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<Policy currentBucket={"bucket"} prefix={""} policy={READ_ONLY} />
|
||||||
|
)
|
||||||
|
expect(wrapper.find(".pmbl-item").at(0).text()).toEqual("*")
|
||||||
|
})
|
||||||
|
})
|
77
browser/app/js/buckets/__tests__/PolicyInput.test.js
Normal file
77
browser/app/js/buckets/__tests__/PolicyInput.test.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage (C) 2018 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import { shallow, mount } from "enzyme"
|
||||||
|
import { PolicyInput } from "../PolicyInput"
|
||||||
|
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from "../../constants"
|
||||||
|
import web from "../../web"
|
||||||
|
|
||||||
|
jest.mock("../../web", () => ({
|
||||||
|
SetBucketPolicy: jest.fn(() => {
|
||||||
|
return Promise.resolve()
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe("PolicyInput", () => {
|
||||||
|
it("should render without crashing", () => {
|
||||||
|
const fetchPolicies = jest.fn()
|
||||||
|
shallow(<PolicyInput currentBucket={"bucket"} fetchPolicies={fetchPolicies}/>)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should call fetchPolicies after the component has mounted", () => {
|
||||||
|
const fetchPolicies = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<PolicyInput currentBucket={"bucket"} fetchPolicies={fetchPolicies} />
|
||||||
|
)
|
||||||
|
setImmediate(() => {
|
||||||
|
expect(fetchPolicies).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should call web.setBucketPolicy and fetchPolicies on submit", () => {
|
||||||
|
const fetchPolicies = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<PolicyInput currentBucket={"bucket"} policies={[]} fetchPolicies={fetchPolicies}/>
|
||||||
|
)
|
||||||
|
wrapper.instance().prefix = { value: "baz" }
|
||||||
|
wrapper.instance().policy = { value: READ_ONLY }
|
||||||
|
wrapper.find("button").simulate("click", { preventDefault: jest.fn() })
|
||||||
|
|
||||||
|
expect(web.SetBucketPolicy).toHaveBeenCalledWith({
|
||||||
|
bucketName: "bucket",
|
||||||
|
prefix: "baz",
|
||||||
|
policy: READ_ONLY
|
||||||
|
})
|
||||||
|
|
||||||
|
setImmediate(() => {
|
||||||
|
expect(fetchPolicies).toHaveBeenCalledWith("bucket")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should change the prefix '*' to an empty string", () => {
|
||||||
|
const fetchPolicies = jest.fn()
|
||||||
|
const wrapper = shallow(
|
||||||
|
<PolicyInput currentBucket={"bucket"} policies={[]} fetchPolicies={fetchPolicies}/>
|
||||||
|
)
|
||||||
|
wrapper.instance().prefix = { value: "*" }
|
||||||
|
wrapper.instance().policy = { value: READ_ONLY }
|
||||||
|
|
||||||
|
wrapper.find("button").simulate("click", { preventDefault: jest.fn() })
|
||||||
|
|
||||||
|
expect(wrapper.instance().prefix).toEqual({ value: "" })
|
||||||
|
})
|
||||||
|
})
|
@ -26,6 +26,9 @@ jest.mock("../../web", () => ({
|
|||||||
}),
|
}),
|
||||||
MakeBucket: jest.fn(() => {
|
MakeBucket: jest.fn(() => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
DeleteBucket: jest.fn(() => {
|
||||||
|
return Promise.resolve()
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -43,7 +46,7 @@ describe("Buckets actions", () => {
|
|||||||
{ type: "buckets/SET_LIST", buckets: ["test1", "test2"] },
|
{ type: "buckets/SET_LIST", buckets: ["test1", "test2"] },
|
||||||
{ type: "buckets/SET_CURRENT_BUCKET", bucket: "test1" }
|
{ type: "buckets/SET_CURRENT_BUCKET", bucket: "test1" }
|
||||||
]
|
]
|
||||||
return store.dispatch(actionsBuckets.fetchBuckets()).then(() => {
|
return store.dispatch(actionsBuckets.fetchBuckets("list")).then(() => {
|
||||||
const actions = store.getActions()
|
const actions = store.getActions()
|
||||||
expect(actions).toEqual(expectedActions)
|
expect(actions).toEqual(expectedActions)
|
||||||
})
|
})
|
||||||
@ -57,7 +60,7 @@ describe("Buckets actions", () => {
|
|||||||
{ type: "buckets/SET_CURRENT_BUCKET", bucket: "test2" }
|
{ type: "buckets/SET_CURRENT_BUCKET", bucket: "test2" }
|
||||||
]
|
]
|
||||||
window.location
|
window.location
|
||||||
return store.dispatch(actionsBuckets.fetchBuckets()).then(() => {
|
return store.dispatch(actionsBuckets.fetchBuckets("list")).then(() => {
|
||||||
const actions = store.getActions()
|
const actions = store.getActions()
|
||||||
expect(actions).toEqual(expectedActions)
|
expect(actions).toEqual(expectedActions)
|
||||||
})
|
})
|
||||||
@ -71,7 +74,7 @@ describe("Buckets actions", () => {
|
|||||||
{ type: "buckets/SET_CURRENT_BUCKET", bucket: "test1" }
|
{ type: "buckets/SET_CURRENT_BUCKET", bucket: "test1" }
|
||||||
]
|
]
|
||||||
window.location
|
window.location
|
||||||
return store.dispatch(actionsBuckets.fetchBuckets()).then(() => {
|
return store.dispatch(actionsBuckets.fetchBuckets("list")).then(() => {
|
||||||
const actions = store.getActions()
|
const actions = store.getActions()
|
||||||
expect(actions).toEqual(expectedActions)
|
expect(actions).toEqual(expectedActions)
|
||||||
})
|
})
|
||||||
@ -107,6 +110,36 @@ describe("Buckets actions", () => {
|
|||||||
expect(actions).toEqual(expectedActions)
|
expect(actions).toEqual(expectedActions)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("creates buckets/SHOW_BUCKET_POLICY for showBucketPolicy", () => {
|
||||||
|
const store = mockStore()
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: "buckets/SHOW_BUCKET_POLICY", show: true }
|
||||||
|
]
|
||||||
|
store.dispatch(actionsBuckets.showBucketPolicy())
|
||||||
|
const actions = store.getActions()
|
||||||
|
expect(actions).toEqual(expectedActions)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("creates buckets/SHOW_BUCKET_POLICY for hideBucketPolicy", () => {
|
||||||
|
const store = mockStore()
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: "buckets/SHOW_BUCKET_POLICY", show: false }
|
||||||
|
]
|
||||||
|
store.dispatch(actionsBuckets.hideBucketPolicy())
|
||||||
|
const actions = store.getActions()
|
||||||
|
expect(actions).toEqual(expectedActions)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("creates buckets/SET_POLICIES action", () => {
|
||||||
|
const store = mockStore()
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: "buckets/SET_POLICIES", policies: ["test1", "test2"] }
|
||||||
|
]
|
||||||
|
store.dispatch(actionsBuckets.setPolicies(["test1", "test2"]))
|
||||||
|
const actions = store.getActions()
|
||||||
|
expect(actions).toEqual(expectedActions)
|
||||||
|
})
|
||||||
|
|
||||||
it("creates buckets/ADD action", () => {
|
it("creates buckets/ADD action", () => {
|
||||||
const store = mockStore()
|
const store = mockStore()
|
||||||
const expectedActions = [{ type: "buckets/ADD", bucket: "test" }]
|
const expectedActions = [{ type: "buckets/ADD", bucket: "test" }]
|
||||||
@ -115,6 +148,14 @@ describe("Buckets actions", () => {
|
|||||||
expect(actions).toEqual(expectedActions)
|
expect(actions).toEqual(expectedActions)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("creates buckets/REMOVE action", () => {
|
||||||
|
const store = mockStore()
|
||||||
|
const expectedActions = [{ type: "buckets/REMOVE", bucket: "test" }]
|
||||||
|
store.dispatch(actionsBuckets.removeBucket("test"))
|
||||||
|
const actions = store.getActions()
|
||||||
|
expect(actions).toEqual(expectedActions)
|
||||||
|
})
|
||||||
|
|
||||||
it("creates buckets/ADD and buckets/SET_CURRENT_BUCKET after creating the bucket", () => {
|
it("creates buckets/ADD and buckets/SET_CURRENT_BUCKET after creating the bucket", () => {
|
||||||
const store = mockStore()
|
const store = mockStore()
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
@ -126,4 +167,19 @@ describe("Buckets actions", () => {
|
|||||||
expect(actions).toEqual(expectedActions)
|
expect(actions).toEqual(expectedActions)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("creates alert/SET, buckets/REMOVE, buckets/SET_LIST and buckets/SET_CURRENT_BUCKET " +
|
||||||
|
"after deleting the bucket", () => {
|
||||||
|
const store = mockStore()
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: "alert/SET", alert: {id: 0, message: "Bucket 'test3' has been deleted.", type: "info"} },
|
||||||
|
{ type: "buckets/REMOVE", bucket: "test3" },
|
||||||
|
{ type: "buckets/SET_LIST", buckets: ["test1", "test2"] },
|
||||||
|
{ type: "buckets/SET_CURRENT_BUCKET", bucket: "test1" }
|
||||||
|
]
|
||||||
|
return store.dispatch(actionsBuckets.deleteBucket("test3")).then(() => {
|
||||||
|
const actions = store.getActions()
|
||||||
|
expect(actions).toEqual(expectedActions)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -22,8 +22,10 @@ describe("buckets reducer", () => {
|
|||||||
const initialState = reducer(undefined, {})
|
const initialState = reducer(undefined, {})
|
||||||
expect(initialState).toEqual({
|
expect(initialState).toEqual({
|
||||||
list: [],
|
list: [],
|
||||||
|
policies: [],
|
||||||
filter: "",
|
filter: "",
|
||||||
currentBucket: "",
|
currentBucket: "",
|
||||||
|
showBucketPolicy: false,
|
||||||
showMakeBucketModal: false
|
showMakeBucketModal: false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -47,6 +49,17 @@ describe("buckets reducer", () => {
|
|||||||
expect(newState.list).toEqual(["test3", "test1", "test2"])
|
expect(newState.list).toEqual(["test3", "test1", "test2"])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should handle REMOVE", () => {
|
||||||
|
const newState = reducer(
|
||||||
|
{ list: ["test1", "test2"] },
|
||||||
|
{
|
||||||
|
type: actions.REMOVE,
|
||||||
|
bucket: "test2"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(newState.list).toEqual(["test1"])
|
||||||
|
})
|
||||||
|
|
||||||
it("should handle SET_FILTER", () => {
|
it("should handle SET_FILTER", () => {
|
||||||
const newState = reducer(undefined, {
|
const newState = reducer(undefined, {
|
||||||
type: actions.SET_FILTER,
|
type: actions.SET_FILTER,
|
||||||
@ -63,6 +76,22 @@ describe("buckets reducer", () => {
|
|||||||
expect(newState.currentBucket).toEqual("test")
|
expect(newState.currentBucket).toEqual("test")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should handle SET_POLICIES", () => {
|
||||||
|
const newState = reducer(undefined, {
|
||||||
|
type: actions.SET_POLICIES,
|
||||||
|
policies: ["test1", "test2"]
|
||||||
|
})
|
||||||
|
expect(newState.policies).toEqual(["test1", "test2"])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle SHOW_BUCKET_POLICY", () => {
|
||||||
|
const newState = reducer(undefined, {
|
||||||
|
type: actions.SHOW_BUCKET_POLICY,
|
||||||
|
show: true
|
||||||
|
})
|
||||||
|
expect(newState.showBucketPolicy).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
it("should handle SHOW_MAKE_BUCKET_MODAL", () => {
|
it("should handle SHOW_MAKE_BUCKET_MODAL", () => {
|
||||||
const newState = reducer(undefined, {
|
const newState = reducer(undefined, {
|
||||||
type: actions.SHOW_MAKE_BUCKET_MODAL,
|
type: actions.SHOW_MAKE_BUCKET_MODAL,
|
||||||
|
@ -22,11 +22,14 @@ import { pathSlice } from "../utils"
|
|||||||
|
|
||||||
export const SET_LIST = "buckets/SET_LIST"
|
export const SET_LIST = "buckets/SET_LIST"
|
||||||
export const ADD = "buckets/ADD"
|
export const ADD = "buckets/ADD"
|
||||||
|
export const REMOVE = "buckets/REMOVE"
|
||||||
export const SET_FILTER = "buckets/SET_FILTER"
|
export const SET_FILTER = "buckets/SET_FILTER"
|
||||||
export const SET_CURRENT_BUCKET = "buckets/SET_CURRENT_BUCKET"
|
export const SET_CURRENT_BUCKET = "buckets/SET_CURRENT_BUCKET"
|
||||||
export const SHOW_MAKE_BUCKET_MODAL = "buckets/SHOW_MAKE_BUCKET_MODAL"
|
export const SHOW_MAKE_BUCKET_MODAL = "buckets/SHOW_MAKE_BUCKET_MODAL"
|
||||||
|
export const SHOW_BUCKET_POLICY = "buckets/SHOW_BUCKET_POLICY"
|
||||||
|
export const SET_POLICIES = "buckets/SET_POLICIES"
|
||||||
|
|
||||||
export const fetchBuckets = () => {
|
export const fetchBuckets = action => {
|
||||||
return function(dispatch) {
|
return function(dispatch) {
|
||||||
return web.ListBuckets().then(res => {
|
return web.ListBuckets().then(res => {
|
||||||
const buckets = res.buckets ? res.buckets.map(bucket => bucket.name) : []
|
const buckets = res.buckets ? res.buckets.map(bucket => bucket.name) : []
|
||||||
@ -38,6 +41,9 @@ export const fetchBuckets = () => {
|
|||||||
} else {
|
} else {
|
||||||
dispatch(selectBucket(buckets[0]))
|
dispatch(selectBucket(buckets[0]))
|
||||||
}
|
}
|
||||||
|
} else if (action === "delete") {
|
||||||
|
dispatch(selectBucket(""))
|
||||||
|
history.replace("/")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -92,11 +98,43 @@ export const makeBucket = bucket => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const deleteBucket = bucket => {
|
||||||
|
return function(dispatch) {
|
||||||
|
return web
|
||||||
|
.DeleteBucket({
|
||||||
|
bucketName: bucket
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
dispatch(
|
||||||
|
alertActions.set({
|
||||||
|
type: "info",
|
||||||
|
message: "Bucket '" + bucket + "' has been deleted."
|
||||||
|
})
|
||||||
|
)
|
||||||
|
dispatch(removeBucket(bucket))
|
||||||
|
dispatch(fetchBuckets("delete"))
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
dispatch(
|
||||||
|
alertActions.set({
|
||||||
|
type: "danger",
|
||||||
|
message: err.message
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const addBucket = bucket => ({
|
export const addBucket = bucket => ({
|
||||||
type: ADD,
|
type: ADD,
|
||||||
bucket
|
bucket
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const removeBucket = bucket => ({
|
||||||
|
type: REMOVE,
|
||||||
|
bucket
|
||||||
|
})
|
||||||
|
|
||||||
export const showMakeBucketModal = () => ({
|
export const showMakeBucketModal = () => ({
|
||||||
type: SHOW_MAKE_BUCKET_MODAL,
|
type: SHOW_MAKE_BUCKET_MODAL,
|
||||||
show: true
|
show: true
|
||||||
@ -106,3 +144,42 @@ export const hideMakeBucketModal = () => ({
|
|||||||
type: SHOW_MAKE_BUCKET_MODAL,
|
type: SHOW_MAKE_BUCKET_MODAL,
|
||||||
show: false
|
show: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const fetchPolicies = bucket => {
|
||||||
|
return function(dispatch) {
|
||||||
|
return web
|
||||||
|
.ListAllBucketPolicies({
|
||||||
|
bucketName: bucket
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
let policies = res.policies
|
||||||
|
if(policies)
|
||||||
|
dispatch(setPolicies(policies))
|
||||||
|
else
|
||||||
|
dispatch(setPolicies([]))
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
dispatch(
|
||||||
|
alertActions.set({
|
||||||
|
type: "danger",
|
||||||
|
message: err.message
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setPolicies = policies => ({
|
||||||
|
type: SET_POLICIES,
|
||||||
|
policies
|
||||||
|
})
|
||||||
|
|
||||||
|
export const showBucketPolicy = () => ({
|
||||||
|
type: SHOW_BUCKET_POLICY,
|
||||||
|
show: true
|
||||||
|
})
|
||||||
|
|
||||||
|
export const hideBucketPolicy = () => ({
|
||||||
|
type: SHOW_BUCKET_POLICY,
|
||||||
|
show: false
|
||||||
|
})
|
@ -16,12 +16,22 @@
|
|||||||
|
|
||||||
import * as actionsBuckets from "./actions"
|
import * as actionsBuckets from "./actions"
|
||||||
|
|
||||||
|
const removeBucket = (list, action) => {
|
||||||
|
const idx = list.findIndex(bucket => bucket === action.bucket)
|
||||||
|
if (idx == -1) {
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
return [...list.slice(0, idx), ...list.slice(idx + 1)]
|
||||||
|
}
|
||||||
|
|
||||||
export default (
|
export default (
|
||||||
state = {
|
state = {
|
||||||
list: [],
|
list: [],
|
||||||
filter: "",
|
filter: "",
|
||||||
currentBucket: "",
|
currentBucket: "",
|
||||||
showMakeBucketModal: false
|
showMakeBucketModal: false,
|
||||||
|
policies: [],
|
||||||
|
showBucketPolicy: false
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@ -36,6 +46,11 @@ export default (
|
|||||||
...state,
|
...state,
|
||||||
list: [action.bucket, ...state.list]
|
list: [action.bucket, ...state.list]
|
||||||
}
|
}
|
||||||
|
case actionsBuckets.REMOVE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
list: removeBucket(state.list, action),
|
||||||
|
}
|
||||||
case actionsBuckets.SET_FILTER:
|
case actionsBuckets.SET_FILTER:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -51,6 +66,16 @@ export default (
|
|||||||
...state,
|
...state,
|
||||||
showMakeBucketModal: action.show
|
showMakeBucketModal: action.show
|
||||||
}
|
}
|
||||||
|
case actionsBuckets.SET_POLICIES:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
policies: action.policies
|
||||||
|
}
|
||||||
|
case actionsBuckets.SHOW_BUCKET_POLICY:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
showBucketPolicy: action.show
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from '../constants'
|
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react'
|
|
||||||
import connect from 'react-redux/lib/components/connect'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import * as actions from '../actions'
|
|
||||||
|
|
||||||
class Policy extends Component {
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context)
|
|
||||||
this.state = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePolicyChange(e) {
|
|
||||||
this.setState({
|
|
||||||
policy: {
|
|
||||||
policy: e.target.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
removePolicy(e) {
|
|
||||||
e.preventDefault()
|
|
||||||
const {dispatch, currentBucket, prefix} = this.props
|
|
||||||
let newPrefix = prefix.replace(currentBucket + '/', '')
|
|
||||||
newPrefix = newPrefix.replace('*', '')
|
|
||||||
web.SetBucketPolicy({
|
|
||||||
bucketName: currentBucket,
|
|
||||||
prefix: newPrefix,
|
|
||||||
policy: 'none'
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
dispatch(actions.setPolicies(this.props.policies.filter(policy => policy.prefix != prefix)))
|
|
||||||
})
|
|
||||||
.catch(e => dispatch(actions.showAlert({
|
|
||||||
type: 'danger',
|
|
||||||
message: e.message,
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {policy, prefix, currentBucket} = this.props
|
|
||||||
let newPrefix = prefix.replace(currentBucket + '/', '')
|
|
||||||
newPrefix = newPrefix.replace('*', '')
|
|
||||||
|
|
||||||
if (!newPrefix)
|
|
||||||
newPrefix = '*'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="pmb-list">
|
|
||||||
<div className="pmbl-item">
|
|
||||||
{ newPrefix }
|
|
||||||
</div>
|
|
||||||
<div className="pmbl-item">
|
|
||||||
<select className="form-control"
|
|
||||||
disabled
|
|
||||||
value={ policy }
|
|
||||||
onChange={ this.handlePolicyChange.bind(this) }>
|
|
||||||
<option value={ READ_ONLY }>
|
|
||||||
Read Only
|
|
||||||
</option>
|
|
||||||
<option value={ WRITE_ONLY }>
|
|
||||||
Write Only
|
|
||||||
</option>
|
|
||||||
<option value={ READ_WRITE }>
|
|
||||||
Read and Write
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="pmbl-item">
|
|
||||||
<button className="btn btn-block btn-danger" onClick={ this.removePolicy.bind(this) }>
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(state => state)(Policy)
|
|
@ -1,98 +0,0 @@
|
|||||||
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from '../constants'
|
|
||||||
import React, { Component, PropTypes } from 'react'
|
|
||||||
import connect from 'react-redux/lib/components/connect'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import * as actions from '../actions'
|
|
||||||
|
|
||||||
class PolicyInput extends Component {
|
|
||||||
componentDidMount() {
|
|
||||||
const {web, dispatch} = this.props
|
|
||||||
this.prefix.focus()
|
|
||||||
web.ListAllBucketPolicies({
|
|
||||||
bucketName: this.props.currentBucket
|
|
||||||
}).then(res => {
|
|
||||||
let policies = res.policies
|
|
||||||
if (policies) dispatch(actions.setPolicies(policies))
|
|
||||||
}).catch(err => {
|
|
||||||
dispatch(actions.showAlert({
|
|
||||||
type: 'danger',
|
|
||||||
message: err.message
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
const {dispatch} = this.props
|
|
||||||
dispatch(actions.setPolicies([]))
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePolicySubmit(e) {
|
|
||||||
e.preventDefault()
|
|
||||||
const {web, dispatch, currentBucket} = this.props
|
|
||||||
|
|
||||||
let prefix = currentBucket + '/' + this.prefix.value
|
|
||||||
let policy = this.policy.value
|
|
||||||
|
|
||||||
if (!prefix.endsWith('*')) prefix = prefix + '*'
|
|
||||||
|
|
||||||
let prefixAlreadyExists = this.props.policies.some(elem => prefix === elem.prefix)
|
|
||||||
|
|
||||||
if (prefixAlreadyExists) {
|
|
||||||
dispatch(actions.showAlert({
|
|
||||||
type: 'danger',
|
|
||||||
message: "Policy for this prefix already exists."
|
|
||||||
}))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
web.SetBucketPolicy({
|
|
||||||
bucketName: this.props.currentBucket,
|
|
||||||
prefix: this.prefix.value,
|
|
||||||
policy: this.policy.value
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
dispatch(actions.setPolicies([{
|
|
||||||
policy, prefix
|
|
||||||
}, ...this.props.policies]))
|
|
||||||
this.prefix.value = ''
|
|
||||||
})
|
|
||||||
.catch(e => dispatch(actions.showAlert({
|
|
||||||
type: 'danger',
|
|
||||||
message: e.message,
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<header className="pmb-list">
|
|
||||||
<div className="pmbl-item">
|
|
||||||
<input type="text"
|
|
||||||
ref={ prefix => this.prefix = prefix }
|
|
||||||
className="form-control"
|
|
||||||
placeholder="Prefix"
|
|
||||||
editable={ true } />
|
|
||||||
</div>
|
|
||||||
<div className="pmbl-item">
|
|
||||||
<select ref={ policy => this.policy = policy } className="form-control">
|
|
||||||
<option value={ READ_ONLY }>
|
|
||||||
Read Only
|
|
||||||
</option>
|
|
||||||
<option value={ WRITE_ONLY }>
|
|
||||||
Write Only
|
|
||||||
</option>
|
|
||||||
<option value={ READ_WRITE }>
|
|
||||||
Read and Write
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="pmbl-item">
|
|
||||||
<button className="btn btn-block btn-primary" onClick={ this.handlePolicySubmit.bind(this) }>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(state => state)(PolicyInput)
|
|
@ -58,7 +58,8 @@ export const fetchObjects = append => {
|
|||||||
buckets: { currentBucket },
|
buckets: { currentBucket },
|
||||||
objects: { currentPrefix, marker }
|
objects: { currentPrefix, marker }
|
||||||
} = getState()
|
} = getState()
|
||||||
return web
|
if (currentBucket) {
|
||||||
|
return web
|
||||||
.ListObjects({
|
.ListObjects({
|
||||||
bucketName: currentBucket,
|
bucketName: currentBucket,
|
||||||
prefix: currentPrefix,
|
prefix: currentPrefix,
|
||||||
@ -87,6 +88,7 @@ export const fetchObjects = append => {
|
|||||||
dispatch(alertActions.set({ type: "danger", message: err.message }))
|
dispatch(alertActions.set({ type: "danger", message: err.message }))
|
||||||
history.push("/login")
|
history.push("/login")
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,7 +280,7 @@ func (h minioReservedBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
|
|||||||
default:
|
default:
|
||||||
// For all other requests reject access to reserved
|
// For all other requests reject access to reserved
|
||||||
// buckets
|
// buckets
|
||||||
bucketName, _ := urlPath2BucketObjectName(r.URL)
|
bucketName, _ := urlPath2BucketObjectName(r.URL.Path)
|
||||||
if isMinioReservedBucket(bucketName) || isMinioMetaBucket(bucketName) {
|
if isMinioReservedBucket(bucketName) || isMinioMetaBucket(bucketName) {
|
||||||
writeErrorResponse(w, ErrAllAccessDisabled, r.URL)
|
writeErrorResponse(w, ErrAllAccessDisabled, r.URL)
|
||||||
return
|
return
|
||||||
@ -439,7 +439,7 @@ var notimplementedObjectResourceNames = map[string]bool{
|
|||||||
|
|
||||||
// Resource handler ServeHTTP() wrapper
|
// Resource handler ServeHTTP() wrapper
|
||||||
func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
bucketName, objectName := urlPath2BucketObjectName(r.URL)
|
bucketName, objectName := urlPath2BucketObjectName(r.URL.Path)
|
||||||
|
|
||||||
// If bucketName is present and not objectName check for bucket level resource queries.
|
// If bucketName is present and not objectName check for bucket level resource queries.
|
||||||
if bucketName != "" && objectName == "" {
|
if bucketName != "" && objectName == "" {
|
||||||
|
@ -62,14 +62,9 @@ func cloneHeader(h http.Header) http.Header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert url path into bucket and object name.
|
// Convert url path into bucket and object name.
|
||||||
func urlPath2BucketObjectName(u *url.URL) (bucketName, objectName string) {
|
func urlPath2BucketObjectName(path string) (bucketName, objectName string) {
|
||||||
if u == nil {
|
|
||||||
// Empty url, return bucket and object names.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim any preceding slash separator.
|
// Trim any preceding slash separator.
|
||||||
urlPath := strings.TrimPrefix(u.Path, slashSeparator)
|
urlPath := strings.TrimPrefix(path, slashSeparator)
|
||||||
|
|
||||||
// Split urlpath using slash separator into a given number of
|
// Split urlpath using slash separator into a given number of
|
||||||
// expected tokens.
|
// expected tokens.
|
||||||
|
@ -205,12 +205,6 @@ func TestURL2BucketObjectName(t *testing.T) {
|
|||||||
bucket: "bucket",
|
bucket: "bucket",
|
||||||
object: "///object////",
|
object: "///object////",
|
||||||
},
|
},
|
||||||
// Test case 8 url is not allocated.
|
|
||||||
{
|
|
||||||
u: nil,
|
|
||||||
bucket: "",
|
|
||||||
object: "",
|
|
||||||
},
|
|
||||||
// Test case 9 url path is empty.
|
// Test case 9 url path is empty.
|
||||||
{
|
{
|
||||||
u: &url.URL{},
|
u: &url.URL{},
|
||||||
@ -221,7 +215,7 @@ func TestURL2BucketObjectName(t *testing.T) {
|
|||||||
|
|
||||||
// Validate all test cases.
|
// Validate all test cases.
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
bucketName, objectName := urlPath2BucketObjectName(testCase.u)
|
bucketName, objectName := urlPath2BucketObjectName(testCase.u.Path)
|
||||||
if bucketName != testCase.bucket {
|
if bucketName != testCase.bucket {
|
||||||
t.Errorf("Test %d: failed expected bucket name \"%s\", got \"%s\"", i+1, testCase.bucket, bucketName)
|
t.Errorf("Test %d: failed expected bucket name \"%s\", got \"%s\"", i+1, testCase.bucket, bucketName)
|
||||||
}
|
}
|
||||||
|
@ -741,6 +741,7 @@ type ListAllBucketPoliciesArgs struct {
|
|||||||
|
|
||||||
// BucketAccessPolicy - Collection of canned bucket policy at a given prefix.
|
// BucketAccessPolicy - Collection of canned bucket policy at a given prefix.
|
||||||
type BucketAccessPolicy struct {
|
type BucketAccessPolicy struct {
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
Prefix string `json:"prefix"`
|
Prefix string `json:"prefix"`
|
||||||
Policy policy.BucketPolicy `json:"policy"`
|
Policy policy.BucketPolicy `json:"policy"`
|
||||||
}
|
}
|
||||||
@ -770,8 +771,11 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB
|
|||||||
}
|
}
|
||||||
reply.UIVersion = browser.UIVersion
|
reply.UIVersion = browser.UIVersion
|
||||||
for prefix, policy := range policy.GetPolicies(policyInfo.Statements, args.BucketName, "") {
|
for prefix, policy := range policy.GetPolicies(policyInfo.Statements, args.BucketName, "") {
|
||||||
|
bucketName, objectPrefix := urlPath2BucketObjectName(prefix)
|
||||||
|
objectPrefix = strings.TrimSuffix(objectPrefix, "*")
|
||||||
reply.Policies = append(reply.Policies, BucketAccessPolicy{
|
reply.Policies = append(reply.Policies, BucketAccessPolicy{
|
||||||
Prefix: prefix,
|
Bucket: bucketName,
|
||||||
|
Prefix: objectPrefix,
|
||||||
Policy: policy,
|
Policy: policy,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -822,6 +826,23 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = json.Marshal(policyInfo)
|
||||||
|
if err != nil {
|
||||||
|
return toJSONError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse check bucket policy.
|
||||||
|
if s3Error := checkBucketPolicyResources(args.BucketName, policyInfo); s3Error != ErrNone {
|
||||||
|
apiErr := getAPIError(s3Error)
|
||||||
|
var err error
|
||||||
|
if apiErr.Code == "XMinioPolicyNesting" {
|
||||||
|
err = PolicyNesting{}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf(apiErr.Description)
|
||||||
|
}
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
|
||||||
// Parse validate and save bucket policy.
|
// Parse validate and save bucket policy.
|
||||||
if err := objectAPI.SetBucketPolicy(context.Background(), args.BucketName, policyInfo); err != nil {
|
if err := objectAPI.SetBucketPolicy(context.Background(), args.BucketName, policyInfo); err != nil {
|
||||||
return toJSONError(err, args.BucketName)
|
return toJSONError(err, args.BucketName)
|
||||||
|
@ -1385,7 +1385,8 @@ func testWebListAllBucketPoliciesHandler(obj ObjectLayer, instanceType string, t
|
|||||||
}
|
}
|
||||||
|
|
||||||
testCaseResult1 := []BucketAccessPolicy{{
|
testCaseResult1 := []BucketAccessPolicy{{
|
||||||
Prefix: bucketName + "/hello*",
|
Bucket: bucketName,
|
||||||
|
Prefix: "hello",
|
||||||
Policy: policy.BucketPolicyReadWrite,
|
Policy: policy.BucketPolicyReadWrite,
|
||||||
}}
|
}}
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user