mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05:00
allow users to change password through browser (#7683)
Allow IAM users to change the password using browser UI.
This commit is contained in:
parent
74e2fe0879
commit
da8214845a
@ -18,79 +18,54 @@ import React from "react"
|
|||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import web from "../web"
|
import web from "../web"
|
||||||
import * as alertActions from "../alert/actions"
|
import * as alertActions from "../alert/actions"
|
||||||
|
import { getRandomAccessKey, getRandomSecretKey } from "../utils"
|
||||||
|
import jwtDecode from "jwt-decode"
|
||||||
|
import classNames from "classnames"
|
||||||
|
|
||||||
import {
|
import { Modal, ModalBody, ModalHeader } from "react-bootstrap"
|
||||||
Tooltip,
|
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalHeader,
|
|
||||||
OverlayTrigger
|
|
||||||
} from "react-bootstrap"
|
|
||||||
import InputGroup from "./InputGroup"
|
import InputGroup from "./InputGroup"
|
||||||
|
|
||||||
export class ChangePasswordModal extends React.Component {
|
export class ChangePasswordModal extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
accessKey: "",
|
currentAccessKey: "",
|
||||||
secretKey: "",
|
currentSecretKey: "",
|
||||||
keysReadOnly: false
|
currentSecretKeyVisible: false,
|
||||||
|
newAccessKey: "",
|
||||||
|
newSecretKey: "",
|
||||||
|
newSecretKeyVisible: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// When its shown, it loads the access key and secret key.
|
// When its shown, it loads the access key from JWT token
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
const { serverInfo } = this.props
|
const token = jwtDecode(web.GetToken())
|
||||||
|
|
||||||
// Check environment variables first.
|
|
||||||
if (serverInfo.info.isEnvCreds || serverInfo.info.isWorm) {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
accessKey: "xxxxxxxxx",
|
currentAccessKey: token.sub,
|
||||||
secretKey: "xxxxxxxxx",
|
newAccessKey: token.sub
|
||||||
keysReadOnly: true
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
web.GetAuth().then(data => {
|
|
||||||
this.setState({
|
|
||||||
accessKey: data.accessKey,
|
|
||||||
secretKey: data.secretKey
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle field changes from inside the modal.
|
|
||||||
accessKeyChange(e) {
|
|
||||||
this.setState({
|
|
||||||
accessKey: e.target.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
secretKeyChange(e) {
|
|
||||||
this.setState({
|
|
||||||
secretKey: e.target.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
secretKeyVisible(secretKeyVisible) {
|
|
||||||
this.setState({
|
|
||||||
secretKeyVisible
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the auth params and set them.
|
// Save the auth params and set them.
|
||||||
setAuth(e) {
|
setAuth(e) {
|
||||||
const { showAlert } = this.props
|
const { showAlert } = this.props
|
||||||
const accessKey = this.state.accessKey
|
|
||||||
const secretKey = this.state.secretKey
|
if (this.canUpdateCredentials()) {
|
||||||
|
const currentAccessKey = this.state.currentAccessKey
|
||||||
|
const currentSecretKey = this.state.currentSecretKey
|
||||||
|
const newAccessKey = this.state.newAccessKey
|
||||||
|
const newSecretKey = this.state.newSecretKey
|
||||||
web
|
web
|
||||||
.SetAuth({
|
.SetAuth({
|
||||||
accessKey,
|
currentAccessKey,
|
||||||
secretKey
|
currentSecretKey,
|
||||||
|
newAccessKey,
|
||||||
|
newSecretKey
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
showAlert({
|
showAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
message: "Changed credentials"
|
message: "Credentials updated successfully."
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
@ -100,75 +75,179 @@ export class ChangePasswordModal extends React.Component {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
generateAuth(e) {
|
generateAuth(e) {
|
||||||
web.GenerateAuth().then(data => {
|
const { serverInfo } = this.props
|
||||||
|
// Generate random access key only for root user
|
||||||
|
if (!serverInfo.userInfo.isIAMUser) {
|
||||||
this.setState({
|
this.setState({
|
||||||
accessKey: data.accessKey,
|
newAccessKey: getRandomAccessKey()
|
||||||
secretKey: data.secretKey,
|
|
||||||
secretKeyVisible: true
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
newSecretKey: getRandomSecretKey(),
|
||||||
|
newSecretKeyVisible: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
canChangePassword() {
|
||||||
|
const { serverInfo } = this.props
|
||||||
|
// Password change is not allowed in WORM mode
|
||||||
|
if (serverInfo.info.isWorm) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// When credentials are set on ENV, password change not allowed for owner
|
||||||
|
if (serverInfo.info.isEnvCreds && !serverInfo.userInfo.isIAMUser) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
canUpdateCredentials() {
|
||||||
|
return (
|
||||||
|
this.state.currentAccessKey.length > 0 &&
|
||||||
|
this.state.currentSecretKey.length > 0 &&
|
||||||
|
this.state.newAccessKey.length > 0 &&
|
||||||
|
this.state.newSecretKey.length > 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { hideChangePassword } = this.props
|
const { hideChangePassword, serverInfo } = this.props
|
||||||
|
const allowChangePassword = this.canChangePassword()
|
||||||
|
|
||||||
|
if (!allowChangePassword) {
|
||||||
|
return (
|
||||||
|
<Modal bsSize="sm" animation={false} show={true}>
|
||||||
|
<ModalHeader>Change Password</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
Credentials of this user cannot be updated through MinIO Browser.
|
||||||
|
</ModalBody>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button
|
||||||
|
id="cancel-change-password"
|
||||||
|
className="btn btn-link"
|
||||||
|
onClick={hideChangePassword}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal bsSize="sm" animation={false} show={true}>
|
<Modal bsSize="sm" animation={false} show={true}>
|
||||||
<ModalHeader>Change Password</ModalHeader>
|
<ModalHeader>Change Password</ModalHeader>
|
||||||
<ModalBody className="m-t-20">
|
<ModalBody className="m-t-20">
|
||||||
|
<div className="has-toggle-password">
|
||||||
<InputGroup
|
<InputGroup
|
||||||
value={this.state.accessKey}
|
value={this.state.currentAccessKey}
|
||||||
onChange={this.accessKeyChange.bind(this)}
|
id="currentAccessKey"
|
||||||
id="accessKey"
|
label="Current Access Key"
|
||||||
label="Access Key"
|
name="currentAccesskey"
|
||||||
name="accesskey"
|
|
||||||
type="text"
|
type="text"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
required="required"
|
required="required"
|
||||||
autoComplete="false"
|
autoComplete="false"
|
||||||
align="ig-left"
|
align="ig-left"
|
||||||
readonly={this.state.keysReadOnly}
|
readonly={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<i
|
<i
|
||||||
onClick={this.secretKeyVisible.bind(
|
onClick={() => {
|
||||||
this,
|
this.setState({
|
||||||
!this.state.secretKeyVisible
|
currentSecretKeyVisible: !this.state.currentSecretKeyVisible
|
||||||
)}
|
})
|
||||||
|
}}
|
||||||
className={
|
className={
|
||||||
"toggle-password fa fa-eye " +
|
"toggle-password fa fa-eye " +
|
||||||
(this.state.secretKeyVisible ? "toggled" : "")
|
(this.state.currentSecretKeyVisible ? "toggled" : "")
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
value={this.state.secretKey}
|
value={this.state.currentSecretKey}
|
||||||
onChange={this.secretKeyChange.bind(this)}
|
onChange={e => {
|
||||||
id="secretKey"
|
this.setState({ currentSecretKey: e.target.value })
|
||||||
label="Secret Key"
|
}}
|
||||||
name="accesskey"
|
id="currentSecretKey"
|
||||||
type={this.state.secretKeyVisible ? "text" : "password"}
|
label="Current Secret Key"
|
||||||
|
name="currentSecretKey"
|
||||||
|
type={this.state.currentSecretKeyVisible ? "text" : "password"}
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
required="required"
|
required="required"
|
||||||
autoComplete="false"
|
autoComplete="false"
|
||||||
align="ig-left"
|
align="ig-left"
|
||||||
readonly={this.state.keysReadOnly}
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="has-toggle-password m-t-30">
|
||||||
|
{!serverInfo.userInfo.isIAMUser && (
|
||||||
|
<InputGroup
|
||||||
|
value={this.state.newAccessKey}
|
||||||
|
id="newAccessKey"
|
||||||
|
label={"New Access Key"}
|
||||||
|
name="newAccesskey"
|
||||||
|
type="text"
|
||||||
|
spellCheck="false"
|
||||||
|
required="required"
|
||||||
|
autoComplete="false"
|
||||||
|
align="ig-left"
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ newAccessKey: e.target.value })
|
||||||
|
}}
|
||||||
|
readonly={serverInfo.userInfo.isIAMUser}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<i
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({
|
||||||
|
newSecretKeyVisible: !this.state.newSecretKeyVisible
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
"toggle-password fa fa-eye " +
|
||||||
|
(this.state.newSecretKeyVisible ? "toggled" : "")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InputGroup
|
||||||
|
value={this.state.newSecretKey}
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ newSecretKey: e.target.value })
|
||||||
|
}}
|
||||||
|
id="newSecretKey"
|
||||||
|
label="New Secret Key"
|
||||||
|
name="newSecretKey"
|
||||||
|
type={this.state.newSecretKeyVisible ? "text" : "password"}
|
||||||
|
spellCheck="false"
|
||||||
|
required="required"
|
||||||
|
autoComplete="false"
|
||||||
|
align="ig-left"
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ newSecretKey: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
<button
|
<button
|
||||||
id="generate-keys"
|
id="generate-keys"
|
||||||
className={
|
className={"btn btn-primary"}
|
||||||
"btn btn-primary " + (this.state.keysReadOnly ? "hidden" : "")
|
|
||||||
}
|
|
||||||
onClick={this.generateAuth.bind(this)}
|
onClick={this.generateAuth.bind(this)}
|
||||||
>
|
>
|
||||||
Generate
|
Generate
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="update-keys"
|
id="update-keys"
|
||||||
className={
|
className={classNames({
|
||||||
"btn btn-success " + (this.state.keysReadOnly ? "hidden" : "")
|
btn: true,
|
||||||
}
|
"btn-success": this.canUpdateCredentials()
|
||||||
|
})}
|
||||||
|
disabled={!this.canUpdateCredentials()}
|
||||||
onClick={this.setAuth.bind(this)}
|
onClick={this.setAuth.bind(this)}
|
||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
@ -198,4 +277,7 @@ const mapDispatchToProps = dispatch => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ChangePasswordModal)
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ChangePasswordModal)
|
||||||
|
@ -17,21 +17,38 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { shallow, mount } from "enzyme"
|
import { shallow, mount } from "enzyme"
|
||||||
import { ChangePasswordModal } from "../ChangePasswordModal"
|
import { ChangePasswordModal } from "../ChangePasswordModal"
|
||||||
|
import jwtDecode from "jwt-decode"
|
||||||
|
|
||||||
|
jest.mock("jwt-decode")
|
||||||
|
|
||||||
|
jwtDecode.mockImplementation(() => ({ sub: "minio" }))
|
||||||
|
|
||||||
jest.mock("../../web", () => ({
|
jest.mock("../../web", () => ({
|
||||||
GetAuth: jest.fn(() => {
|
|
||||||
return Promise.resolve({ accessKey: "test1", secretKey: "test2" })
|
|
||||||
}),
|
|
||||||
GenerateAuth: jest.fn(() => {
|
GenerateAuth: jest.fn(() => {
|
||||||
return Promise.resolve({ accessKey: "gen1", secretKey: "gen2" })
|
return Promise.resolve({ accessKey: "gen1", secretKey: "gen2" })
|
||||||
}),
|
}),
|
||||||
SetAuth: jest.fn(({ accessKey, secretKey }) => {
|
SetAuth: jest.fn(
|
||||||
if (accessKey == "test3" && secretKey == "test4") {
|
({ currentAccessKey, currentSecretKey, newAccessKey, newSecretKey }) => {
|
||||||
|
if (
|
||||||
|
currentAccessKey == "minio" &&
|
||||||
|
currentSecretKey == "minio123" &&
|
||||||
|
newAccessKey == "test" &&
|
||||||
|
newSecretKey == "test123"
|
||||||
|
) {
|
||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject({ message: "Error" })
|
return Promise.reject({
|
||||||
}
|
message: "Error"
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
GetToken: jest.fn(() => "")
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock("../../utils", () => ({
|
||||||
|
getRandomAccessKey: () => "raccesskey",
|
||||||
|
getRandomSecretKey: () => "rsecretkey"
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe("ChangePasswordModal", () => {
|
describe("ChangePasswordModal", () => {
|
||||||
@ -40,57 +57,93 @@ describe("ChangePasswordModal", () => {
|
|||||||
memory: "test",
|
memory: "test",
|
||||||
platform: "test",
|
platform: "test",
|
||||||
runtime: "test",
|
runtime: "test",
|
||||||
info: { isEnvCreds: false }
|
info: { isEnvCreds: false },
|
||||||
|
userInfo: { isIAMUser: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should render without crashing", () => {
|
it("should render without crashing", () => {
|
||||||
shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should get the keys when its rendered", () => {
|
it("should not allow changing password when isWorm is true", () => {
|
||||||
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
const newServerInfo = { ...serverInfo, info: { isWorm: true } }
|
||||||
setImmediate(() => {
|
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
||||||
expect(wrapper.state("accessKey")).toBe("test1")
|
expect(
|
||||||
expect(wrapper.state("secretKey")).toBe("test2")
|
wrapper
|
||||||
})
|
.find("ModalBody")
|
||||||
|
.childAt(0)
|
||||||
|
.text()
|
||||||
|
).toBe("Credentials of this user cannot be updated through MinIO Browser.")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should show readonly keys when isEnvCreds is true", () => {
|
it("should not allow changing password when isEnvCreds is true and not IAM user", () => {
|
||||||
const newServerInfo = { ...serverInfo, info: { isEnvCreds: true } }
|
const newServerInfo = {
|
||||||
|
...serverInfo,
|
||||||
|
info: { isEnvCreds: true },
|
||||||
|
userInfo: { isIAMUser: false }
|
||||||
|
}
|
||||||
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
||||||
expect(wrapper.state("accessKey")).toBe("xxxxxxxxx")
|
expect(
|
||||||
expect(wrapper.state("secretKey")).toBe("xxxxxxxxx")
|
wrapper
|
||||||
expect(wrapper.find("#accessKey").prop("readonly")).toBeTruthy()
|
.find("ModalBody")
|
||||||
expect(wrapper.find("#secretKey").prop("readonly")).toBeTruthy()
|
.childAt(0)
|
||||||
expect(wrapper.find("#generate-keys").hasClass("hidden")).toBeTruthy()
|
.text()
|
||||||
expect(wrapper.find("#update-keys").hasClass("hidden")).toBeTruthy()
|
).toBe("Credentials of this user cannot be updated through MinIO Browser.")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should generate accessKey and secretKey when Generate buttons is clicked", () => {
|
it("should generate accessKey and secretKey when Generate buttons is clicked", () => {
|
||||||
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />)
|
||||||
wrapper.find("#generate-keys").simulate("click")
|
wrapper.find("#generate-keys").simulate("click")
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
expect(wrapper.state("accessKey")).toBe("gen1")
|
expect(wrapper.state("newAccessKey")).toBe("raccesskey")
|
||||||
expect(wrapper.state("secretKey")).toBe("gen2")
|
expect(wrapper.state("newSecretKey")).toBe("rsecretkey")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should not generate accessKey for IAM User", () => {
|
||||||
|
const newServerInfo = {
|
||||||
|
...serverInfo,
|
||||||
|
userInfo: { isIAMUser: true }
|
||||||
|
}
|
||||||
|
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
||||||
|
wrapper.find("#generate-keys").simulate("click")
|
||||||
|
setImmediate(() => {
|
||||||
|
expect(wrapper.state("newAccessKey")).toBe("minio")
|
||||||
|
expect(wrapper.state("newSecretKey")).toBe("rsecretkey")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should not show new accessKey field for IAM User", () => {
|
||||||
|
const newServerInfo = {
|
||||||
|
...serverInfo,
|
||||||
|
userInfo: { isIAMUser: true }
|
||||||
|
}
|
||||||
|
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />)
|
||||||
|
expect(wrapper.find("#newAccesskey").exists()).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
it("should update accessKey and secretKey when Update button is clicked", () => {
|
it("should update accessKey and secretKey when Update button is clicked", () => {
|
||||||
const showAlert = jest.fn()
|
const showAlert = jest.fn()
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ChangePasswordModal serverInfo={serverInfo} showAlert={showAlert} />
|
<ChangePasswordModal serverInfo={serverInfo} showAlert={showAlert} />
|
||||||
)
|
)
|
||||||
wrapper
|
wrapper
|
||||||
.find("#accessKey")
|
.find("#currentAccessKey")
|
||||||
.simulate("change", { target: { value: "test3" } })
|
.simulate("change", { target: { value: "minio" } })
|
||||||
wrapper
|
wrapper
|
||||||
.find("#secretKey")
|
.find("#currentSecretKey")
|
||||||
.simulate("change", { target: { value: "test4" } })
|
.simulate("change", { target: { value: "minio123" } })
|
||||||
|
wrapper
|
||||||
|
.find("#newAccessKey")
|
||||||
|
.simulate("change", { target: { value: "test" } })
|
||||||
|
wrapper
|
||||||
|
.find("#newSecretKey")
|
||||||
|
.simulate("change", { target: { value: "test123" } })
|
||||||
wrapper.find("#update-keys").simulate("click")
|
wrapper.find("#update-keys").simulate("click")
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
expect(showAlert).toHaveBeenCalledWith({
|
expect(showAlert).toHaveBeenCalledWith({
|
||||||
type: "success",
|
type: "success",
|
||||||
message: "Changed credentials"
|
message: "Credentials updated successfully."
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -54,7 +54,8 @@ export const fetchServerInfo = () => {
|
|||||||
memory: res.MinioMemory,
|
memory: res.MinioMemory,
|
||||||
platform: res.MinioPlatform,
|
platform: res.MinioPlatform,
|
||||||
runtime: res.MinioRuntime,
|
runtime: res.MinioRuntime,
|
||||||
info: res.MinioGlobalInfo
|
info: res.MinioGlobalInfo,
|
||||||
|
userInfo: res.MinioUserInfo
|
||||||
}
|
}
|
||||||
dispatch(setServerInfo(serverInfo))
|
dispatch(setServerInfo(serverInfo))
|
||||||
})
|
})
|
||||||
|
@ -14,11 +14,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { minioBrowserPrefix } from './constants.js'
|
import { minioBrowserPrefix } from "./constants.js"
|
||||||
|
|
||||||
export const sortObjectsByName = (objects, order) => {
|
export const sortObjectsByName = (objects, order) => {
|
||||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
let folders = objects.filter(object => object.name.endsWith("/"))
|
||||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
let files = objects.filter(object => !object.name.endsWith("/"))
|
||||||
folders = folders.sort((a, b) => {
|
folders = folders.sort((a, b) => {
|
||||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1
|
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1
|
||||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
|
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
|
||||||
@ -37,32 +37,34 @@ export const sortObjectsByName = (objects, order) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const sortObjectsBySize = (objects, order) => {
|
export const sortObjectsBySize = (objects, order) => {
|
||||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
let folders = objects.filter(object => object.name.endsWith("/"))
|
||||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
let files = objects.filter(object => !object.name.endsWith("/"))
|
||||||
files = files.sort((a, b) => a.size - b.size)
|
files = files.sort((a, b) => a.size - b.size)
|
||||||
if (order)
|
if (order) files = files.reverse()
|
||||||
files = files.reverse()
|
|
||||||
return [...folders, ...files]
|
return [...folders, ...files]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sortObjectsByDate = (objects, order) => {
|
export const sortObjectsByDate = (objects, order) => {
|
||||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
let folders = objects.filter(object => object.name.endsWith("/"))
|
||||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
let files = objects.filter(object => !object.name.endsWith("/"))
|
||||||
files = files.sort((a, b) => new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime())
|
files = files.sort(
|
||||||
if (order)
|
(a, b) =>
|
||||||
files = files.reverse()
|
new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime()
|
||||||
|
)
|
||||||
|
if (order) files = files.reverse()
|
||||||
return [...folders, ...files]
|
return [...folders, ...files]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const pathSlice = (path) => {
|
export const pathSlice = path => {
|
||||||
path = path.replace(minioBrowserPrefix, '')
|
path = path.replace(minioBrowserPrefix, "")
|
||||||
let prefix = ''
|
let prefix = ""
|
||||||
let bucket = ''
|
let bucket = ""
|
||||||
if (!path) return {
|
if (!path)
|
||||||
|
return {
|
||||||
bucket,
|
bucket,
|
||||||
prefix
|
prefix
|
||||||
}
|
}
|
||||||
let objectIndex = path.indexOf('/', 1)
|
let objectIndex = path.indexOf("/", 1)
|
||||||
if (objectIndex == -1) {
|
if (objectIndex == -1) {
|
||||||
bucket = path.slice(1)
|
bucket = path.slice(1)
|
||||||
return {
|
return {
|
||||||
@ -79,7 +81,29 @@ export const pathSlice = (path) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const pathJoin = (bucket, prefix) => {
|
export const pathJoin = (bucket, prefix) => {
|
||||||
if (!prefix)
|
if (!prefix) prefix = ""
|
||||||
prefix = ''
|
return minioBrowserPrefix + "/" + bucket + "/" + prefix
|
||||||
return minioBrowserPrefix + '/' + bucket + '/' + prefix
|
}
|
||||||
|
|
||||||
|
export const getRandomAccessKey = () => {
|
||||||
|
const alphaNumericTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
let arr = new Uint8Array(20)
|
||||||
|
window.crypto.getRandomValues(arr)
|
||||||
|
const random = Array.prototype.map.call(arr, v => {
|
||||||
|
const i = v % alphaNumericTable.length
|
||||||
|
return alphaNumericTable.charAt(i)
|
||||||
|
})
|
||||||
|
return random.join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getRandomSecretKey = () => {
|
||||||
|
let arr = new Uint8Array(40)
|
||||||
|
window.crypto.getRandomValues(arr)
|
||||||
|
const binStr = Array.prototype.map
|
||||||
|
.call(arr, v => {
|
||||||
|
return String.fromCharCode(v)
|
||||||
|
})
|
||||||
|
.join("")
|
||||||
|
const base64Str = btoa(binStr)
|
||||||
|
return base64Str.replace(/\//g, "+").substr(0, 40)
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,9 @@ class Web {
|
|||||||
Logout() {
|
Logout() {
|
||||||
storage.removeItem('token')
|
storage.removeItem('token')
|
||||||
}
|
}
|
||||||
|
GetToken() {
|
||||||
|
return storage.getItem('token')
|
||||||
|
}
|
||||||
ServerInfo() {
|
ServerInfo() {
|
||||||
return this.makeCall('ServerInfo')
|
return this.makeCall('ServerInfo')
|
||||||
}
|
}
|
||||||
@ -99,12 +102,6 @@ class Web {
|
|||||||
RemoveObject(args) {
|
RemoveObject(args) {
|
||||||
return this.makeCall('RemoveObject', args)
|
return this.makeCall('RemoveObject', args)
|
||||||
}
|
}
|
||||||
GetAuth() {
|
|
||||||
return this.makeCall('GetAuth')
|
|
||||||
}
|
|
||||||
GenerateAuth() {
|
|
||||||
return this.makeCall('GenerateAuth')
|
|
||||||
}
|
|
||||||
SetAuth(args) {
|
SetAuth(args) {
|
||||||
return this.makeCall('SetAuth', args)
|
return this.makeCall('SetAuth', args)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
@ -190,8 +190,8 @@
|
|||||||
----------------------------*/
|
----------------------------*/
|
||||||
.toggle-password {
|
.toggle-password {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 30px;
|
bottom: 0 ;
|
||||||
right: 35px;
|
right: 0;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border: 1px solid #eee;
|
border: 1px solid #eee;
|
||||||
@ -206,6 +206,10 @@
|
|||||||
background: #eee;
|
background: #eee;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.has-toggle-password {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
//--------------------------
|
//--------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@
|
|||||||
"humanize": "0.0.9",
|
"humanize": "0.0.9",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
|
"jwt-decode": "^2.2.0",
|
||||||
"local-storage-fallback": "^4.0.2",
|
"local-storage-fallback": "^4.0.2",
|
||||||
"material-design-iconic-font": "^2.2.0",
|
"material-design-iconic-font": "^2.2.0",
|
||||||
"mime-db": "^1.25.0",
|
"mime-db": "^1.25.0",
|
||||||
|
File diff suppressed because one or more lines are too long
@ -4907,6 +4907,11 @@ jsprim@^1.2.2:
|
|||||||
json-schema "0.2.3"
|
json-schema "0.2.3"
|
||||||
verror "1.10.0"
|
verror "1.10.0"
|
||||||
|
|
||||||
|
jwt-decode@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79"
|
||||||
|
integrity sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=
|
||||||
|
|
||||||
keycode@^2.1.2:
|
keycode@^2.1.2:
|
||||||
version "2.1.9"
|
version "2.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa"
|
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa"
|
||||||
|
45
cmd/iam.go
45
cmd/iam.go
@ -491,6 +491,51 @@ func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetUserSecretKey - sets user secret key
|
||||||
|
func (sys *IAMSys) SetUserSecretKey(accessKey string, secretKey string) error {
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil {
|
||||||
|
return errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.Lock()
|
||||||
|
defer sys.Unlock()
|
||||||
|
|
||||||
|
cred, ok := sys.iamUsersMap[accessKey]
|
||||||
|
if !ok {
|
||||||
|
return errNoSuchUser
|
||||||
|
}
|
||||||
|
|
||||||
|
uinfo := madmin.UserInfo{
|
||||||
|
SecretKey: secretKey,
|
||||||
|
Status: madmin.AccountStatus(cred.Status),
|
||||||
|
}
|
||||||
|
|
||||||
|
configFile := pathJoin(iamConfigUsersPrefix, accessKey, iamIdentityFile)
|
||||||
|
data, err := json.Marshal(uinfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalEtcdClient != nil {
|
||||||
|
err = saveConfigEtcd(context.Background(), globalEtcdClient, configFile, data)
|
||||||
|
} else {
|
||||||
|
err = saveConfig(context.Background(), objectAPI, configFile, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.iamUsersMap[accessKey] = auth.Credentials{
|
||||||
|
AccessKey: accessKey,
|
||||||
|
SecretKey: secretKey,
|
||||||
|
Status: string(uinfo.Status),
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetUser - get user credentials
|
// GetUser - get user credentials
|
||||||
func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
|
func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
|
@ -47,6 +47,7 @@ var (
|
|||||||
errChangeCredNotAllowed = errors.New("Changing access key and secret key not allowed")
|
errChangeCredNotAllowed = errors.New("Changing access key and secret key not allowed")
|
||||||
errAuthentication = errors.New("Authentication failed, check your access credentials")
|
errAuthentication = errors.New("Authentication failed, check your access credentials")
|
||||||
errNoAuthToken = errors.New("JWT token missing")
|
errNoAuthToken = errors.New("JWT token missing")
|
||||||
|
errIncorrectCreds = errors.New("Current access key or secret key is incorrect")
|
||||||
)
|
)
|
||||||
|
|
||||||
func authenticateJWTUsers(accessKey, secretKey string, expiry time.Duration) (string, error) {
|
func authenticateJWTUsers(accessKey, secretKey string, expiry time.Duration) (string, error) {
|
||||||
|
@ -69,6 +69,7 @@ type ServerInfoRep struct {
|
|||||||
MinioPlatform string
|
MinioPlatform string
|
||||||
MinioRuntime string
|
MinioRuntime string
|
||||||
MinioGlobalInfo map[string]interface{}
|
MinioGlobalInfo map[string]interface{}
|
||||||
|
MinioUserInfo map[string]interface{}
|
||||||
UIVersion string `json:"uiVersion"`
|
UIVersion string `json:"uiVersion"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,17 +98,17 @@ func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, rep
|
|||||||
|
|
||||||
reply.MinioVersion = Version
|
reply.MinioVersion = Version
|
||||||
reply.MinioGlobalInfo = getGlobalInfo()
|
reply.MinioGlobalInfo = getGlobalInfo()
|
||||||
// If ENV creds are not set and incoming user is not owner
|
|
||||||
// disable changing credentials.
|
// if etcd is set, disallow changing credentials through UI for owner
|
||||||
v, ok := reply.MinioGlobalInfo["isEnvCreds"].(bool)
|
|
||||||
if ok && !v {
|
|
||||||
reply.MinioGlobalInfo["isEnvCreds"] = !owner
|
|
||||||
}
|
|
||||||
// if etcd is set disallow changing credentials through UI
|
|
||||||
if globalEtcdClient != nil {
|
if globalEtcdClient != nil {
|
||||||
reply.MinioGlobalInfo["isEnvCreds"] = true
|
reply.MinioGlobalInfo["isEnvCreds"] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the user is IAM user
|
||||||
|
reply.MinioUserInfo = map[string]interface{}{
|
||||||
|
"isIAMUser": !owner,
|
||||||
|
}
|
||||||
|
|
||||||
reply.MinioMemory = mem
|
reply.MinioMemory = mem
|
||||||
reply.MinioPlatform = platform
|
reply.MinioPlatform = platform
|
||||||
reply.MinioRuntime = goruntime
|
reply.MinioRuntime = goruntime
|
||||||
@ -768,8 +769,10 @@ func (web webAPIHandlers) GenerateAuth(r *http.Request, args *WebGenericArgs, re
|
|||||||
|
|
||||||
// SetAuthArgs - argument for SetAuth
|
// SetAuthArgs - argument for SetAuth
|
||||||
type SetAuthArgs struct {
|
type SetAuthArgs struct {
|
||||||
AccessKey string `json:"accessKey"`
|
CurrentAccessKey string `json:"currentAccessKey"`
|
||||||
SecretKey string `json:"secretKey"`
|
CurrentSecretKey string `json:"currentSecretKey"`
|
||||||
|
NewAccessKey string `json:"newAccessKey"`
|
||||||
|
NewSecretKey string `json:"newSecretKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAuthReply - reply for SetAuth
|
// SetAuthReply - reply for SetAuth
|
||||||
@ -781,17 +784,28 @@ type SetAuthReply struct {
|
|||||||
|
|
||||||
// SetAuth - Set accessKey and secretKey credentials.
|
// SetAuth - Set accessKey and secretKey credentials.
|
||||||
func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *SetAuthReply) error {
|
func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *SetAuthReply) error {
|
||||||
_, owner, authErr := webRequestAuthenticate(r)
|
claims, owner, authErr := webRequestAuthenticate(r)
|
||||||
if authErr != nil {
|
if authErr != nil {
|
||||||
return toJSONError(authErr)
|
return toJSONError(authErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If creds are set through ENV disallow changing credentials.
|
// When WORM is enabled, disallow changing credenatials for owner and user
|
||||||
if globalIsEnvCreds || globalWORMEnabled || !owner || globalEtcdClient != nil {
|
if globalWORMEnabled {
|
||||||
return toJSONError(errChangeCredNotAllowed)
|
return toJSONError(errChangeCredNotAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
creds, err := auth.CreateCredentials(args.AccessKey, args.SecretKey)
|
if owner {
|
||||||
|
if globalIsEnvCreds || globalEtcdClient != nil {
|
||||||
|
return toJSONError(errChangeCredNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get Current creds and verify
|
||||||
|
prevCred := globalServerConfig.GetCredential()
|
||||||
|
if prevCred.AccessKey != args.CurrentAccessKey || prevCred.SecretKey != args.CurrentSecretKey {
|
||||||
|
return errIncorrectCreds
|
||||||
|
}
|
||||||
|
|
||||||
|
creds, err := auth.CreateCredentials(args.NewAccessKey, args.NewSecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toJSONError(err)
|
return toJSONError(err)
|
||||||
}
|
}
|
||||||
@ -801,7 +815,7 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se
|
|||||||
defer globalServerConfigMu.Unlock()
|
defer globalServerConfigMu.Unlock()
|
||||||
|
|
||||||
// Update credentials in memory
|
// Update credentials in memory
|
||||||
prevCred := globalServerConfig.SetCredential(creds)
|
prevCred = globalServerConfig.SetCredential(creds)
|
||||||
|
|
||||||
// Persist updated credentials.
|
// Persist updated credentials.
|
||||||
if err = saveServerConfig(context.Background(), newObjectLayerFn(), globalServerConfig); err != nil {
|
if err = saveServerConfig(context.Background(), newObjectLayerFn(), globalServerConfig); err != nil {
|
||||||
@ -811,38 +825,39 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se
|
|||||||
return toJSONError(err)
|
return toJSONError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Token, err = authenticateWeb(creds.AccessKey, creds.SecretKey)
|
reply.Token, err = authenticateWeb(args.NewAccessKey, args.NewSecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toJSONError(err)
|
return toJSONError(err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// for IAM users, access key cannot be updated
|
||||||
|
// claims.Subject is used instead of accesskey from args
|
||||||
|
prevCred, ok := globalIAMSys.GetUser(claims.Subject)
|
||||||
|
if !ok {
|
||||||
|
return errInvalidAccessKeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw error when wrong secret key is provided
|
||||||
|
if prevCred.SecretKey != args.CurrentSecretKey {
|
||||||
|
return errIncorrectCreds
|
||||||
|
}
|
||||||
|
|
||||||
|
err := globalIAMSys.SetUserSecretKey(claims.Subject, args.NewSecretKey)
|
||||||
|
if err != nil {
|
||||||
|
return toJSONError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.Token, err = authenticateWeb(claims.Subject, args.NewSecretKey)
|
||||||
|
if err != nil {
|
||||||
|
return toJSONError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reply.UIVersion = browser.UIVersion
|
reply.UIVersion = browser.UIVersion
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthReply - Reply current credentials.
|
|
||||||
type GetAuthReply struct {
|
|
||||||
AccessKey string `json:"accessKey"`
|
|
||||||
SecretKey string `json:"secretKey"`
|
|
||||||
UIVersion string `json:"uiVersion"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAuth - return accessKey and secretKey credentials.
|
|
||||||
func (web *webAPIHandlers) GetAuth(r *http.Request, args *WebGenericArgs, reply *GetAuthReply) error {
|
|
||||||
_, owner, authErr := webRequestAuthenticate(r)
|
|
||||||
if authErr != nil {
|
|
||||||
return toJSONError(authErr)
|
|
||||||
}
|
|
||||||
if !owner {
|
|
||||||
return toJSONError(errAccessDenied)
|
|
||||||
}
|
|
||||||
creds := globalServerConfig.GetCredential()
|
|
||||||
reply.AccessKey = creds.AccessKey
|
|
||||||
reply.SecretKey = creds.SecretKey
|
|
||||||
reply.UIVersion = browser.UIVersion
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLTokenReply contains the reply for CreateURLToken.
|
// URLTokenReply contains the reply for CreateURLToken.
|
||||||
type URLTokenReply struct {
|
type URLTokenReply struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
|
@ -701,18 +701,20 @@ func testSetAuthWebHandler(obj ObjectLayer, instanceType string, t TestErrHandle
|
|||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
username string
|
currentAccessKey string
|
||||||
password string
|
currentSecretKey string
|
||||||
|
newAccessKey string
|
||||||
|
newSecretKey string
|
||||||
success bool
|
success bool
|
||||||
}{
|
}{
|
||||||
{"", "", false},
|
{"", "", "", "", false},
|
||||||
{"1", "1", false},
|
{"1", "1", "1", "1", false},
|
||||||
{"azerty", "foooooooooooooo", true},
|
{credentials.AccessKey, credentials.SecretKey, "azerty", "foooooooooooooo", true},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterating over the test cases, calling the function under test and asserting the response.
|
// Iterating over the test cases, calling the function under test and asserting the response.
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
setAuthRequest := SetAuthArgs{AccessKey: testCase.username, SecretKey: testCase.password}
|
setAuthRequest := SetAuthArgs{CurrentAccessKey: testCase.currentAccessKey, CurrentSecretKey: testCase.currentSecretKey, NewAccessKey: testCase.newAccessKey, NewSecretKey: testCase.newSecretKey}
|
||||||
setAuthReply := &SetAuthReply{}
|
setAuthReply := &SetAuthReply{}
|
||||||
req, err := newTestWebRPCRequest("Web.SetAuth", authorization, setAuthRequest)
|
req, err := newTestWebRPCRequest("Web.SetAuth", authorization, setAuthRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -735,42 +737,6 @@ func testSetAuthWebHandler(obj ObjectLayer, instanceType string, t TestErrHandle
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper for calling Get Auth Handler
|
|
||||||
func TestWebHandlerGetAuth(t *testing.T) {
|
|
||||||
ExecObjectLayerTest(t, testGetAuthWebHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// testGetAuthWebHandler - Test GetAuth web handler
|
|
||||||
func testGetAuthWebHandler(obj ObjectLayer, instanceType string, t TestErrHandler) {
|
|
||||||
// Register the API end points with XL/FS object layer.
|
|
||||||
apiRouter := initTestWebRPCEndPoint(obj)
|
|
||||||
credentials := globalServerConfig.GetCredential()
|
|
||||||
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
authorization, err := getWebRPCToken(apiRouter, credentials.AccessKey, credentials.SecretKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Cannot authenticate")
|
|
||||||
}
|
|
||||||
|
|
||||||
getAuthRequest := WebGenericArgs{}
|
|
||||||
getAuthReply := &GetAuthReply{}
|
|
||||||
req, err := newTestWebRPCRequest("Web.GetAuth", authorization, getAuthRequest)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create HTTP request: <ERROR> %v", err)
|
|
||||||
}
|
|
||||||
apiRouter.ServeHTTP(rec, req)
|
|
||||||
if rec.Code != http.StatusOK {
|
|
||||||
t.Fatalf("Expected the response status to be 200, but instead found `%d`", rec.Code)
|
|
||||||
}
|
|
||||||
err = getTestWebRPCResponse(rec, &getAuthReply)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed, %v", err)
|
|
||||||
}
|
|
||||||
if getAuthReply.AccessKey != credentials.AccessKey || getAuthReply.SecretKey != credentials.SecretKey {
|
|
||||||
t.Fatalf("Failed to get correct auth keys")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWebCreateURLToken(t *testing.T) {
|
func TestWebCreateURLToken(t *testing.T) {
|
||||||
ExecObjectLayerTest(t, testCreateURLToken)
|
ExecObjectLayerTest(t, testCreateURLToken)
|
||||||
}
|
}
|
||||||
@ -1518,7 +1484,7 @@ func TestWebCheckAuthorization(t *testing.T) {
|
|||||||
webRPCs := []string{
|
webRPCs := []string{
|
||||||
"ServerInfo", "StorageInfo", "MakeBucket",
|
"ServerInfo", "StorageInfo", "MakeBucket",
|
||||||
"ListBuckets", "ListObjects", "RemoveObject",
|
"ListBuckets", "ListObjects", "RemoveObject",
|
||||||
"GenerateAuth", "SetAuth", "GetAuth",
|
"GenerateAuth", "SetAuth",
|
||||||
"GetBucketPolicy", "SetBucketPolicy", "ListAllBucketPolicies",
|
"GetBucketPolicy", "SetBucketPolicy", "ListAllBucketPolicies",
|
||||||
"PresignedGet",
|
"PresignedGet",
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user