Compare commits

..

112 Commits

Author SHA1 Message Date
Alex
ee974a5961 Release v2.0.0 (#3526)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2025-05-07 15:37:20 -06:00
Daniel Valdivia
40c4ce76c5 License Comparisson updates (#3532)
* License Comparisson updates

Signed-off-by: Daniel Valdivia <hola@danielvaldivia.com>

* Prettier

---------

Signed-off-by: Daniel Valdivia <hola@danielvaldivia.com>
2025-05-07 13:40:42 -07:00
Alex
28b080122a Fixed vulnerabilities in go packages (#3531)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2025-05-05 13:50:36 -07:00
dependabot[bot]
6df2db82af Bump formidable from 3.5.2 to 3.5.4 in /web-app (#3530) 2025-05-05 11:05:51 -06:00
dependabot[bot]
ff2b72f869 Bump http-proxy-middleware from 2.0.7 to 2.0.9 in /web-app (#3529)
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.7 to 2.0.9.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.9/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.7...v2.0.9)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-version: 2.0.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-29 15:49:25 +02:00
Alex
f5ad4defc9 Updated Packages & versions (#3525)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2025-04-03 01:29:17 -06:00
Cesar N.
6499469487 Update jwt package (#3522) 2025-03-21 17:33:58 -06:00
Ramon de Klein
71b1b708b7 Clean-up after PR 3509 (#3517) 2025-03-21 13:43:10 -06:00
Alex
63c6d8952b Implemented AGPL MinIO Object Browser simplified Console (#3509)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2025-03-11 02:30:53 -07:00
Quentin JOLY
33a7fbb205 Fix regex pattern in webhook management (#3512) 2025-03-06 17:41:04 -06:00
Alex
36d0e78834 Updated Project dependencies (#3508)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2025-02-21 14:20:45 -08:00
Allan Roger Reid
9e0416f1ab Allow console to recognize s3.Delete* (#3507) 2025-02-21 12:04:08 -06:00
Cesar N.
f4a08fc0af Release v1.7.6 (#3504) 2025-02-10 21:41:20 -06:00
Burkov Egor
115de8ee25 fix: possible nil deref in admin_info (#3500) 2025-02-10 18:04:21 -08:00
Harshavardhana
27cb17dbfd make sure go toolchain is correctly set (#3502)
and update deps
2025-02-10 12:41:51 -08:00
Chai-Shi
31a5d640eb Support login request with leading or trailing whitespace from payload (#3496)
* trim whitespaces for login request
* simplify trimspace calls
2025-01-23 12:23:46 +01:00
dependabot[bot]
b45e11ce7f Bump nanoid from 3.3.7 to 3.3.8 in /web-app (#3486)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Prakash Senthil Vel <23444145+prakashsvmx@users.noreply.github.com>
2025-01-13 21:50:17 -06:00
Prakash Senthil Vel
4388ecc4eb Fix sanitize file path while drag and drop (#3492) 2025-01-13 21:46:47 -06:00
Harshavardhana
656d8075b2 update all deps to new changes (#3489) 2024-12-30 10:29:10 -08:00
Harshavardhana
8b209a202f update deps as regular cadence (#3488) 2024-12-18 14:37:19 -08:00
brandongevat
2e66a13f48 Fix typo in User DN Search Filter example (#3485) 2024-12-11 08:39:13 +05:30
Alex
d54ed26c71 Release v1.7.5 (#3484)
* Release v1.7.5

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

* Update CHANGELOG.md

Co-authored-by: Victor Bayas <victorsbayas@gmail.com>

---------

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Cesar N. <11819101+cesnietor@users.noreply.github.com>
Co-authored-by: Victor Bayas <victorsbayas@gmail.com>
2024-12-06 19:42:16 -05:00
Cesar Celis
5cf02ffae9 Allow spaces in policy names (#3482) 2024-12-05 09:55:57 -08:00
Harshavardhana
6afd0b16dc avoid leaks during zip download and multi-object downloads (#3481) 2024-12-02 18:17:32 -08:00
dependabot[bot]
0b8af385f1 Bump cross-spawn from 7.0.3 to 7.0.6 in /web-app (#3474)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 14:09:44 -06:00
Alex
9a3063b7da Release v1.7.4 (#3472)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-11-26 14:47:40 -06:00
Anis Eleuch
71541029e7 openid: Gather the refresh token if found. (#3478)
When the openid login page sends the refresh token in 
the login redirection url, the code will get it. This is
helpful to support refresh openid user claims routine.

Co-authored-by: Anis Eleuch <anis@min.io>
2024-11-26 10:28:15 -08:00
Ramon de Klein
076b803a5c Upgrade to MDS v1.0.4 (#3473)
upgrade to MDS v1.0.4
2024-11-19 22:09:37 +01:00
Alex
97ef82f831 Fixed UI Tests (#3471)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2024-11-11 16:49:47 -08:00
Alex
2ca484c691 Deprecated Lifecycle and Tiering UI (#3470)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-11-11 17:36:14 -06:00
Alex
4e5dcf0fc3 Deprecated Site Replication in UI (#3469)
* Deprecated Sire Replication in UI

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

* fix-workflow-issue

---------

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2024-11-11 12:26:19 -08:00
Alex
18e50975d4 Removed Tools support (#3467)
- Removed Menu links for Support tools
- Removed support in UI for registering cluster
- Removed Subnet support
- Removed Websockets for tools support
- Removed Support endpoint
- Removed Subnet support endpoints

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-11-05 12:56:53 -08:00
dependabot[bot]
d425af3c85 Bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 (#3466)
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.0...v4.5.1)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-05 10:56:10 -06:00
Ramon de Klein
fce84d1de0 Release v1.7.3 (#3464) 2024-10-30 10:06:40 -07:00
Cesar N.
dc19984f23 Fix Set Policy on Groups with spaces (#3453) 2024-10-28 20:28:07 +01:00
tnfAngel
8c1ecae68a Fix join slack button (#3460)
* Fix join slack button

* run prettier on changes
2024-10-28 20:21:54 +01:00
dependabot[bot]
862d692444 Bump http-proxy-middleware from 2.0.6 to 2.0.7 in /web-app (#3461)
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.7/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.6...v2.0.7)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 20:20:18 +01:00
Prakash Senthil Vel
854a0c16b3 error handling in bucket creation ui (#3455) 2024-10-28 11:16:01 -07:00
Ramon de Klein
3d74e9762c Disable download public verification key (#3462)
update minio/pkg (to disable downloading public key)
2024-10-28 09:36:10 -07:00
Ramon de Klein
3d8b98f563 Show epoch expiry as no-expiry (#3459) 2024-10-26 09:49:51 -06:00
Cesar N.
06af416642 Release v1.7.2 (#3452) 2024-10-16 09:00:37 -07:00
Javier Adriel
01920841d2 Remove live logs support from UI (#3451) 2024-10-09 15:08:48 -07:00
Javier Adriel
78aceb2b53 Return full value of client in trace message (#3450) 2024-10-09 14:54:17 -07:00
Cesar N.
dce9bbd046 Fix vulnerabilities in npm packages (#3449) 2024-10-08 13:25:52 -07:00
Prakash Senthil Vel
9f4573ade8 enterprise license page update (#3443) 2024-09-30 14:20:49 -07:00
Shireesh Anjal
24af63da42 Fix incorrect logic in serverHealthInfo (#3442)
It was being assumed that whole response has been received as soon as
info.Version is non-empty. This is wrong as the very first response by
minio contains the version. So removed the unnecessary for loop that had
this check to ensure that the whole report is received properly.
2024-09-26 21:53:34 -07:00
Ramon de Klein
52c77fd388 Release v1.7.1 (#3441) 2024-09-20 10:18:54 -07:00
Mark Theunissen
19e6cc87c2 Return network error when logging in and the network connection fails (#3432) 2024-09-20 09:55:47 -06:00
dependabot[bot]
9bfed73b03 Bump webpack from 5.91.0 to 5.94.0 in /web-app (#3431)
Bumps [webpack](https://github.com/webpack/webpack) from 5.91.0 to 5.94.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.91.0...v5.94.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ramon de Klein <mail@ramondeklein.nl>
2024-09-20 12:21:26 +02:00
dependabot[bot]
095241517b Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows (#3433)
* Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.1.7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v3...v4.1.7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Use latest v4 instead of a specific version

* upgrade to `actions/upload-artifact@v4`

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Cesar N. <11819101+cesnietor@users.noreply.github.com>
Co-authored-by: Ramon de Klein <mail@ramondeklein.nl>
2024-09-20 12:21:08 +02:00
dependabot[bot]
339885b2a0 Bump express from 4.19.2 to 4.21.0 in /web-app (#3438)
Bumps [express](https://github.com/expressjs/express) from 4.19.2 to 4.21.0.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ramon de Klein <mail@ramondeklein.nl>
2024-09-20 12:20:27 +02:00
Ramon de Klein
6cfc985337 Add (optional) debug logging for console requests (#3440)
* Add (optional) debug logging for console requests
* Check vulnerabilities with 1.22.7
2024-09-20 10:21:28 +02:00
William Entriken
1c47685aea Use setup-node@v4's new node-version-file directly (#3435) 2024-09-16 11:48:49 -06:00
dependabot[bot]
efa74a5f50 Bump micromatch from 4.0.7 to 4.0.8 in /web-app (#3437)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.7 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 11:47:29 -06:00
Victor Bayas
4c432cd356 Use Knip for detecting dead code (#3426) 2024-08-21 10:35:02 -06:00
Harshavardhana
0b07cb3885 update deps to latest releases (#3425) 2024-08-17 14:52:48 -07:00
Ramon de Klein
8d13be5e87 Fix content type issue (#3424)
Fix content type issue
2024-08-13 17:49:06 -06:00
Victor Bayas
3c34602f9e Fix Web App workflows (#3423)
* Fix Web App workflows

* Update Yarn to 4.4.0
2024-08-08 12:56:03 -07:00
Cesar N.
adfc96074f Release v1.7.0 (#3418) 2024-08-02 15:20:47 -07:00
jinapurapu
7cc7b874d1 Fix MinIO videos link (#3417) 2024-08-02 13:48:54 -07:00
Ramon de Klein
4d12a5061d Remove obsolete KES functionality (#3414) 2024-08-02 11:50:33 -06:00
Prakash Senthil Vel
b274add4da fix nested directory object display (#3415) 2024-08-02 10:06:10 -07:00
Alex
fd51c9dc4c Updated UI dependencies (#3411)
- Fixed an issue with servers list not closing correctly in certain circumstances
- Updated internal UI dependencies

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-07-22 10:44:46 -06:00
Alex
51f8794aa6 Release v1.6.3 (#3408) 2024-07-12 16:07:49 -06:00
Alex
3db998f9c4 Release v1.6.2 (#3407)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-07-12 12:43:59 -07:00
Alex
0a2a7087a7 Updated UI Project Dependencies (#3406)
Updated Project Dependencies

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-07-12 13:08:30 -06:00
Alex
8f0eb11ced Updated License page with new design (#3405) 2024-07-12 10:18:17 -06:00
Anis Eleuch
e3e3599095 ws: Calculate the client IP from the WS request headers as well (#3403)
Currently, the websocket code adds an IP to X-Forwarded-For where the IP is
calculated from the TCP connection established to Console, however the established
TCP connection can be coming from the load balancer as well, hence the
necesssity to evaluate the client IP based on X-Forwarded-For or
X-Real-IP headers

Look for client IPs in the websocket request connection.

Co-authored-by: Anis Eleuch <anis@min.io>
2024-07-10 08:04:50 -07:00
dependabot[bot]
aa74e31453 Bump google.golang.org/grpc from 1.64.0 to 1.64.1 (#3404)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.64.0 to 1.64.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.64.0...v1.64.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-10 09:15:51 +02:00
jiuker
e1f6e729fd format to loginfo (#3400) 2024-07-08 09:08:27 -06:00
Ramon de Klein
13d83a6d1c User session fixes (#3397) 2024-07-04 18:22:36 -07:00
Ramon de Klein
6075387654 Upgrade Go to v1.22.5 and upgrade Github actions (#3398) 2024-07-04 07:28:24 -07:00
Victor Bayas
69fad3f55f Improvements to Drives list UI (#3395) 2024-07-01 17:27:33 -06:00
Alex
e3864b62a4 Release v1.6.1 (#3393) 2024-06-25 10:26:58 -07:00
Harshavardhana
22176f4e0f fix: objectManager implementation avoid racy goroutines (#3392)
fixes #3391
2024-06-25 08:50:31 -07:00
dependabot[bot]
a89d7ec0ea Bump ws from 7.5.9 to 7.5.10 in /web-app (#3390) 2024-06-18 15:22:19 -07:00
Ramon de Klein
8262049e20 Fix showing object-name in legal hold dialog (#3389) 2024-06-18 15:20:03 -07:00
Alex
c61e1e0a2a Added debounce to Share file fields (#3388)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-06-17 16:02:13 -06:00
Alex
b376cf6c65 Improvements to Share Link component behavior (#3387)
Improvements to Share Link component to avoid multiple re-renders during common actions

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-06-17 11:40:27 -06:00
Cesar N
16bae25ce6 Release v1.6.0 (#3386) 2024-06-13 14:26:03 -07:00
Alex
57ba17a12e Changed License plans validation to not allow AGPL as a registered plan (#3385)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-06-13 14:36:08 -06:00
Ramon de Klein
f4d98a4910 Fix share link issue (#3382) (#3384) 2024-06-13 13:30:33 -06:00
Cesar N
fa32d78ff1 Add Tiers improvements for Bucket Lifecycle management (#3380) 2024-06-13 13:17:49 -06:00
jinapurapu
56f22a4479 Fix storageClass on EditLifecycleModal (#3379) 2024-06-13 11:06:30 -07:00
Alex
7b88d3a1bc Updated UI package dependencies (#3381)
Updated package dependencies

- Updated react-pdf to fix security vulnerability
- Removed ignored security alerts

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-06-13 11:23:50 -06:00
Prakash Senthil Vel
c8a39f9544 update logo as per mineos plans (#3383) 2024-06-13 08:01:25 -07:00
Cesar N
e77d1be53e Release v1.5.0 (#3373)
update assets with latest fix

Co-authored-by: cesnietor <cesar.nieto@min.io>
2024-06-07 19:00:37 -07:00
jinapurapu
6765bd0624 Fix remove tier error handling (#3375) 2024-06-07 16:39:18 -06:00
jinapurapu
5f7b563a01 Add Remove Tier feature (#3371) 2024-06-07 10:01:10 -07:00
jinapurapu
3885875149 Add ILM rule tags generated by mc to getBucketLifecycle response (#3369) 2024-06-06 15:22:18 -07:00
Cesar N
cf05d5026f Use sync.Map for websocket cancelContext map (#3368) 2024-06-06 11:38:55 -07:00
Harshavardhana
3e83a30739 go1.22.4 upgrade for vulncheck (#3370) 2024-06-06 10:23:43 -07:00
Ramon de Klein
49c5f5a8f0 Use automatic URI encoding (#3352) 2024-06-05 14:48:27 -07:00
Ramon de Klein
72939e0cd7 Provide workaround for circular dependency (#3361) 2024-06-05 11:36:58 -07:00
Javier Adriel
271560894a Fix mapping over null reference for KMS endpoints (#3367) 2024-06-03 14:03:35 -07:00
Javier Adriel
6a591c1bcd Fix wrong comparisons (#3365) 2024-05-29 15:50:23 -07:00
Victor Bayas
3fdcfef1b4 Set Yarn checksumBehavior to reset (#3366)
* Set Yarn checksumBehavior to reset

* Update .gitignore according to Yarn docs
See https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored

* Update yarn.lock

---------

Co-authored-by: Harshavardhana <harsha@minio.io>
2024-05-29 15:50:12 -07:00
Ramon de Klein
7b8cfa2062 Attempt to fix resolution issues (#3364) 2024-05-29 15:12:02 -07:00
Harshavardhana
cc5921fd74 chore: update all deps (#3363) 2024-05-28 22:19:47 -06:00
Daniel Valdivia
d027b7f759 Release v1.4.1 (#3360)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2024-05-24 12:35:06 -07:00
Aditya Manthramurthy
f1524b0120 Bump up minio/pkg to v3 (#3349) 2024-05-24 10:44:55 -07:00
Kaan Kabalak
9985892751 Update default issue message note (#3358) 2024-05-22 23:16:05 -07:00
Ramon de Klein
cfd60bdd91 Upgrade to Yarn 4 and fix vulnerability check (#3353) 2024-05-22 18:19:32 -07:00
Shireesh Anjal
779f2a86e5 Fix empty version in health report download (#3341) 2024-05-17 14:42:39 -06:00
Pedro Juarez
f47c4445bd Return header with error idp logout (#3346) 2024-05-12 09:30:46 -07:00
Kaan Kabalak
1aeb4cc3d5 Fix SUBNET capitalization in issue template (#3345) 2024-05-10 17:47:38 -06:00
Alex
9e0a0205cc Updated react-pdf dependency (#3342) 2024-05-10 09:11:09 -06:00
Harshavardhana
1058efb17a Remove all unnecessary Dockerfiles from repo (#3340) 2024-05-08 21:53:47 -06:00
Anis Eleuch
d0f744ebef svc: Assume access key creation permission to be available by default (#3306)
Allow SVC creation when CreateServiceAccount is denied with a condition

Adding this policy will make the user not able to create a service account anymore:

```
    {
      "Effect": "Deny",
      "Action": [
              "admin:CreateServiceAccount"
      ],
      "Condition": {
              "NumericGreaterThanIfExists": {"svc:DurationSeconds": "1500"}
      }
    },

```

The reason is that policy.IsAllowedActions() is called with conditions from the user login.

Assume svc account creation to be possible for now until we come up with a better fix

Co-authored-by: Anis Eleuch <anis@min.io>
Co-authored-by: Prakash Senthil Vel <23444145+prakashsvmx@users.noreply.github.com>
2024-05-08 09:47:57 -07:00
Cesar N
a8c043cb16 Use redirect URL in share link if env variable set (#3334) 2024-05-08 09:46:41 -07:00
dependabot[bot]
978e02b5dc Bump react-pdf from 7.7.1 to 7.7.3 in /web-app (#3337)
Bumps [react-pdf](https://github.com/wojtekmaj/react-pdf/tree/HEAD/packages/react-pdf) from 7.7.1 to 7.7.3.
- [Release notes](https://github.com/wojtekmaj/react-pdf/releases)
- [Commits](https://github.com/wojtekmaj/react-pdf/commits/v7.7.3/packages/react-pdf)

---
updated-dependencies:
- dependency-name: react-pdf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 09:46:13 -07:00
dependabot[bot]
b39dbfff96 Bump pdfjs-dist from 3.11.174 to 4.2.67 in /web-app (#3335)
Bumps [pdfjs-dist](https://github.com/mozilla/pdfjs-dist) from 3.11.174 to 4.2.67.
- [Commits](https://github.com/mozilla/pdfjs-dist/commits)

---
updated-dependencies:
- dependency-name: pdfjs-dist
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 09:46:03 -07:00
1315 changed files with 164006 additions and 300460 deletions

View File

@@ -1,7 +0,0 @@
node_modules/
dist/
target/
console
!console/
web-app/node_modules/
.git/

View File

@@ -8,7 +8,8 @@ assignees: ''
---
## NOTE
If this case is urgent, please subscribe to [Subnet](https://min.io/pricing) so that our 24/7 support team may help you faster.
Please subscribe to our [paid subscription plans](https://min.io/pricing) for 24x7 support from our Engineering team.
<!--- Provide a general summary of the issue in the title above -->

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,6 @@ on:
pull_request:
branches:
- master
push:
branches:
- master
permissions:
contents: read # to fetch code (actions/checkout)
@@ -18,11 +15,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: 1.21.9
go-version: 1.23.8
check-latest: true
- name: Get official govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
@@ -36,18 +33,21 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ 1.21.8 ]
go-version: [ 1.23.x ]
os: [ ubuntu-latest ]
steps:
- name: Check out code
uses: actions/checkout@v3
- uses: actions/setup-node@v3
uses: actions/checkout@v4
- name: Read .nvmrc
id: node_version
run: echo "$(cat .nvmrc)" && echo "NVMRC=$(cat .nvmrc)" >> $GITHUB_ENV
- name: Enable Corepack
run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NVMRC }}
cache: "yarn"
cache-dependency-path: web-app/yarn.lock
- name: Checks for known security issues with the installed packages
working-directory: ./web-app
continue-on-error: false
run: |
yarn audit --groups dependencies
yarn npm audit --recursive --environment production --no-deprecations

3
.gitignore vendored
View File

@@ -48,3 +48,6 @@ public.crt
*.code-workspace
*~
.eslintcache
# Ignore Bin files
bin/

55
.golangci.bck.yml Normal file
View File

@@ -0,0 +1,55 @@
linters-settings:
misspell:
locale: US
testifylint:
disable:
- go-require
staticcheck:
checks:
[
"all",
"-ST1005",
"-ST1000",
"-SA4000",
"-SA9004",
"-SA1019",
"-SA1008",
"-U1000",
"-ST1016",
]
goheader:
values:
regexp:
copyright-holder: Copyright \(c\) (20\d\d\-20\d\d)|2021|({{year}})
template-path: .license.tmpl
linters:
disable-all: true
enable:
- goimports
- misspell
- govet
- revive
- ineffassign
- gosimple
- gomodguard
- gofmt
- unused
- staticcheck
- unconvert
- gocritic
- gofumpt
- durationcheck
issues:
exclude-use-default: false
exclude:
- should have a package comment
# TODO(y4m4): Remove once all exported ident. have comments!
- comment on exported function
- comment on exported type
- should have comment
- use leading k in Go names
- comment on exported const
exclude-dirs:
- api/operations

View File

@@ -1,49 +1,73 @@
linters-settings:
golint:
min-confidence: 0
misspell:
locale: US
goheader:
values:
regexp:
copyright-holder: Copyright \(c\) (20\d\d\-20\d\d)|2021|({{year}})
template-path: .license.tmpl
version: "2"
linters:
disable-all: true
default: none
enable:
- goimports
- misspell
- govet
- revive
- ineffassign
- gosimple
- durationcheck
- gocritic
- gomodguard
- gofmt
- unused
- govet
- ineffassign
- misspell
- revive
- staticcheck
- unconvert
- gocritic
- unused
settings:
goheader:
values:
regexp:
copyright-holder: Copyright \(c\) (20\d\d\-20\d\d)|2021|({{year}})
template-path: .license.tmpl
misspell:
locale: US
staticcheck:
checks:
- all
- -QF1001
- -QF1008
- -QF1010
- -QF1012
- -SA1008
- -SA1019
- -SA4000
- -SA9004
- -ST1000
- -ST1005
- -ST1016
- -ST1019
- -U1000
testifylint:
disable:
- go-require
exclusions:
generated: lax
rules:
- path: (.+)\.go$
text: should have a package comment
- path: (.+)\.go$
text: comment on exported function
- path: (.+)\.go$
text: comment on exported type
- path: (.+)\.go$
text: should have comment
- path: (.+)\.go$
text: use leading k in Go names
- path: (.+)\.go$
text: comment on exported const
paths:
- api/operations
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- gofumpt
- durationcheck
service:
golangci-lint-version: 1.43.0 # use the fixed version to not introduce new linters unexpectedly
issues:
exclude-use-default: false
exclude:
- should have a package comment
# TODO(y4m4): Remove once all exported ident. have comments!
- comment on exported function
- comment on exported type
- should have comment
- use leading k in Go names
- comment on exported const
run:
skip-dirs:
- pkg/clientgen
- pkg/apis/networking.gke.io
- api/operations
- goimports
exclusions:
generated: lax
paths:
- api/operations
- third_party$
- builtin$
- examples$

View File

@@ -1,7 +1,153 @@
<!-- @format -->
# Changelog
## Release v2.0.0
Community version is going back to be an object browser only.
Bug Fix:
- Fixed Dependencies vulnerabilities
Deprecations:
- Deprecated support of accounts & policies management, this can be managed by using mc admin commands. Please refer to the [MinIO Console User Management page](https://min.io/docs/minio/kubernetes/upstream/administration/identity-access-management/minio-user-management.html#id1) for more information.
- Deprecated support of bucket management, this can be managed by using mc commands. Please refer to the [MinIO Client](https://min.io/docs/minio/linux/reference/minio-mc.html) for more information.
- Deprecated support of configuration management, this can be managed by using mc admin config commands. Please refer to the [MinIO Client](https://min.io/docs/minio/linux/reference/minio-mc.html) for more information.
## Release v1.7.6
Bug Fix:
- Fix null pointer exception in Admin Info
- Ignore leading or trailing spaces in login request
- Fix file path on drag and drop
- Fix typo in User DN Search Filter example
## Release v1.7.5
Bug Fix:
- Fixed leaks during ZIP multiobject downloads
- Allow spaces in Policy names
## Release v1.7.4
Deprecations:
- Deprecated support tools User Interface in favor of mc admin commands. Please refer to the [MinIO SUBNET Registration page](https://min.io/docs/minio/linux/administration/console/subnet-registration.html#subnet) for more information.
- Deprecated Site replication User Interface in favor of mc admin commands. Please refer to the [MinIO Site Replication page](https://min.io/docs/minio/linux/operations/install-deploy-manage/multi-site-replication.html) for more information.
- Deprecated Lifecycle & Tiers User Interface in favor of mc admin commands. Please refer to the [MinIO Tiers page](https://min.io/docs/minio/linux/reference/minio-mc/mc-ilm-tier.html) for more information.
Bug Fix:
- Avoid loading unpkg.com call when login animation is off
## Release v1.7.3
Bug Fix:
- Use a fixed public license verification key
- Show non-expiring access keys as `no-expiry` instead of Jan 1, 1970
- Use "join Slack" button for non-commercial edition instead of "Signup"
- Fix setting policies on groups that have spaces
## Release v1.7.2
Bug Fix:
- Fixed issue in Server Health Info
- Fixed Security vulnerability in dependencies
- Fixed client string in trace message
Additional Changes:
- Remove live logs in Call Home Page
- Update License page
## Release v1.7.1
Bug Fix:
- Fixed issue that could cause a failure when attempting to view deleted files in the object browser
- Return network error when logging in and the network connection fails
Additional Changes:
- Added debug logging for console HTTP request (see [PR #3440](https://github.com/minio/console/pull/3440) for more detailed information)
## Release v1.7.0
Bug Fix:
- Fixed directory listing
- Fix MinIO videos link
Additional Changes:
- Removed deprecated KES functionality
## Release v1.6.3
Additional Changes:
- Updated go.mod version
## Release v1.6.2
Bug Fix:
- Fixed minor user session issues
- Updated project dependencies
Additional Changes:
- Improved Drives List visualization
- Improved WS request logic
- Updated License page with current MinIO plans.
## Release v1.6.1
Bug Fix:
- Fixed objectManager issues under certain conditions
- Fixed Security vulnerability in dependencies
Additional Changes:
- Improved Share Link behavior
## Release v1.6.0
Bug Fix:
- Fixed share link encoding
- Fixed Edit Lifecycle Storage Class
- Added Tiers Improvements for Bucket Lifecycle management
Additional Changes:
- Vulnerability updates
- Update Logo logic
## Release v1.5.0
Features:
- Added remove Tier functionality
Bug Fix:
- Fixed ILM rule tags not being shown
- Fixed race condition Object Browser websocket
- Fixed Encryption page crashing on empty response
- Fixed Replication Delete Marker comparisons
Additional Changes:
- Use automatic URI encoding for APIs
- Vulnerability updates
## Release v1.4.0
Features:

3330
CREDITS

File diff suppressed because it is too large Load Diff

View File

@@ -77,25 +77,6 @@ Still in the MinIO folder, run
make build
```
# Testing on Kubernetes
If you want to test console on kubernetes, you can perform all the steps from `Building with MinIO`, but change `Step 3`
to the following:
```shell
TAG=miniodev/console:dev make docker
```
This will build a docker container image that can be used to test with your local kubernetes environment.
For example, if you are using kind:
```shell
kind load docker-image miniodev/console:dev
```
and then deploy any `Tenant` that uses this image
# LDAP authentication with Console
## Setup

View File

@@ -1,43 +0,0 @@
ARG NODE_VERSION
FROM node:$NODE_VERSION as uilayer
WORKDIR /app
COPY ./web-app/package.json ./
COPY ./web-app/yarn.lock ./
RUN yarn install
COPY ./web-app .
RUN make build-static
USER node
FROM golang:1.19 as golayer
RUN apt-get update -y && apt-get install -y ca-certificates
ADD go.mod /go/src/github.com/minio/console/go.mod
ADD go.sum /go/src/github.com/minio/console/go.sum
WORKDIR /go/src/github.com/minio/console/
# Get dependencies - will also be cached if we won't change mod/sum
RUN go mod download
ADD . /go/src/github.com/minio/console/
WORKDIR /go/src/github.com/minio/console/
ENV CGO_ENABLED=0
COPY --from=uilayer /app/build /go/src/github.com/minio/console/web-app/build
RUN go build --tags=kqueue,operator -ldflags "-w -s" -a -o console ./cmd/console
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
MAINTAINER MinIO Development "dev@min.io"
EXPOSE 9090
COPY --from=golayer /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=golayer /go/src/github.com/minio/console/console .
ENTRYPOINT ["/console"]

View File

@@ -1,14 +0,0 @@
ARG NODE_VERSION
FROM node:$NODE_VERSION as uilayer
WORKDIR /app
COPY ./web-app/package.json ./
COPY ./web-app/yarn.lock ./
RUN yarn install
COPY ./web-app .
RUN yarn install && make build-static
USER node

View File

@@ -1,27 +0,0 @@
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.2 as build
RUN microdnf update --nodocs && microdnf install ca-certificates --nodocs
FROM registry.access.redhat.com/ubi9/ubi-micro:9.2
ARG TAG
LABEL name="MinIO" \
vendor="MinIO Inc <dev@min.io>" \
maintainer="MinIO Inc <dev@min.io>" \
version="${TAG}" \
release="${TAG}" \
summary="A graphical user interface for MinIO" \
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
# On RHEL the certificate bundle is located at:
# - /etc/pki/tls/certs/ca-bundle.crt (RHEL 6)
# - /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem (RHEL 7)
COPY --from=build /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem /etc/pki/ca-trust/extracted/pem/
COPY LICENSE /LICENSE
COPY CREDITS /CREDITS
COPY console /console
EXPOSE 9090
ENTRYPOINT ["/console"]

View File

@@ -57,14 +57,18 @@ clean-swagger:
swagger-console:
@echo "Generating swagger server code from yaml"
@swagger generate server -A console --main-package=management --server-package=api --exclude-main -P models.Principal -f ./swagger.yml -r NOTICE
@echo "Ensure basic install"
@(cd web-app; yarn; cd ..)
@echo "Generating typescript api"
@npx swagger-typescript-api -p ./swagger.yml -o ./web-app/src/api -n consoleApi.ts
@make swagger-typescript-api path="../swagger.yml" output="./src/api" name="consoleApi.ts"
@git restore api/server.go
swagger-typescript-api:
@(cd web-app; yarn swagger-typescript-api -p $(path) -o $(output) -n $(name) --custom-config ../generator.config.js; cd ..)
assets:
@(if [ -f "${NVM_DIR}/nvm.sh" ]; then \. "${NVM_DIR}/nvm.sh" && nvm install && nvm use && npm install -g yarn ; fi &&\
cd web-app; yarn install --prefer-offline; make build-static; yarn prettier --write . --loglevel warn; cd ..)
cd web-app; corepack enable; yarn install --prefer-offline; make build-static; yarn prettier --write . --log-level warn; cd ..)
test-integration:
@(docker stop pgsqlcontainer || true)
@@ -206,21 +210,11 @@ test-permissions-4:
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-4/")
@(docker stop minio)
test-permissions-5:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-5/")
@(docker stop minio)
test-permissions-6:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-6/")
@(docker stop minio)
test-permissions-7:
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 -d --name minio --rm -p 9000:9000 quay.io/minio/minio:latest server /data{1...4})
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-7/")
@(docker stop minio)
test-apply-permissions:
@(env bash $(PWD)/web-app/tests/scripts/initialize-env.sh)
@@ -243,7 +237,7 @@ test-start-docker-minio-w-redirect-url: initialize-docker-network
-e MINIO_SERVER_URL='http://localhost:9000' \
-v /data1 -v /data2 -v /data3 -v /data4 \
-d --network host --name minio --rm\
quay.io/minio/minio:latest server /data{1...4})
quay.io/minio/minio:latest server /data{1...4})
test-start-docker-nginx-w-subpath:
@(docker run \

View File

@@ -25,21 +25,7 @@ A graphical user interface for [MinIO](https://github.com/minio/minio)
<!-- markdown-toc end -->
## Install
MinIO Console is a library that provides a management and browser UI overlay for the MinIO Server.
The standalone binary installation path has been removed.
In case a Console standalone binary is needed, it can be generated by building this package from source as follows:
### Build from source
> You will need a working Go environment. Therefore, please follow [How to install Go](https://golang.org/doc/install).
> Minimum version required is go1.21
```
go install github.com/minio/console/cmd/console@latest
```
## Setup
@@ -205,6 +191,27 @@ export CONSOLE_MINIO_SERVER=https://localhost:9000
You can verify that the apis work by doing the request on `localhost:9090/api/v1/...`
## Debug logging
In some cases it may be convenient to log all HTTP requests. This can be enabled by setting
the `CONSOLE_DEBUG_LOGLEVEL` environment variable to one of the following values:
- `0` (default) uses no logging.
- `1` log single line per request for server-side errors (status-code 5xx).
- `2` log single line per request for client-side and server-side errors (status-code 4xx/5xx).
- `3` log single line per request for all requests (status-code 4xx/5xx).
- `4` log details per request for server-side errors (status-code 5xx).
- `5` log details per request for client-side and server-side errors (status-code 4xx/5xx).
- `6` log details per request for all requests (status-code 4xx/5xx).
A single line logging has the following information:
- Remote endpoint (IP + port) of the request. Note that reverse proxies may hide the actual remote endpoint of the client's browser.
- HTTP method and URL
- Status code of the response (websocket connections are hijacked, so no response is shown)
- Duration of the request
The detailed logging also includes all request and response headers (if any).
# Contribute to console Project
Please follow console [Contributor's Guide](https://github.com/minio/console/blob/master/CONTRIBUTING.md)

View File

@@ -1,70 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
systemApi "github.com/minio/console/api/operations/system"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
"github.com/minio/console/models"
)
func registerAdminArnsHandlers(api *operations.ConsoleAPI) {
// return a list of arns
api.SystemArnListHandler = systemApi.ArnListHandlerFunc(func(params systemApi.ArnListParams, session *models.Principal) middleware.Responder {
arnsResp, err := getArnsResponse(session, params)
if err != nil {
return systemApi.NewArnListDefault(err.Code).WithPayload(err.APIError)
}
return systemApi.NewArnListOK().WithPayload(arnsResp)
})
}
// getArns invokes admin info and returns a list of arns
func getArns(ctx context.Context, client MinioAdmin) (*models.ArnsResponse, error) {
serverInfo, err := client.serverInfo(ctx)
if err != nil {
return nil, err
}
// build response
return &models.ArnsResponse{
Arns: serverInfo.SQSARN,
}, nil
}
// getArnsResponse returns a list of active arns in the instance
func getArnsResponse(session *models.Principal, params systemApi.ArnListParams) (*models.ArnsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
// serialize output
arnsList, err := getArns(ctx, adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return arnsList, nil
}

View File

@@ -1,97 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"testing"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations/system"
"github.com/minio/console/models"
"github.com/go-openapi/loads"
"github.com/minio/console/api/operations"
"github.com/minio/madmin-go/v3"
asrt "github.com/stretchr/testify/assert"
)
func TestArnsList(t *testing.T) {
assert := asrt.New(t)
adminClient := AdminClientMock{}
// Test-1 : getArns() returns proper arn list
MinioServerInfoMock = func(_ context.Context) (madmin.InfoMessage, error) {
return madmin.InfoMessage{
SQSARN: []string{"uno"},
}, nil
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
arnsList, err := getArns(ctx, adminClient)
assert.NotNil(arnsList, "arn list was returned nil")
if arnsList != nil {
assert.Equal(len(arnsList.Arns), 1, "Incorrect arns count")
}
assert.Nil(err, "Error should have been nil")
// Test-2 : getArns(ctx) fails for whatever reason
MinioServerInfoMock = func(_ context.Context) (madmin.InfoMessage, error) {
return madmin.InfoMessage{}, errors.New("some reason")
}
arnsList, err = getArns(ctx, adminClient)
assert.Nil(arnsList, "arn list was not returned nil")
assert.NotNil(err, "An error should have been returned")
}
func TestRegisterAdminArnsHandlers(t *testing.T) {
assert := asrt.New(t)
swaggerSpec, err := loads.Embedded(SwaggerJSON, FlatSwaggerJSON)
if err != nil {
assert.Fail("Error")
}
api := operations.NewConsoleAPI(swaggerSpec)
api.SystemArnListHandler = nil
registerAdminArnsHandlers(api)
if api.SystemArnListHandler == nil {
assert.Fail("Assignment should happen")
} else {
fmt.Println("Function got assigned: ", api.SystemArnListHandler)
}
// To test error case in registerAdminArnsHandlers
request, _ := http.NewRequest(
"GET",
"http://localhost:9090/api/v1/buckets/",
nil,
)
ArnListParamsStruct := system.ArnListParams{
HTTPRequest: request,
}
modelsPrincipal := models.Principal{
STSAccessKeyID: "accesskey",
}
var value middleware.Responder = api.SystemArnListHandler.Handle(ArnListParamsStruct, &modelsPrincipal)
str := fmt.Sprintf("%#v", value)
fmt.Println("value: ", str)
assert.Equal(strings.Contains(str, "_statusCode:500"), true)
}

View File

@@ -18,385 +18,18 @@ package api
import (
"context"
"io"
"time"
"github.com/minio/madmin-go/v3"
iampolicy "github.com/minio/pkg/v2/policy"
)
type AdminClientMock struct{}
var (
MinioServerInfoMock func(ctx context.Context) (madmin.InfoMessage, error)
minioChangePasswordMock func(ctx context.Context, accessKey, secretKey string) error
minioHelpConfigKVMock func(subSys, key string, envOnly bool) (madmin.Help, error)
minioGetConfigKVMock func(key string) ([]byte, error)
minioSetConfigKVMock func(kv string) (restart bool, err error)
minioDelConfigKVMock func(name string) (err error)
minioHelpConfigKVGlobalMock func(envOnly bool) (madmin.Help, error)
minioGetLogsMock func(ctx context.Context, node string, lineCnt int, logKind string) <-chan madmin.LogInfo
minioListGroupsMock func() ([]string, error)
minioUpdateGroupMembersMock func(madmin.GroupAddRemove) error
minioGetGroupDescriptionMock func(group string) (*madmin.GroupDesc, error)
minioSetGroupStatusMock func(group string, status madmin.GroupStatus) error
minioHealMock func(ctx context.Context, bucket, prefix string, healOpts madmin.HealOpts, clientToken string,
forceStart, forceStop bool) (healStart madmin.HealStartSuccess, healTaskStatus madmin.HealTaskStatus, err error)
minioServerHealthInfoMock func(ctx context.Context, healthDataTypes []madmin.HealthDataType, deadline time.Duration) (interface{}, string, error)
minioListPoliciesMock func() (map[string]*iampolicy.Policy, error)
minioGetPolicyMock func(name string) (*iampolicy.Policy, error)
minioRemovePolicyMock func(name string) error
minioAddPolicyMock func(name string, policy *iampolicy.Policy) error
minioSetPolicyMock func(policyName, entityName string, isGroup bool) error
minioStartProfiling func(profiler madmin.ProfilerType) ([]madmin.StartProfilingResult, error)
minioStopProfiling func() (io.ReadCloser, error)
minioServiceRestartMock func(ctx context.Context) error
getSiteReplicationInfo func(ctx context.Context) (*madmin.SiteReplicationInfo, error)
addSiteReplicationInfo func(ctx context.Context, sites []madmin.PeerSite) (*madmin.ReplicateAddStatus, error)
editSiteReplicationInfo func(ctx context.Context, site madmin.PeerInfo) (*madmin.ReplicateEditStatus, error)
deleteSiteReplicationInfoMock func(ctx context.Context, removeReq madmin.SRRemoveReq) (*madmin.ReplicateRemoveStatus, error)
getSiteReplicationStatus func(ctx context.Context, params madmin.SRStatusOptions) (*madmin.SRStatusInfo, error)
minioListTiersMock func(ctx context.Context) ([]*madmin.TierConfig, error)
minioTierStatsMock func(ctx context.Context) ([]madmin.TierInfo, error)
minioAddTiersMock func(ctx context.Context, tier *madmin.TierConfig) error
minioEditTiersMock func(ctx context.Context, tierName string, creds madmin.TierCreds) error
minioServiceTraceMock func(ctx context.Context, threshold int64, s3, internal, storage, os, errTrace bool) <-chan madmin.ServiceTraceInfo
minioListUsersMock func() (map[string]madmin.UserInfo, error)
minioAddUserMock func(accessKey, secreyKey string) error
minioRemoveUserMock func(accessKey string) error
minioGetUserInfoMock func(accessKey string) (madmin.UserInfo, error)
minioSetUserStatusMock func(accessKey string, status madmin.AccountStatus) error
minioAccountInfoMock func(ctx context.Context) (madmin.AccountInfo, error)
minioAddServiceAccountMock func(ctx context.Context, policy string, user string, accessKey string, secretKey string, description string, name string, expiry *time.Time, status string) (madmin.Credentials, error)
minioListServiceAccountsMock func(ctx context.Context, user string) (madmin.ListServiceAccountsResp, error)
minioDeleteServiceAccountMock func(ctx context.Context, serviceAccount string) error
minioInfoServiceAccountMock func(ctx context.Context, serviceAccount string) (madmin.InfoServiceAccountResp, error)
minioUpdateServiceAccountMock func(ctx context.Context, serviceAccount string, opts madmin.UpdateServiceAccountReq) error
minioGetLDAPPolicyEntitiesMock func(ctx context.Context, query madmin.PolicyEntitiesQuery) (madmin.PolicyEntitiesResult, error)
minioListRemoteBucketsMock func(ctx context.Context, bucket, arnType string) (targets []madmin.BucketTarget, err error)
minioGetRemoteBucketMock func(ctx context.Context, bucket, arnType string) (targets *madmin.BucketTarget, err error)
minioAddRemoteBucketMock func(ctx context.Context, bucket string, target *madmin.BucketTarget) (string, error)
)
func (ac AdminClientMock) serverInfo(ctx context.Context) (madmin.InfoMessage, error) {
return MinioServerInfoMock(ctx)
}
func (ac AdminClientMock) listRemoteBuckets(ctx context.Context, bucket, arnType string) (targets []madmin.BucketTarget, err error) {
return minioListRemoteBucketsMock(ctx, bucket, arnType)
}
func (ac AdminClientMock) getRemoteBucket(ctx context.Context, bucket, arnType string) (targets *madmin.BucketTarget, err error) {
return minioGetRemoteBucketMock(ctx, bucket, arnType)
}
func (ac AdminClientMock) removeRemoteBucket(_ context.Context, _, _ string) error {
return nil
}
func (ac AdminClientMock) addRemoteBucket(ctx context.Context, bucket string, target *madmin.BucketTarget) (string, error) {
return minioAddRemoteBucketMock(ctx, bucket, target)
}
func (ac AdminClientMock) changePassword(ctx context.Context, accessKey, secretKey string) error {
return minioChangePasswordMock(ctx, accessKey, secretKey)
}
func (ac AdminClientMock) speedtest(_ context.Context, _ madmin.SpeedtestOpts) (chan madmin.SpeedTestResult, error) {
return nil, nil
}
func (ac AdminClientMock) verifyTierStatus(_ context.Context, _ string) error {
return nil
}
// mock function helpConfigKV()
func (ac AdminClientMock) helpConfigKV(_ context.Context, subSys, key string, envOnly bool) (madmin.Help, error) {
return minioHelpConfigKVMock(subSys, key, envOnly)
}
// mock function getConfigKV()
func (ac AdminClientMock) getConfigKV(_ context.Context, name string) ([]byte, error) {
return minioGetConfigKVMock(name)
}
// mock function setConfigKV()
func (ac AdminClientMock) setConfigKV(_ context.Context, kv string) (restart bool, err error) {
return minioSetConfigKVMock(kv)
}
// mock function helpConfigKV()
func (ac AdminClientMock) helpConfigKVGlobal(_ context.Context, envOnly bool) (madmin.Help, error) {
return minioHelpConfigKVGlobalMock(envOnly)
}
func (ac AdminClientMock) delConfigKV(_ context.Context, name string) (err error) {
return minioDelConfigKVMock(name)
}
func (ac AdminClientMock) getLogs(ctx context.Context, node string, lineCnt int, logKind string) <-chan madmin.LogInfo {
return minioGetLogsMock(ctx, node, lineCnt, logKind)
}
func (ac AdminClientMock) listGroups(_ context.Context) ([]string, error) {
return minioListGroupsMock()
}
func (ac AdminClientMock) updateGroupMembers(_ context.Context, req madmin.GroupAddRemove) error {
return minioUpdateGroupMembersMock(req)
}
func (ac AdminClientMock) getGroupDescription(_ context.Context, group string) (*madmin.GroupDesc, error) {
return minioGetGroupDescriptionMock(group)
}
func (ac AdminClientMock) setGroupStatus(_ context.Context, group string, status madmin.GroupStatus) error {
return minioSetGroupStatusMock(group, status)
}
func (ac AdminClientMock) heal(ctx context.Context, bucket, prefix string, healOpts madmin.HealOpts, clientToken string,
forceStart, forceStop bool,
) (healStart madmin.HealStartSuccess, healTaskStatus madmin.HealTaskStatus, err error) {
return minioHealMock(ctx, bucket, prefix, healOpts, clientToken, forceStart, forceStop)
}
func (ac AdminClientMock) serverHealthInfo(ctx context.Context, healthDataTypes []madmin.HealthDataType, deadline time.Duration) (interface{}, string, error) {
return minioServerHealthInfoMock(ctx, healthDataTypes, deadline)
}
func (ac AdminClientMock) addOrUpdateIDPConfig(_ context.Context, _, _, _ string, _ bool) (restart bool, err error) {
return true, nil
}
func (ac AdminClientMock) listIDPConfig(_ context.Context, _ string) ([]madmin.IDPListItem, error) {
return []madmin.IDPListItem{{Name: "mock"}}, nil
}
func (ac AdminClientMock) deleteIDPConfig(_ context.Context, _, _ string) (restart bool, err error) {
return true, nil
}
func (ac AdminClientMock) getIDPConfig(_ context.Context, _, _ string) (c madmin.IDPConfig, err error) {
return madmin.IDPConfig{Info: []madmin.IDPCfgInfo{{Key: "mock", Value: "mock"}}}, nil
type AdminClientMock struct {
minioAccountInfoMock func(ctx context.Context) (madmin.AccountInfo, error)
}
func (ac AdminClientMock) kmsStatus(_ context.Context) (madmin.KMSStatus, error) {
return madmin.KMSStatus{Name: "name", DefaultKeyID: "key", Endpoints: map[string]madmin.ItemState{"localhost": madmin.ItemState("online")}}, nil
}
func (ac AdminClientMock) kmsAPIs(_ context.Context) ([]madmin.KMSAPI, error) {
return []madmin.KMSAPI{{Method: "GET", Path: "/mock"}}, nil
}
func (ac AdminClientMock) kmsMetrics(_ context.Context) (*madmin.KMSMetrics, error) {
return &madmin.KMSMetrics{}, nil
}
func (ac AdminClientMock) kmsVersion(_ context.Context) (*madmin.KMSVersion, error) {
return &madmin.KMSVersion{Version: "test-version"}, nil
}
func (ac AdminClientMock) createKey(_ context.Context, _ string) error {
return nil
}
func (ac AdminClientMock) importKey(_ context.Context, _ string, _ []byte) error {
return nil
}
func (ac AdminClientMock) listKeys(_ context.Context, _ string) ([]madmin.KMSKeyInfo, error) {
return []madmin.KMSKeyInfo{{
Name: "name",
CreatedBy: "by",
}}, nil
}
func (ac AdminClientMock) keyStatus(_ context.Context, _ string) (*madmin.KMSKeyStatus, error) {
return &madmin.KMSKeyStatus{KeyID: "key"}, nil
}
func (ac AdminClientMock) deleteKey(_ context.Context, _ string) error {
return nil
}
func (ac AdminClientMock) setKMSPolicy(_ context.Context, _ string, _ []byte) error {
return nil
}
func (ac AdminClientMock) assignPolicy(_ context.Context, _ string, _ []byte) error {
return nil
}
func (ac AdminClientMock) describePolicy(_ context.Context, _ string) (*madmin.KMSDescribePolicy, error) {
return &madmin.KMSDescribePolicy{Name: "name"}, nil
}
func (ac AdminClientMock) getKMSPolicy(_ context.Context, _ string) (*madmin.KMSPolicy, error) {
return &madmin.KMSPolicy{Allow: []string{""}, Deny: []string{""}}, nil
}
func (ac AdminClientMock) listKMSPolicies(_ context.Context, _ string) ([]madmin.KMSPolicyInfo, error) {
return []madmin.KMSPolicyInfo{{
Name: "name",
CreatedBy: "by",
}}, nil
}
func (ac AdminClientMock) deletePolicy(_ context.Context, _ string) error {
return nil
}
func (ac AdminClientMock) describeIdentity(_ context.Context, _ string) (*madmin.KMSDescribeIdentity, error) {
return &madmin.KMSDescribeIdentity{}, nil
}
func (ac AdminClientMock) describeSelfIdentity(_ context.Context) (*madmin.KMSDescribeSelfIdentity, error) {
return &madmin.KMSDescribeSelfIdentity{
Policy: &madmin.KMSPolicy{Allow: []string{}, Deny: []string{}},
}, nil
}
func (ac AdminClientMock) deleteIdentity(_ context.Context, _ string) error {
return nil
}
func (ac AdminClientMock) listIdentities(_ context.Context, _ string) ([]madmin.KMSIdentityInfo, error) {
return []madmin.KMSIdentityInfo{{Identity: "identity"}}, nil
}
func (ac AdminClientMock) listPolicies(_ context.Context) (map[string]*iampolicy.Policy, error) {
return minioListPoliciesMock()
}
func (ac AdminClientMock) getPolicy(_ context.Context, name string) (*iampolicy.Policy, error) {
return minioGetPolicyMock(name)
}
func (ac AdminClientMock) removePolicy(_ context.Context, name string) error {
return minioRemovePolicyMock(name)
}
func (ac AdminClientMock) addPolicy(_ context.Context, name string, policy *iampolicy.Policy) error {
return minioAddPolicyMock(name, policy)
}
func (ac AdminClientMock) setPolicy(_ context.Context, policyName, entityName string, isGroup bool) error {
return minioSetPolicyMock(policyName, entityName, isGroup)
}
// mock function for startProfiling()
func (ac AdminClientMock) startProfiling(_ context.Context, profiler madmin.ProfilerType) ([]madmin.StartProfilingResult, error) {
return minioStartProfiling(profiler)
}
// mock function for stopProfiling()
func (ac AdminClientMock) stopProfiling(_ context.Context) (io.ReadCloser, error) {
return minioStopProfiling()
}
// mock function of serviceRestart()
func (ac AdminClientMock) serviceRestart(ctx context.Context) error {
return minioServiceRestartMock(ctx)
}
func (ac AdminClientMock) getSiteReplicationInfo(ctx context.Context) (*madmin.SiteReplicationInfo, error) {
return getSiteReplicationInfo(ctx)
}
func (ac AdminClientMock) addSiteReplicationInfo(ctx context.Context, sites []madmin.PeerSite, _ madmin.SRAddOptions) (*madmin.ReplicateAddStatus, error) {
return addSiteReplicationInfo(ctx, sites)
}
func (ac AdminClientMock) editSiteReplicationInfo(ctx context.Context, site madmin.PeerInfo, _ madmin.SREditOptions) (*madmin.ReplicateEditStatus, error) {
return editSiteReplicationInfo(ctx, site)
}
func (ac AdminClientMock) deleteSiteReplicationInfo(ctx context.Context, removeReq madmin.SRRemoveReq) (*madmin.ReplicateRemoveStatus, error) {
return deleteSiteReplicationInfoMock(ctx, removeReq)
}
func (ac AdminClientMock) getSiteReplicationStatus(ctx context.Context, params madmin.SRStatusOptions) (*madmin.SRStatusInfo, error) {
return getSiteReplicationStatus(ctx, params)
}
func (ac AdminClientMock) listTiers(ctx context.Context) ([]*madmin.TierConfig, error) {
return minioListTiersMock(ctx)
}
func (ac AdminClientMock) tierStats(ctx context.Context) ([]madmin.TierInfo, error) {
return minioTierStatsMock(ctx)
}
func (ac AdminClientMock) addTier(ctx context.Context, tier *madmin.TierConfig) error {
return minioAddTiersMock(ctx, tier)
}
func (ac AdminClientMock) editTierCreds(ctx context.Context, tierName string, creds madmin.TierCreds) error {
return minioEditTiersMock(ctx, tierName, creds)
}
func (ac AdminClientMock) serviceTrace(ctx context.Context, threshold int64, s3, internal, storage, os, errTrace bool) <-chan madmin.ServiceTraceInfo {
return minioServiceTraceMock(ctx, threshold, s3, internal, storage, os, errTrace)
}
func (ac AdminClientMock) listUsers(_ context.Context) (map[string]madmin.UserInfo, error) {
return minioListUsersMock()
}
func (ac AdminClientMock) addUser(_ context.Context, accessKey, secretKey string) error {
return minioAddUserMock(accessKey, secretKey)
}
func (ac AdminClientMock) removeUser(_ context.Context, accessKey string) error {
return minioRemoveUserMock(accessKey)
}
func (ac AdminClientMock) getUserInfo(_ context.Context, accessKey string) (madmin.UserInfo, error) {
return minioGetUserInfoMock(accessKey)
}
func (ac AdminClientMock) setUserStatus(_ context.Context, accessKey string, status madmin.AccountStatus) error {
return minioSetUserStatusMock(accessKey, status)
}
func (ac AdminClientMock) AccountInfo(ctx context.Context) (madmin.AccountInfo, error) {
return minioAccountInfoMock(ctx)
}
func (ac AdminClientMock) addServiceAccount(ctx context.Context, policy string, user string, accessKey string, secretKey string, description string, name string, expiry *time.Time, status string) (madmin.Credentials, error) {
return minioAddServiceAccountMock(ctx, policy, user, accessKey, secretKey, description, name, expiry, status)
}
func (ac AdminClientMock) listServiceAccounts(ctx context.Context, user string) (madmin.ListServiceAccountsResp, error) {
return minioListServiceAccountsMock(ctx, user)
}
func (ac AdminClientMock) deleteServiceAccount(ctx context.Context, serviceAccount string) error {
return minioDeleteServiceAccountMock(ctx, serviceAccount)
}
func (ac AdminClientMock) infoServiceAccount(ctx context.Context, serviceAccount string) (madmin.InfoServiceAccountResp, error) {
return minioInfoServiceAccountMock(ctx, serviceAccount)
}
func (ac AdminClientMock) updateServiceAccount(ctx context.Context, serviceAccount string, opts madmin.UpdateServiceAccountReq) error {
return minioUpdateServiceAccountMock(ctx, serviceAccount, opts)
}
func (ac AdminClientMock) getLDAPPolicyEntities(ctx context.Context, query madmin.PolicyEntitiesQuery) (madmin.PolicyEntitiesResult, error) {
return minioGetLDAPPolicyEntitiesMock(ctx, query)
return ac.minioAccountInfoMock(ctx)
}

View File

@@ -1,316 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"encoding/base64"
"fmt"
"strings"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/api/operations"
"github.com/minio/console/models"
madmin "github.com/minio/madmin-go/v3"
cfgApi "github.com/minio/console/api/operations/configuration"
)
func registerConfigHandlers(api *operations.ConsoleAPI) {
// List Configurations
api.ConfigurationListConfigHandler = cfgApi.ListConfigHandlerFunc(func(params cfgApi.ListConfigParams, session *models.Principal) middleware.Responder {
configListResp, err := getListConfigResponse(session, params)
if err != nil {
return cfgApi.NewListConfigDefault(err.Code).WithPayload(err.APIError)
}
return cfgApi.NewListConfigOK().WithPayload(configListResp)
})
// Configuration Info
api.ConfigurationConfigInfoHandler = cfgApi.ConfigInfoHandlerFunc(func(params cfgApi.ConfigInfoParams, session *models.Principal) middleware.Responder {
config, err := getConfigResponse(session, params)
if err != nil {
return cfgApi.NewConfigInfoDefault(err.Code).WithPayload(err.APIError)
}
return cfgApi.NewConfigInfoOK().WithPayload(config)
})
// Set Configuration
api.ConfigurationSetConfigHandler = cfgApi.SetConfigHandlerFunc(func(params cfgApi.SetConfigParams, session *models.Principal) middleware.Responder {
resp, err := setConfigResponse(session, params)
if err != nil {
return cfgApi.NewSetConfigDefault(err.Code).WithPayload(err.APIError)
}
return cfgApi.NewSetConfigOK().WithPayload(resp)
})
// Reset Configuration
api.ConfigurationResetConfigHandler = cfgApi.ResetConfigHandlerFunc(func(params cfgApi.ResetConfigParams, session *models.Principal) middleware.Responder {
resp, err := resetConfigResponse(session, params)
if err != nil {
return cfgApi.NewResetConfigDefault(err.Code).WithPayload(err.APIError)
}
return cfgApi.NewResetConfigOK().WithPayload(resp)
})
// Export Configuration as base64 string.
api.ConfigurationExportConfigHandler = cfgApi.ExportConfigHandlerFunc(func(params cfgApi.ExportConfigParams, session *models.Principal) middleware.Responder {
resp, err := exportConfigResponse(session, params)
if err != nil {
return cfgApi.NewExportConfigDefault(err.Code).WithPayload(err.APIError)
}
return cfgApi.NewExportConfigOK().WithPayload(resp)
})
api.ConfigurationPostConfigsImportHandler = cfgApi.PostConfigsImportHandlerFunc(func(params cfgApi.PostConfigsImportParams, session *models.Principal) middleware.Responder {
_, err := importConfigResponse(session, params)
if err != nil {
return cfgApi.NewPostConfigsImportDefault(err.Code).WithPayload(err.APIError)
}
return cfgApi.NewPostConfigsImportDefault(200)
})
}
// listConfig gets all configurations' names and their descriptions
func listConfig(client MinioAdmin) ([]*models.ConfigDescription, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
configKeysHelp, err := client.helpConfigKV(ctx, "", "", false)
if err != nil {
return nil, err
}
var configDescs []*models.ConfigDescription
for _, c := range configKeysHelp.KeysHelp {
desc := &models.ConfigDescription{
Key: c.Key,
Description: c.Description,
}
configDescs = append(configDescs, desc)
}
return configDescs, nil
}
// getListConfigResponse performs listConfig() and serializes it to the handler's output
func getListConfigResponse(session *models.Principal, params cfgApi.ListConfigParams) (*models.ListConfigResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
configDescs, err := listConfig(adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
listGroupsResponse := &models.ListConfigResponse{
Configurations: configDescs,
Total: int64(len(configDescs)),
}
return listGroupsResponse, nil
}
// getConfig gets the key values for a defined configuration.
func getConfig(ctx context.Context, client MinioAdmin, name string) ([]*models.Configuration, error) {
configBytes, err := client.getConfigKV(ctx, name)
if err != nil {
return nil, err
}
subSysConfigs, err := madmin.ParseServerConfigOutput(string(configBytes))
if err != nil {
return nil, err
}
var configSubSysList []*models.Configuration
for _, scfg := range subSysConfigs {
if !madmin.SubSystems.Contains(scfg.SubSystem) {
return nil, fmt.Errorf("no sub-systems found")
}
var confkv []*models.ConfigurationKV
for _, kv := range scfg.KV {
var envOverride *models.EnvOverride
if kv.EnvOverride != nil {
envOverride = &models.EnvOverride{
Name: kv.EnvOverride.Name,
Value: kv.EnvOverride.Value,
}
}
confkv = append(confkv, &models.ConfigurationKV{Key: kv.Key, Value: kv.Value, EnvOverride: envOverride})
}
if len(confkv) == 0 {
continue
}
var fullConfigName string
if scfg.Target == "" {
fullConfigName = scfg.SubSystem
} else {
fullConfigName = scfg.SubSystem + ":" + scfg.Target
}
configSubSysList = append(configSubSysList, &models.Configuration{KeyValues: confkv, Name: fullConfigName})
}
return configSubSysList, nil
}
// getConfigResponse performs getConfig() and serializes it to the handler's output
func getConfigResponse(session *models.Principal, params cfgApi.ConfigInfoParams) ([]*models.Configuration, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
configurations, err := getConfig(ctx, adminClient, params.Name)
if err != nil {
errorVal := ErrorWithContext(ctx, err)
minioError := madmin.ToErrorResponse(err)
if minioError.Code == "XMinioConfigError" {
errorVal.Code = 404
}
return nil, errorVal
}
return configurations, nil
}
// setConfig sets a configuration with the defined key values
func setConfig(ctx context.Context, client MinioAdmin, configName *string, kvs []*models.ConfigurationKV) (restart bool, err error) {
config := buildConfig(configName, kvs)
restart, err = client.setConfigKV(ctx, *config)
if err != nil {
return false, err
}
return restart, nil
}
func setConfigWithARNAccountID(ctx context.Context, client MinioAdmin, configName *string, kvs []*models.ConfigurationKV, arnAccountID string) (restart bool, err error) {
// if arnAccountID is not empty the configuration will be treated as a notification target
// arnAccountID will be used as an identifier for that specific target
// docs: https://min.io/docs/minio/linux/administration/monitoring/bucket-notifications.html
if arnAccountID != "" {
configName = swag.String(fmt.Sprintf("%s:%s", *configName, arnAccountID))
}
return setConfig(ctx, client, configName, kvs)
}
// buildConfig builds a concatenated string including name and keyvalues
// e.g. `region name=us-west-1`
func buildConfig(configName *string, kvs []*models.ConfigurationKV) *string {
var builder strings.Builder
builder.WriteString(*configName)
for _, kv := range kvs {
key := strings.TrimSpace(kv.Key)
if key == "" {
continue
}
builder.WriteString(" ")
builder.WriteString(key)
builder.WriteString("=")
// All newlines must be converted to ','
builder.WriteString(strings.ReplaceAll(strings.TrimSpace(fmt.Sprintf("\"%s\"", kv.Value)), "\n", ","))
}
config := builder.String()
return &config
}
// setConfigResponse implements setConfig() to be used by handler
func setConfigResponse(session *models.Principal, params cfgApi.SetConfigParams) (*models.SetConfigResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
configName := params.Name
needsRestart, err := setConfigWithARNAccountID(ctx, adminClient, &configName, params.Body.KeyValues, params.Body.ArnResourceID)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.SetConfigResponse{Restart: needsRestart}, nil
}
func resetConfig(ctx context.Context, client MinioAdmin, configName *string) (err error) {
err = client.delConfigKV(ctx, *configName)
return err
}
// resetConfigResponse implements resetConfig() to be used by handler
func resetConfigResponse(session *models.Principal, params cfgApi.ResetConfigParams) (*models.SetConfigResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
err = resetConfig(ctx, adminClient, &params.Name)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.SetConfigResponse{Restart: true}, nil
}
func exportConfigResponse(session *models.Principal, params cfgApi.ExportConfigParams) (*models.ConfigExportResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
configRes, err := mAdmin.GetConfig(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// may contain sensitive information so unpack only when required.
return &models.ConfigExportResponse{
Status: "success",
Value: base64.StdEncoding.EncodeToString(configRes),
}, nil
}
func importConfigResponse(session *models.Principal, params cfgApi.PostConfigsImportParams) (*cfgApi.PostConfigsImportDefault, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
file, _, err := params.HTTPRequest.FormFile("file")
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
defer file.Close()
err = mAdmin.SetConfig(ctx, file)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &cfgApi.PostConfigsImportDefault{}, nil
}

View File

@@ -1,609 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"encoding/json"
"errors"
"fmt"
"reflect"
"testing"
"github.com/go-openapi/swag"
"github.com/stretchr/testify/assert"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
)
const (
NotifyPostgresSubSys = "notify_postgres"
PostgresFormat = "format"
PostgresConnectionString = "connection_string"
PostgresTable = "table"
PostgresQueueDir = "queue_dir"
PostgresQueueLimit = "queue_limit"
)
func TestListConfig(t *testing.T) {
assert := assert.New(t)
adminClient := AdminClientMock{}
function := "listConfig()"
// Test-1 : listConfig() get list of two configurations and ensure is output correctly
configListMock := []madmin.HelpKV{
{
Key: "region",
Description: "label the location of the server",
},
{
Key: "notify_nsq",
Description: "publish bucket notifications to NSQ endpoints",
},
}
mockConfigList := madmin.Help{
SubSys: "sys",
Description: "desc",
MultipleTargets: false,
KeysHelp: configListMock,
}
expectedKeysDesc := mockConfigList.KeysHelp
// mock function response from listConfig()
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return mockConfigList, nil
}
configList, err := listConfig(adminClient)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// verify length of keys is correct
assert.Equal(len(expectedKeysDesc), len(configList), fmt.Sprintf("Failed on %s: length of Configs's lists is not the same", function))
// verify KeysHelp content
for i, kv := range configList {
assert.Equal(expectedKeysDesc[i].Key, kv.Key)
assert.Equal(expectedKeysDesc[i].Description, kv.Description)
}
// Test-2 : listConfig() Return error and see that the error is handled correctly and returned
// mock function response from listConfig()
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return madmin.Help{}, errors.New("error")
}
_, err = listConfig(adminClient)
if assert.Error(err) {
assert.Equal("error", err.Error())
}
}
func TestSetConfig(t *testing.T) {
assert := assert.New(t)
adminClient := AdminClientMock{}
function := "setConfig()"
// mock function response from setConfig()
minioSetConfigKVMock = func(_ string) (restart bool, err error) {
return false, nil
}
configName := "notify_postgres"
kvs := []*models.ConfigurationKV{
{
Key: "enable",
Value: "off",
},
{
Key: "connection_string",
Value: "",
},
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : setConfig() sets a config with two key value pairs
restart, err := setConfig(ctx, adminClient, &configName, kvs)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
assert.Equal(restart, false)
// Test-2 : setConfig() returns error, handle properly
minioSetConfigKVMock = func(_ string) (restart bool, err error) {
return false, errors.New("error")
}
restart, err = setConfig(ctx, adminClient, &configName, kvs)
if assert.Error(err) {
assert.Equal("error", err.Error())
}
assert.Equal(restart, false)
// Test-4 : setConfig() set config, need restart
minioSetConfigKVMock = func(_ string) (restart bool, err error) {
return true, nil
}
restart, err = setConfig(ctx, adminClient, &configName, kvs)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
assert.Equal(restart, true)
}
func TestDelConfig(t *testing.T) {
assert := assert.New(t)
adminClient := AdminClientMock{}
function := "resetConfig()"
// mock function response from setConfig()
minioDelConfigKVMock = func(_ string) (err error) {
return nil
}
configName := "region"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : resetConfig() resets a config with the config name
err := resetConfig(ctx, adminClient, &configName)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-2 : resetConfig() returns error, handle properly
minioDelConfigKVMock = func(_ string) (err error) {
return errors.New("error")
}
err = resetConfig(ctx, adminClient, &configName)
if assert.Error(err) {
assert.Equal("error", err.Error())
}
}
func Test_buildConfig(t *testing.T) {
type args struct {
configName *string
kvs []*models.ConfigurationKV
}
tests := []struct {
name string
args args
want *string
}{
// Test-1: buildConfig() format correctly configuration as "config_name k=v k2=v2"
{
name: "format correctly",
args: args{
configName: swag.String("notify_postgres"),
kvs: []*models.ConfigurationKV{
{
Key: "enable",
Value: "off",
},
{
Key: "connection_string",
Value: "",
},
},
},
want: swag.String("notify_postgres enable=\"off\" connection_string=\"\""),
},
// Test-2: buildConfig() format correctly configuration as "config_name k=v k2=v2 k2=v3" with duplicate keys
{
name: "duplicated keys in config",
args: args{
configName: swag.String("notify_postgres"),
kvs: []*models.ConfigurationKV{
{
Key: "enable",
Value: "off",
},
{
Key: "connection_string",
Value: "",
},
{
Key: "connection_string",
Value: "x",
},
},
},
want: swag.String("notify_postgres enable=\"off\" connection_string=\"\" connection_string=\"x\""),
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
if got := buildConfig(tt.args.configName, tt.args.kvs); !reflect.DeepEqual(got, tt.want) {
t.Errorf("buildConfig() = %s, want %s", *got, *tt.want)
}
})
}
}
func Test_setConfigWithARN(t *testing.T) {
assert := assert.New(t)
client := AdminClientMock{}
type args struct {
ctx context.Context
client MinioAdmin
configName *string
kvs []*models.ConfigurationKV
arn string
}
tests := []struct {
name string
args args
mockSetConfig func(kv string) (restart bool, err error)
wantErr bool
expected bool
}{
{
name: "Set valid config with arn",
args: args{
ctx: context.Background(),
client: client,
configName: swag.String("notify_kafka"),
kvs: []*models.ConfigurationKV{
{
Key: "brokers",
Value: "http://localhost:8080/broker1,http://localhost:8080/broker2",
},
},
arn: "1",
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
wantErr: false,
expected: false,
},
{
name: "Set valid config, expect restart",
args: args{
ctx: context.Background(),
client: client,
configName: swag.String("notify_kafka"),
kvs: []*models.ConfigurationKV{
{
Key: "brokers",
Value: "http://localhost:8080/broker1,http://localhost:8080/broker2",
},
},
arn: "1",
},
mockSetConfig: func(_ string) (restart bool, err error) {
return true, nil
},
wantErr: false,
expected: true,
},
{
name: "Set valid config without arn",
args: args{
ctx: context.Background(),
client: client,
configName: swag.String("region"),
kvs: []*models.ConfigurationKV{
{
Key: "name",
Value: "us-west-1",
},
},
arn: "",
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
wantErr: false,
expected: false,
},
{
name: "Setting an incorrect config",
args: args{
ctx: context.Background(),
client: client,
configName: swag.String("oorgle"),
kvs: []*models.ConfigurationKV{
{
Key: "name",
Value: "us-west-1",
},
},
arn: "",
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, errors.New("error")
},
wantErr: true,
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
// mock function response from setConfig()
minioSetConfigKVMock = tt.mockSetConfig
restart, err := setConfigWithARNAccountID(tt.args.ctx, tt.args.client, tt.args.configName, tt.args.kvs, tt.args.arn)
if (err != nil) != tt.wantErr {
t.Errorf("setConfigWithARNAccountID() error = %v, wantErr %v", err, tt.wantErr)
}
assert.Equal(restart, tt.expected)
})
}
}
func Test_getConfig(t *testing.T) {
client := AdminClientMock{}
type args struct {
client MinioAdmin
name string
}
tests := []struct {
name string
args args
mock func()
want []*models.Configuration
wantErr bool
}{
{
name: "get config",
args: args{
client: client,
name: "notify_postgres",
},
mock: func() {
// mock function response from getConfig()
minioGetConfigKVMock = func(_ string) ([]byte, error) {
return []byte(`notify_postgres:_ connection_string="host=localhost dbname=minio_events user=postgres password=password port=5432 sslmode=disable" table=bucketevents`), nil
}
configListMock := []madmin.HelpKV{
{
Key: PostgresConnectionString,
Description: `Postgres server connection-string e.g. "host=localhost port=5432 dbname=minio_events user=postgres password=password sslmode=disable"`,
Type: "string",
},
{
Key: PostgresTable,
Description: "DB table name to store/update events, table is auto-created",
Type: "string",
},
{
Key: PostgresFormat,
Description: "desc",
Type: "namespace*|access",
},
{
Key: PostgresQueueDir,
Description: "des",
Optional: true,
Type: "path",
},
{
Key: PostgresQueueLimit,
Description: "desc",
Optional: true,
Type: "number",
},
{
Key: madmin.CommentKey,
Description: "",
Optional: true,
Type: "sentence",
},
}
mockConfigList := madmin.Help{
SubSys: NotifyPostgresSubSys,
Description: "publish bucket notifications to Postgres databases",
MultipleTargets: true,
KeysHelp: configListMock,
}
// mock function response from listConfig()
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return mockConfigList, nil
}
},
want: []*models.Configuration{
{
KeyValues: []*models.ConfigurationKV{
{
Key: PostgresConnectionString,
Value: "host=localhost dbname=minio_events user=postgres password=password port=5432 sslmode=disable",
},
{
Key: PostgresTable,
Value: "bucketevents",
},
}, Name: "notify_postgres",
},
},
wantErr: false,
},
{
name: "valid config, but server returned empty",
args: args{
client: client,
name: NotifyPostgresSubSys,
},
mock: func() {
// mock function response from getConfig()
minioGetConfigKVMock = func(_ string) ([]byte, error) {
return []byte(`notify_postgres:_`), nil
}
configListMock := []madmin.HelpKV{
{
Key: PostgresConnectionString,
Description: `Postgres server connection-string e.g. "host=localhost port=5432 dbname=minio_events user=postgres password=password sslmode=disable"`,
Type: "string",
},
{
Key: PostgresTable,
Description: "DB table name to store/update events, table is auto-created",
Type: "string",
},
{
Key: PostgresFormat,
Description: "desc",
Type: "namespace*|access",
},
{
Key: PostgresQueueDir,
Description: "des",
Optional: true,
Type: "path",
},
{
Key: PostgresQueueLimit,
Description: "desc",
Optional: true,
Type: "number",
},
{
Key: madmin.CommentKey,
Description: "optionally add a comment to this setting",
Optional: true,
Type: "sentence",
},
}
mockConfigList := madmin.Help{
SubSys: NotifyPostgresSubSys,
Description: "publish bucket notifications to Postgres databases",
MultipleTargets: true,
KeysHelp: configListMock,
}
// mock function response from listConfig()
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return mockConfigList, nil
}
},
want: nil,
wantErr: false,
},
{
name: "random bytes coming out of getConfigKv",
args: args{
client: client,
name: "notify_postgres",
},
mock: func() {
// mock function response from getConfig()
minioGetConfigKVMock = func(_ string) ([]byte, error) {
x := make(map[string]string)
x["x"] = "x"
j, _ := json.Marshal(x)
return j, nil
}
configListMock := []madmin.HelpKV{
{
Key: PostgresConnectionString,
Description: `Postgres server connection-string e.g. "host=localhost port=5432 dbname=minio_events user=postgres password=password sslmode=disable"`,
Type: "string",
},
{
Key: PostgresTable,
Description: "DB table name to store/update events, table is auto-created",
Type: "string",
},
{
Key: PostgresFormat,
Description: "desc",
Type: "namespace*|access",
},
{
Key: PostgresQueueDir,
Description: "des",
Optional: true,
Type: "path",
},
{
Key: PostgresQueueLimit,
Description: "desc",
Optional: true,
Type: "number",
},
{
Key: madmin.CommentKey,
Description: "optionally add a comment to this setting",
Optional: true,
Type: "sentence",
},
}
mockConfigList := madmin.Help{
SubSys: NotifyPostgresSubSys,
Description: "publish bucket notifications to Postgres databases",
MultipleTargets: true,
KeysHelp: configListMock,
}
// mock function response from listConfig()
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return mockConfigList, nil
}
},
want: nil,
wantErr: true,
},
{
name: "bad config",
args: args{
client: client,
name: "notify_postgresx",
},
mock: func() {
// mock function response from getConfig()
minioGetConfigKVMock = func(_ string) ([]byte, error) {
return nil, errors.New("invalid config")
}
mockConfigList := madmin.Help{}
// mock function response from listConfig()
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return mockConfigList, nil
}
},
want: nil,
wantErr: true,
},
{
name: "no help",
args: args{
client: client,
name: "notify_postgresx",
},
mock: func() {
// mock function response from getConfig()
minioGetConfigKVMock = func(_ string) ([]byte, error) {
return nil, errors.New("invalid config")
}
// mock function response from listConfig()
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return madmin.Help{}, errors.New("no help")
}
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
tt.mock()
t.Run(tt.name, func(_ *testing.T) {
got, err := getConfig(context.Background(), tt.args.client, tt.args.name)
if (err != nil) != tt.wantErr {
t.Errorf("getConfig() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("getConfig() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,104 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"encoding/json"
"strings"
"time"
"github.com/minio/madmin-go/v3"
"github.com/minio/websocket"
)
const logTimeFormat string = "15:04:05 MST 01/02/2006"
// startConsoleLog starts log of the servers
func startConsoleLog(ctx context.Context, conn WSConn, client MinioAdmin, logRequest LogRequest) error {
var node string
// name of node, default = "" (all)
if logRequest.node == "all" {
node = ""
} else {
node = logRequest.node
}
trimNode := strings.Split(node, ":")
// number of log lines
lineCount := 100
// type of logs "minio"|"application"|"all" default = "all"
var logKind string
if logRequest.logType == "minio" || logRequest.logType == "application" || logRequest.logType == "all" {
logKind = logRequest.logType
} else {
logKind = "all"
}
// Start listening on all Console Log activity.
logCh := client.getLogs(ctx, trimNode[0], lineCount, logKind)
for {
select {
case <-ctx.Done():
return nil
case logInfo, ok := <-logCh:
// zero value returned because the channel is closed and empty
if !ok {
return nil
}
if logInfo.Err != nil {
LogError("error on console logs: %v", logInfo.Err)
return logInfo.Err
}
// Serialize message to be sent
bytes, err := json.Marshal(serializeConsoleLogInfo(&logInfo))
if err != nil {
LogError("error on json.Marshal: %v", err)
return err
}
// Send Message through websocket connection
err = conn.writeMessage(websocket.TextMessage, bytes)
if err != nil {
LogError("error writeMessage: %v", err)
return err
}
}
}
}
func serializeConsoleLogInfo(l *madmin.LogInfo) (logInfo madmin.LogInfo) {
logInfo = *l
if logInfo.ConsoleMsg != "" {
logInfo.ConsoleMsg = strings.TrimPrefix(logInfo.ConsoleMsg, "\n")
}
if logInfo.Time != "" {
logInfo.Time = getLogTime(logInfo.Time)
}
return logInfo
}
func getLogTime(lt string) string {
tm, err := time.Parse(time.RFC3339Nano, lt)
if err != nil {
return lt
}
return tm.Format(logTimeFormat)
}

View File

@@ -1,117 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"encoding/json"
"fmt"
"testing"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
)
func TestAdminConsoleLog(t *testing.T) {
assert := assert.New(t)
adminClient := AdminClientMock{}
mockWSConn := mockConn{}
function := "startConsoleLog(ctx, )"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
testReceiver := make(chan madmin.LogInfo, 5)
textToReceive := "test message"
testStreamSize := 5
isClosed := false // testReceiver is closed?
// Test-1: Serve Console with no errors until Console finishes sending
// define mock function behavior for minio server Console
minioGetLogsMock = func(_ context.Context, _ string, _ int, _ string) <-chan madmin.LogInfo {
ch := make(chan madmin.LogInfo)
// Only success, start a routine to start reading line by line.
go func(ch chan<- madmin.LogInfo) {
defer close(ch)
lines := make([]int, testStreamSize)
// mocking sending 5 lines of info
for range lines {
info := madmin.LogInfo{
ConsoleMsg: textToReceive,
}
ch <- info
}
}(ch)
return ch
}
writesCount := 1
// mock connection WriteMessage() no error
connWriteMessageMock = func(_ int, data []byte) error {
// emulate that receiver gets the message written
var t madmin.LogInfo
_ = json.Unmarshal(data, &t)
if writesCount == testStreamSize {
if !isClosed {
close(testReceiver)
isClosed = true
}
return nil
}
testReceiver <- t
writesCount++
return nil
}
if err := startConsoleLog(ctx, mockWSConn, adminClient, LogRequest{node: "", logType: "all"}); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// check that the TestReceiver got the same number of data from Console.
for i := range testReceiver {
assert.Equal(textToReceive, i.ConsoleMsg)
}
// Test-2: if error happens while writing, return error
connWriteMessageMock = func(_ int, _ []byte) error {
return fmt.Errorf("error on write")
}
if err := startConsoleLog(ctx, mockWSConn, adminClient, LogRequest{node: "", logType: "all"}); assert.Error(err) {
assert.Equal("error on write", err.Error())
}
// Test-3: error happens on GetLogs Minio, Console should stop
// and error shall be returned.
minioGetLogsMock = func(_ context.Context, _ string, _ int, _ string) <-chan madmin.LogInfo {
ch := make(chan madmin.LogInfo)
// Only success, start a routine to start reading line by line.
go func(ch chan<- madmin.LogInfo) {
defer close(ch)
lines := make([]int, 2)
// mocking sending 5 lines of info
for range lines {
info := madmin.LogInfo{
ConsoleMsg: textToReceive,
}
ch <- info
}
ch <- madmin.LogInfo{Err: fmt.Errorf("error on Console")}
}(ch)
return ch
}
connWriteMessageMock = func(_ int, _ []byte) error {
return nil
}
if err := startConsoleLog(ctx, mockWSConn, adminClient, LogRequest{node: "", logType: "all"}); assert.Error(err) {
assert.Equal("error on Console", err.Error())
}
}

View File

@@ -1,355 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
"github.com/minio/console/pkg/utils"
"github.com/minio/madmin-go/v3"
groupApi "github.com/minio/console/api/operations/group"
"github.com/minio/console/models"
)
func registerGroupsHandlers(api *operations.ConsoleAPI) {
// List Groups
api.GroupListGroupsHandler = groupApi.ListGroupsHandlerFunc(func(params groupApi.ListGroupsParams, session *models.Principal) middleware.Responder {
listGroupsResponse, err := getListGroupsResponse(session, params)
if err != nil {
return groupApi.NewListGroupsDefault(err.Code).WithPayload(err.APIError)
}
return groupApi.NewListGroupsOK().WithPayload(listGroupsResponse)
})
// Group Info
api.GroupGroupInfoHandler = groupApi.GroupInfoHandlerFunc(func(params groupApi.GroupInfoParams, session *models.Principal) middleware.Responder {
groupInfo, err := getGroupInfoResponse(session, params)
if err != nil {
return groupApi.NewGroupInfoDefault(err.Code).WithPayload(err.APIError)
}
return groupApi.NewGroupInfoOK().WithPayload(groupInfo)
})
// Add Group
api.GroupAddGroupHandler = groupApi.AddGroupHandlerFunc(func(params groupApi.AddGroupParams, session *models.Principal) middleware.Responder {
if err := getAddGroupResponse(session, params); err != nil {
return groupApi.NewAddGroupDefault(err.Code).WithPayload(err.APIError)
}
return groupApi.NewAddGroupCreated()
})
// Remove Group
api.GroupRemoveGroupHandler = groupApi.RemoveGroupHandlerFunc(func(params groupApi.RemoveGroupParams, session *models.Principal) middleware.Responder {
if err := getRemoveGroupResponse(session, params); err != nil {
return groupApi.NewRemoveGroupDefault(err.Code).WithPayload(err.APIError)
}
return groupApi.NewRemoveGroupNoContent()
})
// Update Group
api.GroupUpdateGroupHandler = groupApi.UpdateGroupHandlerFunc(func(params groupApi.UpdateGroupParams, session *models.Principal) middleware.Responder {
groupUpdateResp, err := getUpdateGroupResponse(session, params)
if err != nil {
return groupApi.NewUpdateGroupDefault(err.Code).WithPayload(err.APIError)
}
return groupApi.NewUpdateGroupOK().WithPayload(groupUpdateResp)
})
}
// getListGroupsResponse performs listGroups() and serializes it to the handler's output
func getListGroupsResponse(session *models.Principal, params groupApi.ListGroupsParams) (*models.ListGroupsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
groups, err := adminClient.listGroups(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// serialize output
listGroupsResponse := &models.ListGroupsResponse{
Groups: groups,
Total: int64(len(groups)),
}
return listGroupsResponse, nil
}
// groupInfo calls MinIO server get Group's info
func groupInfo(ctx context.Context, client MinioAdmin, group string) (*madmin.GroupDesc, error) {
groupDesc, err := client.getGroupDescription(ctx, group)
if err != nil {
return nil, err
}
return groupDesc, nil
}
// getGroupInfoResponse performs groupInfo() and serializes it to the handler's output
func getGroupInfoResponse(session *models.Principal, params groupApi.GroupInfoParams) (*models.Group, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
groupName, err := utils.DecodeBase64(params.Name)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
groupDesc, err := groupInfo(ctx, adminClient, groupName)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
groupResponse := &models.Group{
Members: groupDesc.Members,
Name: groupDesc.Name,
Policy: groupDesc.Policy,
Status: groupDesc.Status,
}
return groupResponse, nil
}
// addGroupAdd a MinIO group with the defined members
func addGroup(ctx context.Context, client MinioAdmin, group string, members []string) error {
gAddRemove := madmin.GroupAddRemove{
Group: group,
Members: members,
IsRemove: false,
}
err := client.updateGroupMembers(ctx, gAddRemove)
if err != nil {
return err
}
return nil
}
// getAddGroupResponse performs addGroup() and serializes it to the handler's output
func getAddGroupResponse(session *models.Principal, params groupApi.AddGroupParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// AddGroup request needed to proceed
if params.Body == nil {
return ErrorWithContext(ctx, ErrGroupBodyNotInRequest)
}
groupRequest := params.Body
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
groupList, _ := adminClient.listGroups(ctx)
for _, b := range groupList {
if b == *groupRequest.Group {
return ErrorWithContext(ctx, ErrGroupAlreadyExists)
}
}
if err := addGroup(ctx, adminClient, *groupRequest.Group, groupRequest.Members); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
// removeGroup deletes a minIO group only if it has no members
func removeGroup(ctx context.Context, client MinioAdmin, group string) error {
gAddRemove := madmin.GroupAddRemove{
Group: group,
Members: []string{},
IsRemove: true,
}
err := client.updateGroupMembers(ctx, gAddRemove)
if err != nil {
return err
}
return nil
}
// getRemoveGroupResponse performs removeGroup() and serializes it to the handler's output
func getRemoveGroupResponse(session *models.Principal, params groupApi.RemoveGroupParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params.Name == "" {
return ErrorWithContext(ctx, ErrGroupNameNotInRequest)
}
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
// Create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
groupName, err := utils.DecodeBase64(params.Name)
if err != nil {
return ErrorWithContext(ctx, err)
}
if err := removeGroup(ctx, adminClient, groupName); err != nil {
minioError := madmin.ToErrorResponse(err)
err2 := ErrorWithContext(ctx, err)
if minioError.Code == "XMinioAdminNoSuchGroup" {
err2.Code = 404
}
return err2
}
return nil
}
// updateGroup updates a group by adding/removing members and setting the status to the desired one
//
// isRemove: whether remove members or not
func updateGroupMembers(ctx context.Context, client MinioAdmin, group string, members []string, isRemove bool) error {
gAddRemove := madmin.GroupAddRemove{
Group: group,
Members: members,
IsRemove: isRemove,
}
err := client.updateGroupMembers(ctx, gAddRemove)
if err != nil {
return err
}
return nil
}
// addOrDeleteMembers updates a group members by adding or deleting them based on the expectedMembers
func addOrDeleteMembers(ctx context.Context, client MinioAdmin, group *madmin.GroupDesc, expectedMembers []string) error {
// get members to delete/add
membersToDelete := DifferenceArrays(group.Members, expectedMembers)
membersToAdd := DifferenceArrays(expectedMembers, group.Members)
// delete members if any to be deleted
if len(membersToDelete) > 0 {
err := updateGroupMembers(ctx, client, group.Name, membersToDelete, true)
if err != nil {
return err
}
}
// add members if any to be added
if len(membersToAdd) > 0 {
err := updateGroupMembers(ctx, client, group.Name, membersToAdd, false)
if err != nil {
return err
}
}
return nil
}
func setGroupStatus(ctx context.Context, client MinioAdmin, group, status string) error {
var setStatus madmin.GroupStatus
switch status {
case "enabled":
setStatus = madmin.GroupEnabled
case "disabled":
setStatus = madmin.GroupDisabled
default:
return errors.New(500, "status not valid")
}
return client.setGroupStatus(ctx, group, setStatus)
}
// getUpdateGroupResponse updates a group by adding or removing it's members depending on the request,
// also sets the group's status if status in the request is different than the current one.
// Then serializes the output to be used by the handler.
func getUpdateGroupResponse(session *models.Principal, params groupApi.UpdateGroupParams) (*models.Group, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params.Name == "" {
return nil, ErrorWithContext(ctx, ErrGroupNameNotInRequest)
}
if params.Body == nil {
return nil, ErrorWithContext(ctx, ErrGroupBodyNotInRequest)
}
expectedGroupUpdate := params.Body
groupName, err := utils.DecodeBase64(params.Name)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
groupUpdated, err := groupUpdate(ctx, adminClient, groupName, expectedGroupUpdate)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
groupResponse := &models.Group{
Name: groupUpdated.Name,
Members: groupUpdated.Members,
Policy: groupUpdated.Policy,
Status: groupUpdated.Status,
}
return groupResponse, nil
}
// groupUpdate updates a group given the expected parameters, compares the expected parameters against the current ones
// and updates them accordingly, status is only updated if the expected status is different than the current one.
// Then fetches the group again to return the object updated.
func groupUpdate(ctx context.Context, client MinioAdmin, groupName string, expectedGroup *models.UpdateGroupRequest) (*madmin.GroupDesc, error) {
expectedMembers := expectedGroup.Members
expectedStatus := *expectedGroup.Status
// get current members and status
groupDescription, err := groupInfo(ctx, client, groupName)
if err != nil {
LogInfo("error getting group info: %v", err)
return nil, err
}
// update group members
err = addOrDeleteMembers(ctx, client, groupDescription, expectedMembers)
if err != nil {
LogInfo("error updating group: %v", err)
return nil, err
}
// update group status only if different from current status
if expectedStatus != groupDescription.Status {
err = setGroupStatus(ctx, client, groupDescription.Name, expectedStatus)
if err != nil {
LogInfo("error updating group's status: %v", err)
return nil, err
}
}
// return latest group info to verify that changes were applied correctly
groupDescription, err = groupInfo(ctx, client, groupName)
if err != nil {
LogInfo("error getting group info: %v", err)
return nil, err
}
return groupDescription, nil
}

View File

@@ -1,291 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"errors"
"fmt"
"testing"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
)
func TestListGroups(t *testing.T) {
assert := assert.New(t)
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : listGroups() Get response from minio client with two Groups and return the same number on listGroups()
mockGroupsList := []string{"group1", "group2"}
// mock function response from listGroups()
minioListGroupsMock = func() ([]string, error) {
return mockGroupsList, nil
}
// get list Groups response this response should have Name, CreationDate, Size and Access
// as part of of each Groups
function := "listGroups()"
groupsList, err := adminClient.listGroups(ctx)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// verify length of Groupss is correct
assert.Equal(len(mockGroupsList), len(groupsList), fmt.Sprintf("Failed on %s: length of Groups's lists is not the same", function))
for i, g := range groupsList {
assert.Equal(mockGroupsList[i], g)
}
// Test-2 : listGroups() Return error and see that the error is handled correctly and returned
minioListGroupsMock = func() ([]string, error) {
return nil, errors.New("error")
}
_, err = adminClient.listGroups(ctx)
if assert.Error(err) {
assert.Equal("error", err.Error())
}
}
func TestAddGroup(t *testing.T) {
assert := assert.New(t)
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : addGroup() add a new group with two members
newGroup := "acmeGroup"
groupMembers := []string{"user1", "user2"}
// mock function response from updateGroupMembers()
minioUpdateGroupMembersMock = func(madmin.GroupAddRemove) error {
return nil
}
function := "addGroup()"
if err := addGroup(ctx, adminClient, newGroup, groupMembers); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-2 : addGroup() Return error and see that the error is handled correctly and returned
minioUpdateGroupMembersMock = func(madmin.GroupAddRemove) error {
return errors.New("error")
}
if err := addGroup(ctx, adminClient, newGroup, groupMembers); assert.Error(err) {
assert.Equal("error", err.Error())
}
}
func TestRemoveGroup(t *testing.T) {
assert := assert.New(t)
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : removeGroup() remove group assume it has no members
groupToRemove := "acmeGroup"
// mock function response from updateGroupMembers()
minioUpdateGroupMembersMock = func(madmin.GroupAddRemove) error {
return nil
}
function := "removeGroup()"
if err := removeGroup(ctx, adminClient, groupToRemove); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-2 : removeGroup() Return error and see that the error is handled correctly and returned
minioUpdateGroupMembersMock = func(madmin.GroupAddRemove) error {
return errors.New("error")
}
if err := removeGroup(ctx, adminClient, groupToRemove); assert.Error(err) {
assert.Equal("error", err.Error())
}
}
func TestGroupInfo(t *testing.T) {
assert := assert.New(t)
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : groupInfo() get group info
groupName := "acmeGroup"
mockResponse := &madmin.GroupDesc{
Name: groupName,
Policy: "policyTest",
Members: []string{"user1", "user2"},
Status: "enabled",
}
// mock function response from updateGroupMembers()
minioGetGroupDescriptionMock = func(_ string) (*madmin.GroupDesc, error) {
return mockResponse, nil
}
function := "groupInfo()"
info, err := groupInfo(ctx, adminClient, groupName)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
assert.Equal(groupName, info.Name)
assert.Equal("policyTest", info.Policy)
assert.ElementsMatch([]string{"user1", "user2"}, info.Members)
assert.Equal("enabled", info.Status)
// Test-2 : groupInfo() Return error and see that the error is handled correctly and returned
minioGetGroupDescriptionMock = func(_ string) (*madmin.GroupDesc, error) {
return nil, errors.New("error")
}
_, err = groupInfo(ctx, adminClient, groupName)
if assert.Error(err) {
assert.Equal("error", err.Error())
}
}
func TestUpdateGroup(t *testing.T) {
assert := assert.New(t)
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : addOrDeleteMembers() update group members add user3 and delete user2
function := "addOrDeleteMembers()"
groupName := "acmeGroup"
mockGroupDesc := &madmin.GroupDesc{
Name: groupName,
Policy: "policyTest",
Members: []string{"user1", "user2"},
Status: "enabled",
}
membersDesired := []string{"user3", "user1"}
minioUpdateGroupMembersMock = func(madmin.GroupAddRemove) error {
return nil
}
if err := addOrDeleteMembers(ctx, adminClient, mockGroupDesc, membersDesired); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-2 : addOrDeleteMembers() handle error correctly
minioUpdateGroupMembersMock = func(madmin.GroupAddRemove) error {
return errors.New("error")
}
if err := addOrDeleteMembers(ctx, adminClient, mockGroupDesc, membersDesired); assert.Error(err) {
assert.Equal("error", err.Error())
}
// Test-3 : addOrDeleteMembers() only add members but handle error on adding
minioUpdateGroupMembersMock = func(madmin.GroupAddRemove) error {
return errors.New("error")
}
membersDesired = []string{"user3", "user1", "user2"}
if err := addOrDeleteMembers(ctx, adminClient, mockGroupDesc, membersDesired); assert.Error(err) {
assert.Equal("error", err.Error())
}
// Test-4: addOrDeleteMembers() no updates needed so error shall not be triggered or handled.
minioUpdateGroupMembersMock = func(madmin.GroupAddRemove) error {
return errors.New("error")
}
membersDesired = []string{"user1", "user2"}
if err := addOrDeleteMembers(ctx, adminClient, mockGroupDesc, membersDesired); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-5 : groupUpdate() integrate all from getting current group to update it and see if it changed.
// This test mocks one function twice and makes sure it returns different content on each call.
function = "groupUpdate()"
groupName = "acmeGroup"
membersDesired = []string{"user1", "user2", "user3"}
expectedGroupUpdate := &models.UpdateGroupRequest{
Members: membersDesired,
Status: swag.String("disabled"),
}
mockResponseBeforeUpdate := &madmin.GroupDesc{
Name: groupName,
Policy: "policyTest",
Members: []string{"user1", "user2"},
Status: "enabled",
}
mockResponseAfterUpdate := &madmin.GroupDesc{
Name: groupName,
Policy: "policyTest",
Members: []string{"user1", "user2", "user3"},
Status: "disabled",
}
// groupUpdate uses getInfo() twice which uses getGroupDescription() so we need to mock as if it called
// the function twice but the second time returned an error
is2ndRunGroupInfo := false
// mock function response from updateGroupMembers()
minioGetGroupDescriptionMock = func(_ string) (*madmin.GroupDesc, error) {
if is2ndRunGroupInfo {
return mockResponseAfterUpdate, nil
}
is2ndRunGroupInfo = true
return mockResponseBeforeUpdate, nil
}
minioUpdateGroupMembersMock = func(madmin.GroupAddRemove) error {
return nil
}
minioSetGroupStatusMock = func(_ string, _ madmin.GroupStatus) error {
return nil
}
groupUpdated, err := groupUpdate(ctx, adminClient, groupName, expectedGroupUpdate)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// assert elements were updated as expected
assert.ElementsMatch(membersDesired, groupUpdated.Members)
assert.Equal(groupName, groupUpdated.Name)
assert.Equal(*expectedGroupUpdate.Status, groupUpdated.Status)
}
func TestSetGroupStatus(t *testing.T) {
assert := assert.New(t)
adminClient := AdminClientMock{}
function := "setGroupStatus()"
groupName := "acmeGroup"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1: setGroupStatus() update valid disabled status
expectedStatus := "disabled"
minioSetGroupStatusMock = func(_ string, _ madmin.GroupStatus) error {
return nil
}
if err := setGroupStatus(ctx, adminClient, groupName, expectedStatus); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-2: setGroupStatus() update valid enabled status
expectedStatus = "enabled"
minioSetGroupStatusMock = func(_ string, _ madmin.GroupStatus) error {
return nil
}
if err := setGroupStatus(ctx, adminClient, groupName, expectedStatus); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-3: setGroupStatus() update invalid status, should send error
expectedStatus = "invalid"
minioSetGroupStatusMock = func(_ string, _ madmin.GroupStatus) error {
return nil
}
if err := setGroupStatus(ctx, adminClient, groupName, expectedStatus); assert.Error(err) {
assert.Equal("status not valid", err.Error())
}
// Test-4: setGroupStatus() handler error correctly
expectedStatus = "enabled"
minioSetGroupStatusMock = func(_ string, _ madmin.GroupStatus) error {
return errors.New("error")
}
if err := setGroupStatus(ctx, adminClient, groupName, expectedStatus); assert.Error(err) {
assert.Equal("error", err.Error())
}
}

View File

@@ -1,172 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
b64 "encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"time"
"github.com/minio/console/pkg/logger"
"github.com/minio/console/pkg/utils"
subnet "github.com/minio/console/pkg/subnet"
"github.com/minio/madmin-go/v3"
mc "github.com/minio/mc/cmd"
"github.com/minio/websocket"
)
// startHealthInfo starts fetching mc.ServerHealthInfo and
// sends messages with the corresponding data on the websocket connection
func startHealthInfo(ctx context.Context, conn WSConn, client MinioAdmin, deadline *time.Duration) error {
if deadline == nil {
return errors.New("duration can't be nil on startHealthInfo")
}
// Fetch info of all servers (cluster or single server)
healthDataTypes := []madmin.HealthDataType{
madmin.HealthDataTypeMinioInfo,
madmin.HealthDataTypeMinioConfig,
madmin.HealthDataTypeSysCPU,
madmin.HealthDataTypeSysDriveHw,
madmin.HealthDataTypeSysDocker,
madmin.HealthDataTypeSysOsInfo,
madmin.HealthDataTypeSysLoad,
madmin.HealthDataTypeSysMem,
madmin.HealthDataTypeSysNet,
madmin.HealthDataTypeSysProcess,
}
var err error
// Fetch info of all servers (cluster or single server)
healthInfo, version, err := client.serverHealthInfo(ctx, healthDataTypes, *deadline)
if err != nil {
return err
}
compressedDiag, err := mc.TarGZHealthInfo(healthInfo, version)
if err != nil {
return err
}
encodedDiag := b64.StdEncoding.EncodeToString(compressedDiag)
type messageReport struct {
Encoded string `json:"encoded"`
ServerHealthInfo interface{} `json:"serverHealthInfo"`
SubnetResponse string `json:"subnetResponse"`
}
ctx = context.WithValue(ctx, utils.ContextClientIP, conn.remoteAddress())
err = sendHealthInfoToSubnet(ctx, healthInfo, client)
report := messageReport{
Encoded: encodedDiag,
ServerHealthInfo: healthInfo,
SubnetResponse: mc.SubnetBaseURL() + "/health",
}
if err != nil {
report.SubnetResponse = fmt.Sprintf("Error: %s", err.Error())
}
message, err := json.Marshal(report)
if err != nil {
return err
}
// Send Message through websocket connection
return conn.writeMessage(websocket.TextMessage, message)
}
// getHealthInfoOptionsFromReq gets duration for startHealthInfo request
// path come as : `/health-info?deadline=2h`
func getHealthInfoOptionsFromReq(req *http.Request) (*time.Duration, error) {
deadlineDuration, err := time.ParseDuration(req.FormValue("deadline"))
if err != nil {
return nil, err
}
return &deadlineDuration, nil
}
func updateMcGlobals(subnetTokenConfig subnet.LicenseTokenConfig) error {
mc.GlobalDevMode = getConsoleDevMode()
if len(subnetTokenConfig.Proxy) > 0 {
proxyURL, e := url.Parse(subnetTokenConfig.Proxy)
if e != nil {
return e
}
mc.GlobalSubnetProxyURL = proxyURL
}
return nil
}
func sendHealthInfoToSubnet(ctx context.Context, healthInfo interface{}, client MinioAdmin) error {
filename := fmt.Sprintf("health_%d.json.gz", time.Now().Unix())
subnetTokenConfig, e := GetSubnetKeyFromMinIOConfig(ctx, client)
if e != nil {
return e
}
e = updateMcGlobals(*subnetTokenConfig)
if e != nil {
return e
}
var apiKey string
if len(subnetTokenConfig.APIKey) != 0 {
apiKey = subnetTokenConfig.APIKey
} else {
apiKey, e = subnet.GetSubnetAPIKeyUsingLicense(subnetTokenConfig.License)
if e != nil {
return e
}
}
compressedHealthInfo, e := mc.TarGZHealthInfo(healthInfo, madmin.HealthInfoVersion)
if e != nil {
return e
}
e = os.WriteFile(filename, compressedHealthInfo, 0o666)
if e != nil {
return e
}
headers := mc.SubnetAPIKeyAuthHeaders(apiKey)
resp, e := (&mc.SubnetFileUploader{
FilePath: filename,
ReqURL: mc.SubnetUploadURL("health"),
Headers: headers,
DeleteAfterUpload: true,
}).UploadFileToSubnet()
if e != nil {
// file gets deleted only if upload is successful
// so we delete explicitly here as we already have the bytes
logger.LogIf(ctx, os.Remove(filename))
return e
}
type SubnetResponse struct {
LicenseV2 string `json:"license_v2,omitempty"`
APIKey string `json:"api_key,omitempty"`
}
var subnetResp SubnetResponse
e = json.Unmarshal([]byte(resp), &subnetResp)
if e != nil {
return e
}
return nil
}

View File

@@ -1,147 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"encoding/json"
"errors"
"reflect"
"testing"
"time"
madmin "github.com/minio/madmin-go/v3"
)
func Test_serverHealthInfo(t *testing.T) {
var testReceiver chan madmin.HealthInfo
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
client := AdminClientMock{}
mockWSConn := mockConn{}
deadlineDuration, _ := time.ParseDuration("1h")
type args struct {
deadline time.Duration
wsWriteMock func(messageType int, data []byte) error
mockMessages []madmin.HealthInfo
}
tests := []struct {
test string
args args
wantError error
}{
{
test: "Return simple health info, no errors",
args: args{
deadline: deadlineDuration,
mockMessages: []madmin.HealthInfo{{}, {}},
wsWriteMock: func(_ int, data []byte) error {
// mock connection WriteMessage() no error
// emulate that receiver gets the message written
var t madmin.HealthInfo
_ = json.Unmarshal(data, &t)
testReceiver <- t
return nil
},
},
wantError: nil,
},
{
test: "Return simple health info2, no errors",
args: args{
deadline: deadlineDuration,
mockMessages: []madmin.HealthInfo{{}},
wsWriteMock: func(_ int, data []byte) error {
// mock connection WriteMessage() no error
// emulate that receiver gets the message written
var t madmin.HealthInfo
_ = json.Unmarshal(data, &t)
testReceiver <- t
return nil
},
},
wantError: nil,
},
{
test: "Handle error on ws write",
args: args{
deadline: deadlineDuration,
mockMessages: []madmin.HealthInfo{{}},
wsWriteMock: func(_ int, data []byte) error {
// mock connection WriteMessage() no error
// emulate that receiver gets the message written
var t madmin.HealthInfo
_ = json.Unmarshal(data, &t)
return errors.New("error on write")
},
},
wantError: errors.New("error on write"),
},
{
test: "Handle error on health function",
args: args{
deadline: deadlineDuration,
mockMessages: []madmin.HealthInfo{
{
Error: "error on healthInfo",
},
},
wsWriteMock: func(_ int, data []byte) error {
// mock connection WriteMessage() no error
// emulate that receiver gets the message written
var t madmin.HealthInfo
_ = json.Unmarshal(data, &t)
return nil
},
},
wantError: nil,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.test, func(_ *testing.T) {
// make testReceiver channel
testReceiver = make(chan madmin.HealthInfo, len(tt.args.mockMessages))
// mock function same for all tests, changes mockMessages
minioServerHealthInfoMock = func(_ context.Context, _ []madmin.HealthDataType,
_ time.Duration,
) (interface{}, string, error) {
info := tt.args.mockMessages[0]
return info, madmin.HealthInfoVersion, nil
}
connWriteMessageMock = tt.args.wsWriteMock
err := startHealthInfo(ctx, mockWSConn, client, &deadlineDuration)
// close test mock channel
close(testReceiver)
// check that the TestReceiver got the same number of data from Console.
index := 0
for info := range testReceiver {
if !reflect.DeepEqual(info, tt.args.mockMessages[index]) {
t.Errorf("startHealthInfo() got: %v, want: %v", info, tt.args.mockMessages[index])
return
}
index++
}
if !reflect.DeepEqual(err, tt.wantError) {
t.Errorf("startHealthInfo() error: %v, wantError: %v", err, tt.wantError)
return
}
})
}
}

View File

@@ -1,290 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package api
import (
"context"
"fmt"
"time"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
"github.com/minio/console/api/operations/idp"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
)
var errInvalidIDPType = fmt.Errorf("IDP type must be one of %v", madmin.ValidIDPConfigTypes)
func registerIDPHandlers(api *operations.ConsoleAPI) {
api.IdpCreateConfigurationHandler = idp.CreateConfigurationHandlerFunc(func(params idp.CreateConfigurationParams, session *models.Principal) middleware.Responder {
response, err := createIDPConfigurationResponse(session, params)
if err != nil {
return idp.NewCreateConfigurationDefault(err.Code).WithPayload(err.APIError)
}
return idp.NewCreateConfigurationCreated().WithPayload(response)
})
api.IdpUpdateConfigurationHandler = idp.UpdateConfigurationHandlerFunc(func(params idp.UpdateConfigurationParams, session *models.Principal) middleware.Responder {
response, err := updateIDPConfigurationResponse(session, params)
if err != nil {
return idp.NewUpdateConfigurationDefault(err.Code).WithPayload(err.APIError)
}
return idp.NewUpdateConfigurationOK().WithPayload(response)
})
api.IdpListConfigurationsHandler = idp.ListConfigurationsHandlerFunc(func(params idp.ListConfigurationsParams, session *models.Principal) middleware.Responder {
response, err := listIDPConfigurationsResponse(session, params)
if err != nil {
return idp.NewListConfigurationsDefault(err.Code).WithPayload(err.APIError)
}
return idp.NewListConfigurationsOK().WithPayload(response)
})
api.IdpDeleteConfigurationHandler = idp.DeleteConfigurationHandlerFunc(func(params idp.DeleteConfigurationParams, session *models.Principal) middleware.Responder {
response, err := deleteIDPConfigurationResponse(session, params)
if err != nil {
return idp.NewDeleteConfigurationDefault(err.Code).WithPayload(err.APIError)
}
return idp.NewDeleteConfigurationOK().WithPayload(response)
})
api.IdpGetConfigurationHandler = idp.GetConfigurationHandlerFunc(func(params idp.GetConfigurationParams, session *models.Principal) middleware.Responder {
response, err := getIDPConfigurationsResponse(session, params)
if err != nil {
return idp.NewGetConfigurationDefault(err.Code).WithPayload(err.APIError)
}
return idp.NewGetConfigurationOK().WithPayload(response)
})
api.IdpGetLDAPEntitiesHandler = idp.GetLDAPEntitiesHandlerFunc(func(params idp.GetLDAPEntitiesParams, session *models.Principal) middleware.Responder {
response, err := getLDAPEntitiesResponse(session, params)
if err != nil {
return idp.NewGetLDAPEntitiesDefault(err.Code).WithPayload(err.APIError)
}
return idp.NewGetLDAPEntitiesOK().WithPayload(response)
})
}
func createIDPConfigurationResponse(session *models.Principal, params idp.CreateConfigurationParams) (*models.SetIDPResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
restart, err := createOrUpdateIDPConfig(ctx, params.Type, params.Body.Name, params.Body.Input, false, AdminClient{Client: mAdmin})
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.SetIDPResponse{Restart: restart}, nil
}
func updateIDPConfigurationResponse(session *models.Principal, params idp.UpdateConfigurationParams) (*models.SetIDPResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
restart, err := createOrUpdateIDPConfig(ctx, params.Type, params.Name, params.Body.Input, true, AdminClient{Client: mAdmin})
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.SetIDPResponse{Restart: restart}, nil
}
func createOrUpdateIDPConfig(ctx context.Context, idpType, name, input string, update bool, client MinioAdmin) (bool, error) {
if !madmin.ValidIDPConfigTypes.Contains(idpType) {
return false, errInvalidIDPType
}
restart, err := client.addOrUpdateIDPConfig(ctx, idpType, name, input, update)
if err != nil {
return false, err
}
return restart, nil
}
func listIDPConfigurationsResponse(session *models.Principal, params idp.ListConfigurationsParams) (*models.IdpListConfigurationsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
results, err := listIDPConfigurations(ctx, params.Type, AdminClient{Client: mAdmin})
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.IdpListConfigurationsResponse{Results: results}, nil
}
func listIDPConfigurations(ctx context.Context, idpType string, client MinioAdmin) ([]*models.IdpServerConfiguration, error) {
if !madmin.ValidIDPConfigTypes.Contains(idpType) {
return nil, errInvalidIDPType
}
results, err := client.listIDPConfig(ctx, idpType)
if err != nil {
return nil, err
}
return parseIDPConfigurations(results), nil
}
func parseIDPConfigurations(configs []madmin.IDPListItem) (serverConfigs []*models.IdpServerConfiguration) {
for _, c := range configs {
serverConfigs = append(serverConfigs, &models.IdpServerConfiguration{
Name: c.Name,
Enabled: c.Enabled,
Type: c.Type,
})
}
return serverConfigs
}
func deleteIDPConfigurationResponse(session *models.Principal, params idp.DeleteConfigurationParams) (*models.SetIDPResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
restart, err := deleteIDPConfig(ctx, params.Type, params.Name, AdminClient{Client: mAdmin})
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.SetIDPResponse{Restart: restart}, nil
}
func deleteIDPConfig(ctx context.Context, idpType, name string, client MinioAdmin) (bool, error) {
if !madmin.ValidIDPConfigTypes.Contains(idpType) {
return false, errInvalidIDPType
}
restart, err := client.deleteIDPConfig(ctx, idpType, name)
if err != nil {
return false, err
}
return restart, nil
}
func getIDPConfigurationsResponse(session *models.Principal, params idp.GetConfigurationParams) (*models.IdpServerConfiguration, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
result, err := getIDPConfiguration(ctx, params.Type, params.Name, AdminClient{Client: mAdmin})
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return result, nil
}
func getIDPConfiguration(ctx context.Context, idpType, name string, client MinioAdmin) (*models.IdpServerConfiguration, error) {
if !madmin.ValidIDPConfigTypes.Contains(idpType) {
return nil, errInvalidIDPType
}
config, err := client.getIDPConfig(ctx, idpType, name)
if err != nil {
return nil, err
}
return &models.IdpServerConfiguration{
Name: config.Name,
Type: config.Type,
Info: parseIDPConfigurationsInfo(config.Info),
}, nil
}
func parseIDPConfigurationsInfo(infoList []madmin.IDPCfgInfo) (results []*models.IdpServerConfigurationInfo) {
for _, info := range infoList {
results = append(results, &models.IdpServerConfigurationInfo{
Key: info.Key,
Value: info.Value,
IsCfg: info.IsCfg,
IsEnv: info.IsEnv,
})
}
return results
}
func getLDAPEntitiesResponse(session *models.Principal, params idp.GetLDAPEntitiesParams) (*models.LdapEntities, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
result, err := getEntitiesResult(ctx, AdminClient{Client: mAdmin}, params.Body.Users, params.Body.Groups, params.Body.Policies)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return result, nil
}
func getEntitiesResult(ctx context.Context, client MinioAdmin, users, groups, policies []string) (*models.LdapEntities, error) {
entities, err := client.getLDAPPolicyEntities(ctx, madmin.PolicyEntitiesQuery{
Users: users,
Groups: groups,
Policy: policies,
})
if err != nil {
return nil, err
}
var result models.LdapEntities
var usersEntity []*models.LdapUserPolicyEntity
var groupsEntity []*models.LdapGroupPolicyEntity
var policiesEntity []*models.LdapPolicyEntity
result.Timestamp = entities.Timestamp.Format(time.RFC3339)
for _, userMapping := range entities.UserMappings {
mapItem := models.LdapUserPolicyEntity{
User: userMapping.User,
Policies: userMapping.Policies,
}
usersEntity = append(usersEntity, &mapItem)
}
result.Users = usersEntity
for _, groupsMapping := range entities.GroupMappings {
mapItem := models.LdapGroupPolicyEntity{
Group: groupsMapping.Group,
Policies: groupsMapping.Policies,
}
groupsEntity = append(groupsEntity, &mapItem)
}
result.Groups = groupsEntity
for _, policyMapping := range entities.PolicyMappings {
mapItem := models.LdapPolicyEntity{
Policy: policyMapping.Policy,
Users: policyMapping.Users,
Groups: policyMapping.Groups,
}
policiesEntity = append(policiesEntity, &mapItem)
}
result.Policies = policiesEntity
return &result, nil
}

View File

@@ -1,319 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/minio/madmin-go/v3"
"github.com/minio/console/api/operations"
"github.com/minio/console/api/operations/idp"
"github.com/minio/console/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type IDPTestSuite struct {
suite.Suite
assert *assert.Assertions
currentServer string
isServerSet bool
server *httptest.Server
adminClient AdminClientMock
}
func (suite *IDPTestSuite) SetupSuite() {
suite.assert = assert.New(suite.T())
suite.adminClient = AdminClientMock{}
minioServiceRestartMock = func(_ context.Context) error {
return nil
}
}
func (suite *IDPTestSuite) SetupTest() {
suite.server = httptest.NewServer(http.HandlerFunc(suite.serverHandler))
suite.currentServer, suite.isServerSet = os.LookupEnv(ConsoleMinIOServer)
os.Setenv(ConsoleMinIOServer, suite.server.URL)
}
func (suite *IDPTestSuite) serverHandler(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(400)
}
func (suite *IDPTestSuite) TearDownSuite() {
}
func (suite *IDPTestSuite) TearDownTest() {
if suite.isServerSet {
os.Setenv(ConsoleMinIOServer, suite.currentServer)
} else {
os.Unsetenv(ConsoleMinIOServer)
}
}
func (suite *IDPTestSuite) TestRegisterIDPHandlers() {
api := &operations.ConsoleAPI{}
suite.assertHandlersAreNil(api)
registerIDPHandlers(api)
suite.assertHandlersAreNotNil(api)
}
func (suite *IDPTestSuite) assertHandlersAreNil(api *operations.ConsoleAPI) {
suite.assert.Nil(api.IdpCreateConfigurationHandler)
suite.assert.Nil(api.IdpListConfigurationsHandler)
suite.assert.Nil(api.IdpUpdateConfigurationHandler)
suite.assert.Nil(api.IdpGetConfigurationHandler)
suite.assert.Nil(api.IdpGetConfigurationHandler)
suite.assert.Nil(api.IdpDeleteConfigurationHandler)
}
func (suite *IDPTestSuite) assertHandlersAreNotNil(api *operations.ConsoleAPI) {
suite.assert.NotNil(api.IdpCreateConfigurationHandler)
suite.assert.NotNil(api.IdpListConfigurationsHandler)
suite.assert.NotNil(api.IdpUpdateConfigurationHandler)
suite.assert.NotNil(api.IdpGetConfigurationHandler)
suite.assert.NotNil(api.IdpGetConfigurationHandler)
suite.assert.NotNil(api.IdpDeleteConfigurationHandler)
}
func (suite *IDPTestSuite) TestCreateIDPConfigurationHandlerWithError() {
params, api := suite.initCreateIDPConfigurationRequest()
response := api.IdpCreateConfigurationHandler.Handle(params, &models.Principal{})
_, ok := response.(*idp.CreateConfigurationDefault)
suite.assert.True(ok)
}
func (suite *IDPTestSuite) initCreateIDPConfigurationRequest() (params idp.CreateConfigurationParams, api operations.ConsoleAPI) {
registerIDPHandlers(&api)
params.HTTPRequest = &http.Request{}
params.Body = &models.IdpServerConfiguration{}
params.Type = "ldap"
return params, api
}
func (suite *IDPTestSuite) TestCreateIDPConfigurationWithoutError() {
ctx := context.Background()
_, err := createOrUpdateIDPConfig(ctx, "ldap", "", "", false, suite.adminClient)
suite.assert.Nil(err)
}
func (suite *IDPTestSuite) TestCreateIDPConfigurationWithWrongType() {
ctx := context.Background()
_, err := createOrUpdateIDPConfig(ctx, "", "", "", false, suite.adminClient)
suite.assert.NotNil(err)
}
func (suite *IDPTestSuite) TestUpdateIDPConfigurationHandlerWithError() {
params, api := suite.initUpdateIDPConfigurationRequest()
response := api.IdpUpdateConfigurationHandler.Handle(params, &models.Principal{})
_, ok := response.(*idp.UpdateConfigurationDefault)
suite.assert.True(ok)
}
func (suite *IDPTestSuite) initUpdateIDPConfigurationRequest() (params idp.UpdateConfigurationParams, api operations.ConsoleAPI) {
registerIDPHandlers(&api)
params.HTTPRequest = &http.Request{}
params.Body = &models.IdpServerConfiguration{}
params.Type = "ldap"
return params, api
}
func (suite *IDPTestSuite) TestUpdateIDPConfigurationWithoutError() {
ctx := context.Background()
_, err := createOrUpdateIDPConfig(ctx, "ldap", "", "", true, suite.adminClient)
suite.assert.Nil(err)
}
func (suite *IDPTestSuite) TestUpdateIDPConfigurationWithWrongType() {
ctx := context.Background()
_, err := createOrUpdateIDPConfig(ctx, "", "", "", true, suite.adminClient)
suite.assert.NotNil(err)
}
func (suite *IDPTestSuite) TestListIDPConfigurationHandlerWithError() {
params, api := suite.initListIDPConfigurationsRequest()
response := api.IdpListConfigurationsHandler.Handle(params, &models.Principal{})
_, ok := response.(*idp.ListConfigurationsDefault)
suite.assert.True(ok)
}
func (suite *IDPTestSuite) initListIDPConfigurationsRequest() (params idp.ListConfigurationsParams, api operations.ConsoleAPI) {
registerIDPHandlers(&api)
params.HTTPRequest = &http.Request{}
params.Type = "ldap"
return params, api
}
func (suite *IDPTestSuite) TestListIDPConfigurationsWithoutError() {
ctx := context.Background()
res, err := listIDPConfigurations(ctx, "ldap", suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *IDPTestSuite) TestListIDPConfigurationsWithWrongType() {
ctx := context.Background()
res, err := listIDPConfigurations(ctx, "", suite.adminClient)
suite.assert.Nil(res)
suite.assert.NotNil(err)
}
func (suite *IDPTestSuite) TestDeleteIDPConfigurationHandlerWithError() {
params, api := suite.initDeleteIDPConfigurationRequest()
response := api.IdpDeleteConfigurationHandler.Handle(params, &models.Principal{})
_, ok := response.(*idp.DeleteConfigurationDefault)
suite.assert.True(ok)
}
func (suite *IDPTestSuite) initDeleteIDPConfigurationRequest() (params idp.DeleteConfigurationParams, api operations.ConsoleAPI) {
registerIDPHandlers(&api)
params.HTTPRequest = &http.Request{}
params.Type = "ldap"
return params, api
}
func (suite *IDPTestSuite) TestDeleteIDPConfigurationWithoutError() {
ctx := context.Background()
_, err := deleteIDPConfig(ctx, "ldap", "", suite.adminClient)
suite.assert.Nil(err)
}
func (suite *IDPTestSuite) TestDeleteIDPConfigurationWithWrongType() {
ctx := context.Background()
_, err := deleteIDPConfig(ctx, "", "", suite.adminClient)
suite.assert.NotNil(err)
}
func (suite *IDPTestSuite) TestGetIDPConfigurationHandlerWithError() {
params, api := suite.initGetIDPConfigurationRequest()
response := api.IdpGetConfigurationHandler.Handle(params, &models.Principal{})
_, ok := response.(*idp.GetConfigurationDefault)
suite.assert.True(ok)
}
func (suite *IDPTestSuite) initGetIDPConfigurationRequest() (params idp.GetConfigurationParams, api operations.ConsoleAPI) {
registerIDPHandlers(&api)
params.HTTPRequest = &http.Request{}
params.Type = "ldap"
return params, api
}
func (suite *IDPTestSuite) TestGetIDPConfigurationWithoutError() {
ctx := context.Background()
res, err := getIDPConfiguration(ctx, "ldap", "", suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *IDPTestSuite) TestGetIDPConfigurationWithWrongType() {
ctx := context.Background()
res, err := getIDPConfiguration(ctx, "", "", suite.adminClient)
suite.assert.Nil(res)
suite.assert.NotNil(err)
}
func TestIDP(t *testing.T) {
suite.Run(t, new(IDPTestSuite))
}
func TestGetEntitiesResult(t *testing.T) {
assert := assert.New(t)
// mock minIO client
client := AdminClientMock{}
function := "getEntitiesResult()"
usersList := []string{"user1", "user2", "user3"}
policiesList := []string{"policy1", "policy2", "policy3"}
groupsList := []string{"group1", "group3", "group5"}
policyMap := []madmin.PolicyEntities{
{Policy: "testPolicy0", Groups: groupsList, Users: usersList},
{Policy: "testPolicy1", Groups: groupsList, Users: usersList},
}
usersMap := []madmin.UserPolicyEntities{
{User: "testUser0", Policies: policiesList},
{User: "testUser1", Policies: policiesList},
}
groupsMap := []madmin.GroupPolicyEntities{
{Group: "group0", Policies: policiesList},
{Group: "group1", Policies: policiesList},
}
// Test-1: getEntitiesResult list all information provided
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mockResponse := madmin.PolicyEntitiesResult{
PolicyMappings: policyMap,
GroupMappings: groupsMap,
UserMappings: usersMap,
}
minioGetLDAPPolicyEntitiesMock = func(_ context.Context, _ madmin.PolicyEntitiesQuery) (madmin.PolicyEntitiesResult, error) {
return mockResponse, nil
}
entities, err := getEntitiesResult(ctx, client, usersList, groupsList, policiesList)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
for i, groupIt := range entities.Groups {
assert.Equal(fmt.Sprintf("group%d", i), groupIt.Group)
for i, polItm := range groupIt.Policies {
assert.Equal(policiesList[i], polItm)
}
}
for i, usrIt := range entities.Users {
assert.Equal(fmt.Sprintf("testUser%d", i), usrIt.User)
for i, polItm := range usrIt.Policies {
assert.Equal(policiesList[i], polItm)
}
}
for i, policyIt := range entities.Policies {
assert.Equal(fmt.Sprintf("testPolicy%d", i), policyIt.Policy)
for i, userItm := range policyIt.Users {
assert.Equal(usersList[i], userItm)
}
for i, grItm := range policyIt.Groups {
assert.Equal(groupsList[i], grItm)
}
}
// Test-2: getEntitiesResult error is returned from getLDAPPolicyEntities()
minioGetLDAPPolicyEntitiesMock = func(_ context.Context, _ madmin.PolicyEntitiesQuery) (madmin.PolicyEntitiesResult, error) {
return madmin.PolicyEntitiesResult{}, errors.New("error")
}
_, err = getEntitiesResult(ctx, client, usersList, groupsList, policiesList)
if assert.Error(err) {
assert.Equal("error", err.Error())
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,152 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/minio/console/pkg/utils"
"github.com/minio/console/api/operations"
systemApi "github.com/minio/console/api/operations/system"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type AdminInfoTestSuite struct {
suite.Suite
assert *assert.Assertions
currentServer string
isServerSet bool
isPrometheusRequest bool
server *httptest.Server
adminClient AdminClientMock
}
func (suite *AdminInfoTestSuite) SetupSuite() {
suite.assert = assert.New(suite.T())
suite.adminClient = AdminClientMock{}
MinioServerInfoMock = func(_ context.Context) (madmin.InfoMessage, error) {
return madmin.InfoMessage{
Servers: []madmin.ServerProperties{{
Disks: []madmin.Disk{{}},
}},
Backend: madmin.ErasureBackend{Type: "mock"},
}, nil
}
}
func (suite *AdminInfoTestSuite) SetupTest() {
suite.server = httptest.NewServer(http.HandlerFunc(suite.serverHandler))
suite.currentServer, suite.isServerSet = os.LookupEnv(ConsoleMinIOServer)
os.Setenv(ConsoleMinIOServer, suite.server.URL)
}
func (suite *AdminInfoTestSuite) serverHandler(w http.ResponseWriter, _ *http.Request) {
if suite.isPrometheusRequest {
w.WriteHeader(200)
} else {
w.WriteHeader(400)
}
}
func (suite *AdminInfoTestSuite) TearDownSuite() {
}
func (suite *AdminInfoTestSuite) TearDownTest() {
if suite.isServerSet {
os.Setenv(ConsoleMinIOServer, suite.currentServer)
} else {
os.Unsetenv(ConsoleMinIOServer)
}
}
func (suite *AdminInfoTestSuite) TestRegisterAdminInfoHandlers() {
api := &operations.ConsoleAPI{}
suite.assertHandlersAreNil(api)
registerAdminInfoHandlers(api)
suite.assertHandlersAreNotNil(api)
}
func (suite *AdminInfoTestSuite) assertHandlersAreNil(api *operations.ConsoleAPI) {
suite.assert.Nil(api.SystemAdminInfoHandler)
suite.assert.Nil(api.SystemDashboardWidgetDetailsHandler)
}
func (suite *AdminInfoTestSuite) assertHandlersAreNotNil(api *operations.ConsoleAPI) {
suite.assert.NotNil(api.SystemAdminInfoHandler)
suite.assert.NotNil(api.SystemDashboardWidgetDetailsHandler)
}
func (suite *AdminInfoTestSuite) TestSystemAdminInfoHandlerWithError() {
params, api := suite.initSystemAdminInfoRequest()
response := api.SystemAdminInfoHandler.Handle(params, &models.Principal{})
_, ok := response.(*systemApi.AdminInfoDefault)
suite.assert.True(ok)
}
func (suite *AdminInfoTestSuite) initSystemAdminInfoRequest() (params systemApi.AdminInfoParams, api operations.ConsoleAPI) {
registerAdminInfoHandlers(&api)
params.HTTPRequest = &http.Request{}
defaultOnly := false
params.DefaultOnly = &defaultOnly
return params, api
}
func (suite *AdminInfoTestSuite) TestSystemDashboardWidgetDetailsHandlerWithError() {
params, api := suite.initSystemDashboardWidgetDetailsRequest()
response := api.SystemDashboardWidgetDetailsHandler.Handle(params, &models.Principal{})
_, ok := response.(*systemApi.DashboardWidgetDetailsDefault)
suite.assert.True(ok)
}
func (suite *AdminInfoTestSuite) initSystemDashboardWidgetDetailsRequest() (params systemApi.DashboardWidgetDetailsParams, api operations.ConsoleAPI) {
registerAdminInfoHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *AdminInfoTestSuite) TestGetUsageWidgetsForDeploymentWithoutError() {
ctx := context.WithValue(context.Background(), utils.ContextClientIP, "127.0.0.1")
suite.isPrometheusRequest = true
res, err := getUsageWidgetsForDeployment(ctx, suite.server.URL, suite.adminClient)
suite.assert.Nil(err)
suite.assert.NotNil(res)
suite.isPrometheusRequest = false
}
func (suite *AdminInfoTestSuite) TestGetWidgetDetailsWithoutError() {
ctx := context.WithValue(context.Background(), utils.ContextClientIP, "127.0.0.1")
suite.isPrometheusRequest = true
var step int32 = 1
var start int64
var end int64 = 1
res, err := getWidgetDetails(ctx, suite.server.URL, "mock", 1, &step, &start, &end)
suite.assert.Nil(err)
suite.assert.NotNil(res)
suite.isPrometheusRequest = false
}
func TestAdminInfo(t *testing.T) {
suite.Run(t, new(AdminInfoTestSuite))
}

View File

@@ -1,126 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"encoding/base64"
"fmt"
"io"
"net/http"
"strings"
"unicode/utf8"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
inspectApi "github.com/minio/console/api/operations/inspect"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
"github.com/secure-io/sio-go"
)
func registerInspectHandler(api *operations.ConsoleAPI) {
api.InspectInspectHandler = inspectApi.InspectHandlerFunc(func(params inspectApi.InspectParams, principal *models.Principal) middleware.Responder {
if v, err := base64.URLEncoding.DecodeString(params.File); err == nil && utf8.Valid(v) {
params.File = string(v)
}
if v, err := base64.URLEncoding.DecodeString(params.Volume); err == nil && utf8.Valid(v) {
params.Volume = string(v)
}
k, r, err := getInspectResult(principal, &params)
if err != nil {
return inspectApi.NewInspectDefault(err.Code).WithPayload(err.APIError)
}
return middleware.ResponderFunc(processInspectResponse(&params, k, r))
})
}
func getInspectResult(session *models.Principal, params *inspectApi.InspectParams) ([]byte, io.ReadCloser, *CodedAPIError) {
ctx := params.HTTPRequest.Context()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, nil, ErrorWithContext(ctx, err)
}
cfg := madmin.InspectOptions{
File: params.File,
Volume: params.Volume,
}
// TODO: Remove encryption option and always encrypt.
// Maybe also add public key field.
if params.Encrypt != nil && *params.Encrypt {
cfg.PublicKey, _ = base64.StdEncoding.DecodeString("MIIBCgKCAQEAs/128UFS9A8YSJY1XqYKt06dLVQQCGDee69T+0Tip/1jGAB4z0/3QMpH0MiS8Wjs4BRWV51qvkfAHzwwdU7y6jxU05ctb/H/WzRj3FYdhhHKdzear9TLJftlTs+xwj2XaADjbLXCV1jGLS889A7f7z5DgABlVZMQd9BjVAR8ED3xRJ2/ZCNuQVJ+A8r7TYPGMY3wWvhhPgPk3Lx4WDZxDiDNlFs4GQSaESSsiVTb9vyGe/94CsCTM6Cw9QG6ifHKCa/rFszPYdKCabAfHcS3eTr0GM+TThSsxO7KfuscbmLJkfQev1srfL2Ii2RbnysqIJVWKEwdW05ID8ryPkuTuwIDAQAB")
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
k, r, err := adminClient.inspect(ctx, cfg)
if err != nil {
return nil, nil, ErrorWithContext(ctx, err)
}
return k, r, nil
}
// borrowed from mc cli
func decryptInspectV1(key [32]byte, r io.Reader) io.ReadCloser {
stream, err := sio.AES_256_GCM.Stream(key[:])
if err != nil {
return nil
}
nonce := make([]byte, stream.NonceSize())
return io.NopCloser(stream.DecryptReader(r, nonce, nil))
}
func processInspectResponse(params *inspectApi.InspectParams, k []byte, r io.ReadCloser) func(w http.ResponseWriter, _ runtime.Producer) {
isEnc := params.Encrypt != nil && *params.Encrypt
return func(w http.ResponseWriter, _ runtime.Producer) {
ext := "enc"
if len(k) == 32 && !isEnc {
ext = "zip"
r = decryptInspectV1(*(*[32]byte)(k), r)
}
fileName := fmt.Sprintf("inspect-%s-%s.%s", params.Volume, params.File, ext)
fileName = strings.Map(func(r rune) rune {
switch {
case r >= 'A' && r <= 'Z':
return r
case r >= 'a' && r <= 'z':
return r
case r >= '0' && r <= '9':
return r
default:
if strings.ContainsAny(string(r), "-+._") {
return r
}
return '_'
}
}, fileName)
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName))
_, err := io.Copy(w, r)
if err != nil {
LogError("unable to write all the data: %v", err)
}
}
}

View File

@@ -1,669 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package api
import (
"context"
"encoding/json"
"sort"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
kmsAPI "github.com/minio/console/api/operations/k_m_s"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
)
func registerKMSHandlers(api *operations.ConsoleAPI) {
registerKMSStatusHandlers(api)
registerKMSKeyHandlers(api)
registerKMSPolicyHandlers(api)
registerKMSIdentityHandlers(api)
}
func registerKMSStatusHandlers(api *operations.ConsoleAPI) {
api.KmsKMSStatusHandler = kmsAPI.KMSStatusHandlerFunc(func(params kmsAPI.KMSStatusParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSStatusResponse(session, params)
if err != nil {
return kmsAPI.NewKMSStatusDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSStatusOK().WithPayload(resp)
})
api.KmsKMSMetricsHandler = kmsAPI.KMSMetricsHandlerFunc(func(params kmsAPI.KMSMetricsParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSMetricsResponse(session, params)
if err != nil {
return kmsAPI.NewKMSMetricsDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSMetricsOK().WithPayload(resp)
})
api.KmsKMSAPIsHandler = kmsAPI.KMSAPIsHandlerFunc(func(params kmsAPI.KMSAPIsParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSAPIsResponse(session, params)
if err != nil {
return kmsAPI.NewKMSAPIsDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSAPIsOK().WithPayload(resp)
})
api.KmsKMSVersionHandler = kmsAPI.KMSVersionHandlerFunc(func(params kmsAPI.KMSVersionParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSVersionResponse(session, params)
if err != nil {
return kmsAPI.NewKMSVersionDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSVersionOK().WithPayload(resp)
})
}
func GetKMSStatusResponse(session *models.Principal, params kmsAPI.KMSStatusParams) (*models.KmsStatusResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return kmsStatus(ctx, AdminClient{Client: mAdmin})
}
func kmsStatus(ctx context.Context, minioClient MinioAdmin) (*models.KmsStatusResponse, *CodedAPIError) {
st, err := minioClient.kmsStatus(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.KmsStatusResponse{
DefaultKeyID: st.DefaultKeyID,
Name: st.Name,
Endpoints: parseStatusEndpoints(st.Endpoints),
}, nil
}
func parseStatusEndpoints(endpoints map[string]madmin.ItemState) (kmsEndpoints []*models.KmsEndpoint) {
for key, value := range endpoints {
kmsEndpoints = append(kmsEndpoints, &models.KmsEndpoint{URL: key, Status: string(value)})
}
return kmsEndpoints
}
func GetKMSMetricsResponse(session *models.Principal, params kmsAPI.KMSMetricsParams) (*models.KmsMetricsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return kmsMetrics(ctx, AdminClient{Client: mAdmin})
}
func kmsMetrics(ctx context.Context, minioClient MinioAdmin) (*models.KmsMetricsResponse, *CodedAPIError) {
metrics, err := minioClient.kmsMetrics(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.KmsMetricsResponse{
RequestOK: &metrics.RequestOK,
RequestErr: &metrics.RequestErr,
RequestFail: &metrics.RequestFail,
RequestActive: &metrics.RequestActive,
AuditEvents: &metrics.AuditEvents,
ErrorEvents: &metrics.ErrorEvents,
LatencyHistogram: parseHistogram(metrics.LatencyHistogram),
Uptime: &metrics.UpTime,
Cpus: &metrics.CPUs,
UsableCPUs: &metrics.UsableCPUs,
Threads: &metrics.Threads,
HeapAlloc: &metrics.HeapAlloc,
HeapObjects: metrics.HeapObjects,
StackAlloc: &metrics.StackAlloc,
}, nil
}
func parseHistogram(histogram map[int64]int64) (records []*models.KmsLatencyHistogram) {
for duration, total := range histogram {
records = append(records, &models.KmsLatencyHistogram{Duration: duration, Total: total})
}
cp := func(i, j int) bool {
return records[i].Duration < records[j].Duration
}
sort.Slice(records, cp)
return records
}
func GetKMSAPIsResponse(session *models.Principal, params kmsAPI.KMSAPIsParams) (*models.KmsAPIsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return kmsAPIs(ctx, AdminClient{Client: mAdmin})
}
func kmsAPIs(ctx context.Context, minioClient MinioAdmin) (*models.KmsAPIsResponse, *CodedAPIError) {
apis, err := minioClient.kmsAPIs(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.KmsAPIsResponse{
Results: parseApis(apis),
}, nil
}
func parseApis(apis []madmin.KMSAPI) (data []*models.KmsAPI) {
for _, api := range apis {
data = append(data, &models.KmsAPI{
Method: api.Method,
Path: api.Path,
MaxBody: api.MaxBody,
Timeout: api.Timeout,
})
}
return data
}
func GetKMSVersionResponse(session *models.Principal, params kmsAPI.KMSVersionParams) (*models.KmsVersionResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return kmsVersion(ctx, AdminClient{Client: mAdmin})
}
func kmsVersion(ctx context.Context, minioClient MinioAdmin) (*models.KmsVersionResponse, *CodedAPIError) {
version, err := minioClient.kmsVersion(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.KmsVersionResponse{
Version: version.Version,
}, nil
}
func registerKMSKeyHandlers(api *operations.ConsoleAPI) {
api.KmsKMSCreateKeyHandler = kmsAPI.KMSCreateKeyHandlerFunc(func(params kmsAPI.KMSCreateKeyParams, session *models.Principal) middleware.Responder {
err := GetKMSCreateKeyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSCreateKeyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSCreateKeyCreated()
})
api.KmsKMSImportKeyHandler = kmsAPI.KMSImportKeyHandlerFunc(func(params kmsAPI.KMSImportKeyParams, session *models.Principal) middleware.Responder {
err := GetKMSImportKeyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSImportKeyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSImportKeyCreated()
})
api.KmsKMSListKeysHandler = kmsAPI.KMSListKeysHandlerFunc(func(params kmsAPI.KMSListKeysParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSListKeysResponse(session, params)
if err != nil {
return kmsAPI.NewKMSListKeysDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSListKeysOK().WithPayload(resp)
})
api.KmsKMSKeyStatusHandler = kmsAPI.KMSKeyStatusHandlerFunc(func(params kmsAPI.KMSKeyStatusParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSKeyStatusResponse(session, params)
if err != nil {
return kmsAPI.NewKMSKeyStatusDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSKeyStatusOK().WithPayload(resp)
})
api.KmsKMSDeleteKeyHandler = kmsAPI.KMSDeleteKeyHandlerFunc(func(params kmsAPI.KMSDeleteKeyParams, session *models.Principal) middleware.Responder {
err := GetKMSDeleteKeyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSDeleteKeyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSDeleteKeyOK()
})
}
func GetKMSCreateKeyResponse(session *models.Principal, params kmsAPI.KMSCreateKeyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
return createKey(ctx, *params.Body.Key, AdminClient{Client: mAdmin})
}
func createKey(ctx context.Context, key string, minioClient MinioAdmin) *CodedAPIError {
if err := minioClient.createKey(ctx, key); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func GetKMSImportKeyResponse(session *models.Principal, params kmsAPI.KMSImportKeyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
bytes, err := json.Marshal(params.Body)
if err != nil {
return ErrorWithContext(ctx, err)
}
return importKey(ctx, params.Name, bytes, AdminClient{Client: mAdmin})
}
func importKey(ctx context.Context, key string, bytes []byte, minioClient MinioAdmin) *CodedAPIError {
if err := minioClient.importKey(ctx, key, bytes); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func GetKMSListKeysResponse(session *models.Principal, params kmsAPI.KMSListKeysParams) (*models.KmsListKeysResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
pattern := ""
if params.Pattern != nil {
pattern = *params.Pattern
}
return listKeys(ctx, pattern, AdminClient{Client: mAdmin})
}
func listKeys(ctx context.Context, pattern string, minioClient MinioAdmin) (*models.KmsListKeysResponse, *CodedAPIError) {
results, err := minioClient.listKeys(ctx, pattern)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.KmsListKeysResponse{Results: parseKeys(results)}, nil
}
func parseKeys(results []madmin.KMSKeyInfo) (data []*models.KmsKeyInfo) {
for _, key := range results {
data = append(data, &models.KmsKeyInfo{
CreatedAt: key.CreatedAt,
CreatedBy: key.CreatedBy,
Name: key.Name,
})
}
return data
}
func GetKMSKeyStatusResponse(session *models.Principal, params kmsAPI.KMSKeyStatusParams) (*models.KmsKeyStatusResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return keyStatus(ctx, params.Name, AdminClient{Client: mAdmin})
}
func keyStatus(ctx context.Context, key string, minioClient MinioAdmin) (*models.KmsKeyStatusResponse, *CodedAPIError) {
ks, err := minioClient.keyStatus(ctx, key)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.KmsKeyStatusResponse{
KeyID: ks.KeyID,
EncryptionErr: ks.EncryptionErr,
DecryptionErr: ks.DecryptionErr,
}, nil
}
func GetKMSDeleteKeyResponse(session *models.Principal, params kmsAPI.KMSDeleteKeyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
return deleteKey(ctx, params.Name, AdminClient{Client: mAdmin})
}
func deleteKey(ctx context.Context, key string, minioClient MinioAdmin) *CodedAPIError {
if err := minioClient.deleteKey(ctx, key); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func registerKMSPolicyHandlers(api *operations.ConsoleAPI) {
api.KmsKMSSetPolicyHandler = kmsAPI.KMSSetPolicyHandlerFunc(func(params kmsAPI.KMSSetPolicyParams, session *models.Principal) middleware.Responder {
err := GetKMSSetPolicyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSSetPolicyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSSetPolicyOK()
})
api.KmsKMSAssignPolicyHandler = kmsAPI.KMSAssignPolicyHandlerFunc(func(params kmsAPI.KMSAssignPolicyParams, session *models.Principal) middleware.Responder {
err := GetKMSAssignPolicyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSAssignPolicyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSAssignPolicyOK()
})
api.KmsKMSDescribePolicyHandler = kmsAPI.KMSDescribePolicyHandlerFunc(func(params kmsAPI.KMSDescribePolicyParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSDescribePolicyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSDescribePolicyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSDescribePolicyOK().WithPayload(resp)
})
api.KmsKMSGetPolicyHandler = kmsAPI.KMSGetPolicyHandlerFunc(func(params kmsAPI.KMSGetPolicyParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSGetPolicyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSGetPolicyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSGetPolicyOK().WithPayload(resp)
})
api.KmsKMSListPoliciesHandler = kmsAPI.KMSListPoliciesHandlerFunc(func(params kmsAPI.KMSListPoliciesParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSListPoliciesResponse(session, params)
if err != nil {
return kmsAPI.NewKMSListPoliciesDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSListPoliciesOK().WithPayload(resp)
})
api.KmsKMSDeletePolicyHandler = kmsAPI.KMSDeletePolicyHandlerFunc(func(params kmsAPI.KMSDeletePolicyParams, session *models.Principal) middleware.Responder {
err := GetKMSDeletePolicyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSDeletePolicyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSDeletePolicyOK()
})
}
func GetKMSSetPolicyResponse(session *models.Principal, params kmsAPI.KMSSetPolicyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
bytes, err := json.Marshal(params.Body)
if err != nil {
return ErrorWithContext(ctx, err)
}
return setPolicy(ctx, *params.Body.Policy, bytes, AdminClient{Client: mAdmin})
}
func setPolicy(ctx context.Context, policy string, content []byte, minioClient MinioAdmin) *CodedAPIError {
if err := minioClient.setKMSPolicy(ctx, policy, content); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func GetKMSAssignPolicyResponse(session *models.Principal, params kmsAPI.KMSAssignPolicyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
bytes, err := json.Marshal(params.Body)
if err != nil {
return ErrorWithContext(ctx, err)
}
return assignPolicy(ctx, params.Name, bytes, AdminClient{Client: mAdmin})
}
func assignPolicy(ctx context.Context, policy string, content []byte, minioClient MinioAdmin) *CodedAPIError {
if err := minioClient.assignPolicy(ctx, policy, content); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func GetKMSDescribePolicyResponse(session *models.Principal, params kmsAPI.KMSDescribePolicyParams) (*models.KmsDescribePolicyResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return describePolicy(ctx, params.Name, AdminClient{Client: mAdmin})
}
func describePolicy(ctx context.Context, policy string, minioClient MinioAdmin) (*models.KmsDescribePolicyResponse, *CodedAPIError) {
dp, err := minioClient.describePolicy(ctx, policy)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.KmsDescribePolicyResponse{
Name: dp.Name,
CreatedAt: dp.CreatedAt,
CreatedBy: dp.CreatedBy,
}, nil
}
func GetKMSGetPolicyResponse(session *models.Principal, params kmsAPI.KMSGetPolicyParams) (*models.KmsGetPolicyResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return getPolicy(ctx, params.Name, AdminClient{Client: mAdmin})
}
func getPolicy(ctx context.Context, policy string, minioClient MinioAdmin) (*models.KmsGetPolicyResponse, *CodedAPIError) {
p, err := minioClient.getKMSPolicy(ctx, policy)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.KmsGetPolicyResponse{
Allow: p.Allow,
Deny: p.Deny,
}, nil
}
func GetKMSListPoliciesResponse(session *models.Principal, params kmsAPI.KMSListPoliciesParams) (*models.KmsListPoliciesResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
pattern := ""
if params.Pattern != nil {
pattern = *params.Pattern
}
return listKMSPolicies(ctx, pattern, AdminClient{Client: mAdmin})
}
func listKMSPolicies(ctx context.Context, pattern string, minioClient MinioAdmin) (*models.KmsListPoliciesResponse, *CodedAPIError) {
results, err := minioClient.listKMSPolicies(ctx, pattern)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.KmsListPoliciesResponse{Results: parsePolicies(results)}, nil
}
func parsePolicies(results []madmin.KMSPolicyInfo) (data []*models.KmsPolicyInfo) {
for _, policy := range results {
data = append(data, &models.KmsPolicyInfo{
CreatedAt: policy.CreatedAt,
CreatedBy: policy.CreatedBy,
Name: policy.Name,
})
}
return data
}
func GetKMSDeletePolicyResponse(session *models.Principal, params kmsAPI.KMSDeletePolicyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
return deletePolicy(ctx, params.Name, AdminClient{Client: mAdmin})
}
func deletePolicy(ctx context.Context, policy string, minioClient MinioAdmin) *CodedAPIError {
if err := minioClient.deletePolicy(ctx, policy); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func registerKMSIdentityHandlers(api *operations.ConsoleAPI) {
api.KmsKMSDescribeIdentityHandler = kmsAPI.KMSDescribeIdentityHandlerFunc(func(params kmsAPI.KMSDescribeIdentityParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSDescribeIdentityResponse(session, params)
if err != nil {
return kmsAPI.NewKMSDescribeIdentityDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSDescribeIdentityOK().WithPayload(resp)
})
api.KmsKMSDescribeSelfIdentityHandler = kmsAPI.KMSDescribeSelfIdentityHandlerFunc(func(params kmsAPI.KMSDescribeSelfIdentityParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSDescribeSelfIdentityResponse(session, params)
if err != nil {
return kmsAPI.NewKMSDescribeSelfIdentityDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSDescribeSelfIdentityOK().WithPayload(resp)
})
api.KmsKMSListIdentitiesHandler = kmsAPI.KMSListIdentitiesHandlerFunc(func(params kmsAPI.KMSListIdentitiesParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSListIdentitiesResponse(session, params)
if err != nil {
return kmsAPI.NewKMSListIdentitiesDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSListIdentitiesOK().WithPayload(resp)
})
api.KmsKMSDeleteIdentityHandler = kmsAPI.KMSDeleteIdentityHandlerFunc(func(params kmsAPI.KMSDeleteIdentityParams, session *models.Principal) middleware.Responder {
err := GetKMSDeleteIdentityResponse(session, params)
if err != nil {
return kmsAPI.NewKMSDeleteIdentityDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSDeleteIdentityOK()
})
}
func GetKMSDescribeIdentityResponse(session *models.Principal, params kmsAPI.KMSDescribeIdentityParams) (*models.KmsDescribeIdentityResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return describeIdentity(ctx, params.Name, AdminClient{Client: mAdmin})
}
func describeIdentity(ctx context.Context, identity string, minioClient MinioAdmin) (*models.KmsDescribeIdentityResponse, *CodedAPIError) {
i, err := minioClient.describeIdentity(ctx, identity)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.KmsDescribeIdentityResponse{
Policy: i.Policy,
Admin: i.IsAdmin,
Identity: i.Identity,
CreatedAt: i.CreatedAt,
CreatedBy: i.CreatedBy,
}, nil
}
func GetKMSDescribeSelfIdentityResponse(session *models.Principal, params kmsAPI.KMSDescribeSelfIdentityParams) (*models.KmsDescribeSelfIdentityResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return describeSelfIdentity(ctx, AdminClient{Client: mAdmin})
}
func describeSelfIdentity(ctx context.Context, minioClient MinioAdmin) (*models.KmsDescribeSelfIdentityResponse, *CodedAPIError) {
i, err := minioClient.describeSelfIdentity(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.KmsDescribeSelfIdentityResponse{
Policy: &models.KmsGetPolicyResponse{
Allow: i.Policy.Allow,
Deny: i.Policy.Deny,
},
Identity: i.Identity,
Admin: i.IsAdmin,
CreatedAt: i.CreatedAt,
CreatedBy: i.CreatedBy,
}, nil
}
func GetKMSListIdentitiesResponse(session *models.Principal, params kmsAPI.KMSListIdentitiesParams) (*models.KmsListIdentitiesResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
pattern := ""
if params.Pattern != nil {
pattern = *params.Pattern
}
return listIdentities(ctx, pattern, AdminClient{Client: mAdmin})
}
func listIdentities(ctx context.Context, pattern string, minioClient MinioAdmin) (*models.KmsListIdentitiesResponse, *CodedAPIError) {
results, err := minioClient.listIdentities(ctx, pattern)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.KmsListIdentitiesResponse{Results: parseIdentities(results)}, nil
}
func parseIdentities(results []madmin.KMSIdentityInfo) (data []*models.KmsIdentityInfo) {
for _, policy := range results {
data = append(data, &models.KmsIdentityInfo{
CreatedAt: policy.CreatedAt,
CreatedBy: policy.CreatedBy,
Identity: policy.Identity,
Error: policy.Error,
Policy: policy.Policy,
})
}
return data
}
func GetKMSDeleteIdentityResponse(session *models.Principal, params kmsAPI.KMSDeleteIdentityParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
return deleteIdentity(ctx, params.Name, AdminClient{Client: mAdmin})
}
func deleteIdentity(ctx context.Context, identity string, minioClient MinioAdmin) *CodedAPIError {
if err := minioClient.deleteIdentity(ctx, identity); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}

View File

@@ -1,498 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/minio/console/api/operations"
kmsAPI "github.com/minio/console/api/operations/k_m_s"
"github.com/minio/console/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type KMSTestSuite struct {
suite.Suite
assert *assert.Assertions
currentServer string
isServerSet bool
server *httptest.Server
adminClient AdminClientMock
}
func (suite *KMSTestSuite) SetupSuite() {
suite.assert = assert.New(suite.T())
suite.adminClient = AdminClientMock{}
}
func (suite *KMSTestSuite) SetupTest() {
suite.server = httptest.NewServer(http.HandlerFunc(suite.serverHandler))
suite.currentServer, suite.isServerSet = os.LookupEnv(ConsoleMinIOServer)
os.Setenv(ConsoleMinIOServer, suite.server.URL)
}
func (suite *KMSTestSuite) serverHandler(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(400)
}
func (suite *KMSTestSuite) TearDownSuite() {
}
func (suite *KMSTestSuite) TearDownTest() {
if suite.isServerSet {
os.Setenv(ConsoleMinIOServer, suite.currentServer)
} else {
os.Unsetenv(ConsoleMinIOServer)
}
}
func (suite *KMSTestSuite) TestRegisterKMSHandlers() {
api := &operations.ConsoleAPI{}
suite.assertHandlersAreNil(api)
registerKMSHandlers(api)
suite.assertHandlersAreNotNil(api)
}
func (suite *KMSTestSuite) assertHandlersAreNil(api *operations.ConsoleAPI) {
suite.assert.Nil(api.KmsKMSStatusHandler)
suite.assert.Nil(api.KmsKMSMetricsHandler)
suite.assert.Nil(api.KmsKMSAPIsHandler)
suite.assert.Nil(api.KmsKMSVersionHandler)
suite.assert.Nil(api.KmsKMSCreateKeyHandler)
suite.assert.Nil(api.KmsKMSImportKeyHandler)
suite.assert.Nil(api.KmsKMSListKeysHandler)
suite.assert.Nil(api.KmsKMSKeyStatusHandler)
suite.assert.Nil(api.KmsKMSDeleteKeyHandler)
suite.assert.Nil(api.KmsKMSSetPolicyHandler)
suite.assert.Nil(api.KmsKMSAssignPolicyHandler)
suite.assert.Nil(api.KmsKMSDescribePolicyHandler)
suite.assert.Nil(api.KmsKMSGetPolicyHandler)
suite.assert.Nil(api.KmsKMSListPoliciesHandler)
suite.assert.Nil(api.KmsKMSDeletePolicyHandler)
suite.assert.Nil(api.KmsKMSDescribeIdentityHandler)
suite.assert.Nil(api.KmsKMSDescribeSelfIdentityHandler)
suite.assert.Nil(api.KmsKMSListIdentitiesHandler)
suite.assert.Nil(api.KmsKMSDeleteIdentityHandler)
}
func (suite *KMSTestSuite) assertHandlersAreNotNil(api *operations.ConsoleAPI) {
suite.assert.NotNil(api.KmsKMSStatusHandler)
suite.assert.NotNil(api.KmsKMSMetricsHandler)
suite.assert.NotNil(api.KmsKMSAPIsHandler)
suite.assert.NotNil(api.KmsKMSVersionHandler)
suite.assert.NotNil(api.KmsKMSCreateKeyHandler)
suite.assert.NotNil(api.KmsKMSImportKeyHandler)
suite.assert.NotNil(api.KmsKMSListKeysHandler)
suite.assert.NotNil(api.KmsKMSKeyStatusHandler)
suite.assert.NotNil(api.KmsKMSDeleteKeyHandler)
suite.assert.NotNil(api.KmsKMSSetPolicyHandler)
suite.assert.NotNil(api.KmsKMSAssignPolicyHandler)
suite.assert.NotNil(api.KmsKMSDescribePolicyHandler)
suite.assert.NotNil(api.KmsKMSGetPolicyHandler)
suite.assert.NotNil(api.KmsKMSListPoliciesHandler)
suite.assert.NotNil(api.KmsKMSDeletePolicyHandler)
suite.assert.NotNil(api.KmsKMSDescribeIdentityHandler)
suite.assert.NotNil(api.KmsKMSDescribeSelfIdentityHandler)
suite.assert.NotNil(api.KmsKMSListIdentitiesHandler)
suite.assert.NotNil(api.KmsKMSDeleteIdentityHandler)
}
func (suite *KMSTestSuite) TestKMSStatusHandlerWithError() {
params, api := suite.initKMSStatusRequest()
response := api.KmsKMSStatusHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSStatusDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSStatusRequest() (params kmsAPI.KMSStatusParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSStatusWithoutError() {
ctx := context.Background()
res, err := kmsStatus(ctx, suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSMetricsHandlerWithError() {
params, api := suite.initKMSMetricsRequest()
response := api.KmsKMSMetricsHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSMetricsDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSMetricsRequest() (params kmsAPI.KMSMetricsParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSMetricsWithoutError() {
ctx := context.Background()
res, err := kmsMetrics(ctx, suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSAPIsHandlerWithError() {
params, api := suite.initKMSAPIsRequest()
response := api.KmsKMSAPIsHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSAPIsDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSAPIsRequest() (params kmsAPI.KMSAPIsParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSAPIsWithoutError() {
ctx := context.Background()
res, err := kmsAPIs(ctx, suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSVersionHandlerWithError() {
params, api := suite.initKMSVersionRequest()
response := api.KmsKMSVersionHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSVersionDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSVersionRequest() (params kmsAPI.KMSVersionParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSVersionWithoutError() {
ctx := context.Background()
res, err := kmsVersion(ctx, suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSCreateKeyHandlerWithError() {
params, api := suite.initKMSCreateKeyRequest()
response := api.KmsKMSCreateKeyHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSCreateKeyDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSCreateKeyRequest() (params kmsAPI.KMSCreateKeyParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
key := "key"
params.Body = &models.KmsCreateKeyRequest{Key: &key}
return params, api
}
func (suite *KMSTestSuite) TestKMSCreateKeyWithoutError() {
ctx := context.Background()
err := createKey(ctx, "key", suite.adminClient)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSImportKeyHandlerWithError() {
params, api := suite.initKMSImportKeyRequest()
response := api.KmsKMSImportKeyHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSImportKeyDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSImportKeyRequest() (params kmsAPI.KMSImportKeyParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSImportKeyWithoutError() {
ctx := context.Background()
err := importKey(ctx, "key", []byte(""), suite.adminClient)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSListKeysHandlerWithError() {
params, api := suite.initKMSListKeysRequest()
response := api.KmsKMSListKeysHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSListKeysDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSListKeysRequest() (params kmsAPI.KMSListKeysParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSListKeysWithoutError() {
ctx := context.Background()
res, err := listKeys(ctx, "", suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSKeyStatusHandlerWithError() {
params, api := suite.initKMSKeyStatusRequest()
response := api.KmsKMSKeyStatusHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSKeyStatusDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSKeyStatusRequest() (params kmsAPI.KMSKeyStatusParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSKeyStatusWithoutError() {
ctx := context.Background()
res, err := keyStatus(ctx, "key", suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSDeleteKeyHandlerWithError() {
params, api := suite.initKMSDeleteKeyRequest()
response := api.KmsKMSDeleteKeyHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSDeleteKeyDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSDeleteKeyRequest() (params kmsAPI.KMSDeleteKeyParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSDeleteKeyWithoutError() {
ctx := context.Background()
err := deleteKey(ctx, "key", suite.adminClient)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSSetPolicyHandlerWithError() {
params, api := suite.initKMSSetPolicyRequest()
response := api.KmsKMSSetPolicyHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSSetPolicyDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSSetPolicyRequest() (params kmsAPI.KMSSetPolicyParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
policy := "policy"
params.Body = &models.KmsSetPolicyRequest{Policy: &policy}
return params, api
}
func (suite *KMSTestSuite) TestKMSSetPolicyWithoutError() {
ctx := context.Background()
err := setPolicy(ctx, "policy", []byte(""), suite.adminClient)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSAssignPolicyHandlerWithError() {
params, api := suite.initKMSAssignPolicyRequest()
response := api.KmsKMSAssignPolicyHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSAssignPolicyDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSAssignPolicyRequest() (params kmsAPI.KMSAssignPolicyParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSAssignPolicyWithoutError() {
ctx := context.Background()
err := assignPolicy(ctx, "policy", []byte(""), suite.adminClient)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSDescribePolicyHandlerWithError() {
params, api := suite.initKMSDescribePolicyRequest()
response := api.KmsKMSDescribePolicyHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSDescribePolicyDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSDescribePolicyRequest() (params kmsAPI.KMSDescribePolicyParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSDescribePolicyWithoutError() {
ctx := context.Background()
res, err := describePolicy(ctx, "policy", suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSGetPolicyHandlerWithError() {
params, api := suite.initKMSGetPolicyRequest()
response := api.KmsKMSGetPolicyHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSGetPolicyDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSGetPolicyRequest() (params kmsAPI.KMSGetPolicyParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSGetPolicyWithoutError() {
ctx := context.Background()
res, err := getPolicy(ctx, "policy", suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSListPoliciesHandlerWithError() {
params, api := suite.initKMSListPoliciesRequest()
response := api.KmsKMSListPoliciesHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSListPoliciesDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSListPoliciesRequest() (params kmsAPI.KMSListPoliciesParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSListPoliciesWithoutError() {
ctx := context.Background()
res, err := listKMSPolicies(ctx, "", suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSDeletePolicyHandlerWithError() {
params, api := suite.initKMSDeletePolicyRequest()
response := api.KmsKMSDeletePolicyHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSDeletePolicyDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSDeletePolicyRequest() (params kmsAPI.KMSDeletePolicyParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSDeletePolicyWithoutError() {
ctx := context.Background()
err := deletePolicy(ctx, "policy", suite.adminClient)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSDescribeIdentityHandlerWithError() {
params, api := suite.initKMSDescribeIdentityRequest()
response := api.KmsKMSDescribeIdentityHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSDescribeIdentityDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSDescribeIdentityRequest() (params kmsAPI.KMSDescribeIdentityParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSDescribeIdentityWithoutError() {
ctx := context.Background()
res, err := describeIdentity(ctx, "identity", suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSDescribeSelfIdentityHandlerWithError() {
params, api := suite.initKMSDescribeSelfIdentityRequest()
response := api.KmsKMSDescribeSelfIdentityHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSDescribeSelfIdentityDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSDescribeSelfIdentityRequest() (params kmsAPI.KMSDescribeSelfIdentityParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSDescribeSelfIdentityWithoutError() {
ctx := context.Background()
res, err := describeSelfIdentity(ctx, suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSListIdentitiesHandlerWithError() {
params, api := suite.initKMSListIdentitiesRequest()
response := api.KmsKMSListIdentitiesHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSListIdentitiesDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSListIdentitiesRequest() (params kmsAPI.KMSListIdentitiesParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSListIdentitiesWithoutError() {
ctx := context.Background()
res, err := listIdentities(ctx, "", suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *KMSTestSuite) TestKMSDeleteIdentityHandlerWithError() {
params, api := suite.initKMSDeleteIdentityRequest()
response := api.KmsKMSDeleteIdentityHandler.Handle(params, &models.Principal{})
_, ok := response.(*kmsAPI.KMSDeleteIdentityDefault)
suite.assert.True(ok)
}
func (suite *KMSTestSuite) initKMSDeleteIdentityRequest() (params kmsAPI.KMSDeleteIdentityParams, api operations.ConsoleAPI) {
registerKMSHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *KMSTestSuite) TestKMSDeleteIdentityWithoutError() {
ctx := context.Background()
err := deleteIdentity(ctx, "identity", suite.adminClient)
suite.assert.Nil(err)
}
func TestKMS(t *testing.T) {
suite.Run(t, new(KMSTestSuite))
}

View File

@@ -1,55 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
systemApi "github.com/minio/console/api/operations/system"
"github.com/minio/console/models"
)
func registerNodesHandler(api *operations.ConsoleAPI) {
api.SystemListNodesHandler = systemApi.ListNodesHandlerFunc(func(params systemApi.ListNodesParams, session *models.Principal) middleware.Responder {
listNodesResponse, err := getListNodesResponse(session, params)
if err != nil {
return systemApi.NewListNodesDefault(err.Code).WithPayload(err.APIError)
}
return systemApi.NewListNodesOK().WithPayload(listNodesResponse)
})
}
// getListNodesResponse returns a list of available node endpoints .
func getListNodesResponse(session *models.Principal, params systemApi.ListNodesParams) ([]string, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
var nodeList []string
adminResources, _ := mAdmin.ServerInfo(ctx)
for _, n := range adminResources.Servers {
nodeList = append(nodeList, n.Endpoint)
}
return nodeList, nil
}

View File

@@ -1,162 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"errors"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
configurationApi "github.com/minio/console/api/operations/configuration"
"github.com/minio/console/models"
)
func registerAdminNotificationEndpointsHandlers(api *operations.ConsoleAPI) {
// return a list of notification endpoints
api.ConfigurationNotificationEndpointListHandler = configurationApi.NotificationEndpointListHandlerFunc(func(params configurationApi.NotificationEndpointListParams, session *models.Principal) middleware.Responder {
notifEndpoints, err := getNotificationEndpointsResponse(session, params)
if err != nil {
return configurationApi.NewNotificationEndpointListDefault(err.Code).WithPayload(err.APIError)
}
return configurationApi.NewNotificationEndpointListOK().WithPayload(notifEndpoints)
})
// add a new notification endpoints
api.ConfigurationAddNotificationEndpointHandler = configurationApi.AddNotificationEndpointHandlerFunc(func(params configurationApi.AddNotificationEndpointParams, session *models.Principal) middleware.Responder {
notifEndpoints, err := getAddNotificationEndpointResponse(session, params)
if err != nil {
return configurationApi.NewAddNotificationEndpointDefault(err.Code).WithPayload(err.APIError)
}
return configurationApi.NewAddNotificationEndpointCreated().WithPayload(notifEndpoints)
})
}
// getNotificationEndpoints invokes admin info and returns a list of notification endpoints
func getNotificationEndpoints(ctx context.Context, client MinioAdmin) (*models.NotifEndpointResponse, error) {
serverInfo, err := client.serverInfo(ctx)
if err != nil {
return nil, err
}
var listEndpoints []*models.NotificationEndpointItem
for i := range serverInfo.Services.Notifications {
for service, endpointStatus := range serverInfo.Services.Notifications[i] {
for j := range endpointStatus {
for account, status := range endpointStatus[j] {
listEndpoints = append(listEndpoints, &models.NotificationEndpointItem{
Service: models.NofiticationService(service),
AccountID: account,
Status: status.Status,
})
}
}
}
}
// build response
return &models.NotifEndpointResponse{
NotificationEndpoints: listEndpoints,
}, nil
}
// getNotificationEndpointsResponse returns a list of notification endpoints in the instance
func getNotificationEndpointsResponse(session *models.Principal, params configurationApi.NotificationEndpointListParams) (*models.NotifEndpointResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
// serialize output
notfEndpointResp, err := getNotificationEndpoints(ctx, adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return notfEndpointResp, nil
}
func addNotificationEndpoint(ctx context.Context, client MinioAdmin, params *configurationApi.AddNotificationEndpointParams) (*models.SetNotificationEndpointResponse, error) {
configs := []*models.ConfigurationKV{}
var configName string
// we have different add validations for each service
switch *params.Body.Service {
case models.NofiticationServiceAmqp:
configName = "notify_amqp"
case models.NofiticationServiceMqtt:
configName = "notify_mqtt"
case models.NofiticationServiceElasticsearch:
configName = "notify_elasticsearch"
case models.NofiticationServiceRedis:
configName = "notify_redis"
case models.NofiticationServiceNats:
configName = "notify_nats"
case models.NofiticationServicePostgres:
configName = "notify_postgres"
case models.NofiticationServiceMysql:
configName = "notify_mysql"
case models.NofiticationServiceKafka:
configName = "notify_kafka"
case models.NofiticationServiceWebhook:
configName = "notify_webhook"
case models.NofiticationServiceNsq:
configName = "notify_nsq"
default:
return nil, errors.New("provided service is not supported")
}
// set all the config values if found on the param.Body.Properties
for k, val := range params.Body.Properties {
configs = append(configs, &models.ConfigurationKV{
Key: k,
Value: val,
})
}
needsRestart, err := setConfigWithARNAccountID(ctx, client, &configName, configs, *params.Body.AccountID)
if err != nil {
return nil, err
}
return &models.SetNotificationEndpointResponse{
AccountID: params.Body.AccountID,
Properties: params.Body.Properties,
Service: params.Body.Service,
Restart: needsRestart,
}, nil
}
// getNotificationEndpointsResponse returns a list of notification endpoints in the instance
func getAddNotificationEndpointResponse(session *models.Principal, params configurationApi.AddNotificationEndpointParams) (*models.SetNotificationEndpointResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
// serialize output
notfEndpointResp, err := addNotificationEndpoint(ctx, adminClient, &params)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return notfEndpointResp, nil
}

View File

@@ -1,454 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"errors"
"reflect"
"testing"
"github.com/go-openapi/swag"
cfgApi "github.com/minio/console/api/operations/configuration"
"github.com/minio/console/models"
)
func Test_addNotificationEndpoint(t *testing.T) {
client := AdminClientMock{}
type args struct {
ctx context.Context
client MinioAdmin
params *cfgApi.AddNotificationEndpointParams
}
tests := []struct {
name string
args args
mockSetConfig func(kv string) (restart bool, err error)
want *models.SetNotificationEndpointResponse
wantErr bool
}{
{
name: "valid postgres",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"host": "localhost",
"user": "user",
"password": "passwrd",
},
Service: models.NewNofiticationService("postgres"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
AccountID: swag.String("1"),
Properties: map[string]string{
"host": "localhost",
"user": "user",
"password": "passwrd",
},
Service: models.NewNofiticationService("postgres"),
Restart: false,
},
wantErr: false,
},
{
name: "set config returns error",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"host": "localhost",
"user": "user",
"password": "passwrd",
},
Service: models.NewNofiticationService("postgres"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, errors.New("error")
},
want: nil,
wantErr: true,
},
{
name: "valid mysql",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"host": "localhost",
"user": "user",
"password": "passwrd",
},
Service: models.NewNofiticationService("mysql"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
AccountID: swag.String("1"),
Properties: map[string]string{
"host": "localhost",
"user": "user",
"password": "passwrd",
},
Service: models.NewNofiticationService("mysql"),
Restart: false,
},
wantErr: false,
},
{
name: "valid kafka",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"brokers": "http://localhost:8080/broker1",
},
Service: models.NewNofiticationService("kafka"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
AccountID: swag.String("1"),
Properties: map[string]string{
"brokers": "http://localhost:8080/broker1",
},
Service: models.NewNofiticationService("kafka"),
Restart: false,
},
wantErr: false,
},
{
name: "valid amqp",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"url": "http://localhost:8080/broker1",
},
Service: models.NewNofiticationService("amqp"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
AccountID: swag.String("1"),
Properties: map[string]string{
"url": "http://localhost:8080/broker1",
},
Service: models.NewNofiticationService("amqp"),
Restart: false,
},
wantErr: false,
},
{
name: "valid mqtt",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"broker": "http://localhost:8080/broker1",
"topic": "minio",
},
Service: models.NewNofiticationService("mqtt"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
AccountID: swag.String("1"),
Properties: map[string]string{
"broker": "http://localhost:8080/broker1",
"topic": "minio",
},
Service: models.NewNofiticationService("mqtt"),
Restart: false,
},
wantErr: false,
},
{
name: "valid elasticsearch",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"url": "http://localhost:8080/broker1",
"index": "minio",
"format": "namespace",
},
Service: models.NewNofiticationService("elasticsearch"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
AccountID: swag.String("1"),
Properties: map[string]string{
"url": "http://localhost:8080/broker1",
"index": "minio",
"format": "namespace",
},
Service: models.NewNofiticationService("elasticsearch"),
Restart: false,
},
wantErr: false,
},
{
name: "valid redis",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"address": "http://localhost:8080/broker1",
"key": "minio",
"format": "namespace",
},
Service: models.NewNofiticationService("redis"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
AccountID: swag.String("1"),
Properties: map[string]string{
"address": "http://localhost:8080/broker1",
"key": "minio",
"format": "namespace",
},
Service: models.NewNofiticationService("redis"),
Restart: false,
},
wantErr: false,
},
{
name: "valid nats",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"address": "http://localhost:8080/broker1",
"subject": "minio",
},
Service: models.NewNofiticationService("nats"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
AccountID: swag.String("1"),
Properties: map[string]string{
"address": "http://localhost:8080/broker1",
"subject": "minio",
},
Service: models.NewNofiticationService("nats"),
Restart: false,
},
wantErr: false,
},
{
name: "valid webhook",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"endpoint": "http://localhost:8080/broker1",
},
Service: models.NewNofiticationService("webhook"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
AccountID: swag.String("1"),
Properties: map[string]string{
"endpoint": "http://localhost:8080/broker1",
},
Service: models.NewNofiticationService("webhook"),
Restart: false,
},
wantErr: false,
},
{
name: "valid nsq",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"nsqd_address": "http://localhost:8080/broker1",
"topic": "minio",
},
Service: models.NewNofiticationService("nsq"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
AccountID: swag.String("1"),
Properties: map[string]string{
"nsqd_address": "http://localhost:8080/broker1",
"topic": "minio",
},
Service: models.NewNofiticationService("nsq"),
Restart: false,
},
wantErr: false,
},
{
name: "invalid service",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"host": "localhost",
"user": "user",
"password": "passwrd",
},
Service: models.NewNofiticationService("oorgle"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return false, errors.New("invalid config")
},
want: nil,
wantErr: true,
},
{
name: "valid config, restart required",
args: args{
ctx: context.Background(),
client: client,
params: &cfgApi.AddNotificationEndpointParams{
HTTPRequest: nil,
Body: &models.NotificationEndpoint{
AccountID: swag.String("1"),
Properties: map[string]string{
"host": "localhost",
"user": "user",
"password": "passwrd",
},
Service: models.NewNofiticationService("postgres"),
},
},
},
mockSetConfig: func(_ string) (restart bool, err error) {
return true, nil
},
want: &models.SetNotificationEndpointResponse{
AccountID: swag.String("1"),
Properties: map[string]string{
"host": "localhost",
"user": "user",
"password": "passwrd",
},
Service: models.NewNofiticationService("postgres"),
Restart: true,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
// mock function response from setConfig()
minioSetConfigKVMock = tt.mockSetConfig
got, err := addNotificationEndpoint(tt.args.ctx, tt.args.client, tt.args.params)
if (err != nil) != tt.wantErr {
t.Errorf("addNotificationEndpoint() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("addNotificationEndpoint() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -18,7 +18,6 @@ package api
import (
"context"
"encoding/base64"
"time"
"github.com/minio/mc/cmd"
@@ -60,20 +59,7 @@ type ObjectResponse struct {
func getObjectsOptionsFromReq(request ObjectsRequest) (*objectsListOpts, error) {
pOptions := objectsListOpts{
BucketName: request.BucketName,
Prefix: "",
}
prefix := request.Prefix
if prefix != "" {
encodedPrefix := SanitizeEncodedPrefix(prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
LogError("error decoding prefix: %v", err)
return nil, err
}
pOptions.Prefix = string(decodedPrefix)
Prefix: request.Prefix,
}
if request.Mode == "rewind" {

View File

@@ -1,720 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"bytes"
"context"
"encoding/json"
"fmt"
"sort"
"strings"
bucketApi "github.com/minio/console/api/operations/bucket"
policyApi "github.com/minio/console/api/operations/policy"
"github.com/minio/console/pkg/utils"
s3 "github.com/minio/minio-go/v7"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
"github.com/minio/console/models"
iampolicy "github.com/minio/pkg/v2/policy"
policies "github.com/minio/console/api/policy"
)
func registersPoliciesHandler(api *operations.ConsoleAPI) {
// List Policies
api.PolicyListPoliciesHandler = policyApi.ListPoliciesHandlerFunc(func(params policyApi.ListPoliciesParams, session *models.Principal) middleware.Responder {
listPoliciesResponse, err := getListPoliciesResponse(session, params)
if err != nil {
return policyApi.NewListPoliciesDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewListPoliciesOK().WithPayload(listPoliciesResponse)
})
// Policy Info
api.PolicyPolicyInfoHandler = policyApi.PolicyInfoHandlerFunc(func(params policyApi.PolicyInfoParams, session *models.Principal) middleware.Responder {
policyInfo, err := getPolicyInfoResponse(session, params)
if err != nil {
return policyApi.NewPolicyInfoDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewPolicyInfoOK().WithPayload(policyInfo)
})
// Add Policy
api.PolicyAddPolicyHandler = policyApi.AddPolicyHandlerFunc(func(params policyApi.AddPolicyParams, session *models.Principal) middleware.Responder {
policyResponse, err := getAddPolicyResponse(session, params)
if err != nil {
return policyApi.NewAddPolicyDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewAddPolicyCreated().WithPayload(policyResponse)
})
// Remove Policy
api.PolicyRemovePolicyHandler = policyApi.RemovePolicyHandlerFunc(func(params policyApi.RemovePolicyParams, session *models.Principal) middleware.Responder {
if err := getRemovePolicyResponse(session, params); err != nil {
return policyApi.NewRemovePolicyDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewRemovePolicyNoContent()
})
// Set Policy
api.PolicySetPolicyHandler = policyApi.SetPolicyHandlerFunc(func(params policyApi.SetPolicyParams, session *models.Principal) middleware.Responder {
if err := getSetPolicyResponse(session, params); err != nil {
return policyApi.NewSetPolicyDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewSetPolicyNoContent()
})
// Set Policy Multiple User/Groups
api.PolicySetPolicyMultipleHandler = policyApi.SetPolicyMultipleHandlerFunc(func(params policyApi.SetPolicyMultipleParams, session *models.Principal) middleware.Responder {
if err := getSetPolicyMultipleResponse(session, params); err != nil {
return policyApi.NewSetPolicyMultipleDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewSetPolicyMultipleNoContent()
})
api.BucketListPoliciesWithBucketHandler = bucketApi.ListPoliciesWithBucketHandlerFunc(func(params bucketApi.ListPoliciesWithBucketParams, session *models.Principal) middleware.Responder {
policyResponse, err := getListPoliciesWithBucketResponse(session, params)
if err != nil {
return bucketApi.NewListPoliciesWithBucketDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewListPoliciesWithBucketOK().WithPayload(policyResponse)
})
api.BucketListAccessRulesWithBucketHandler = bucketApi.ListAccessRulesWithBucketHandlerFunc(func(params bucketApi.ListAccessRulesWithBucketParams, session *models.Principal) middleware.Responder {
policyResponse, err := getListAccessRulesWithBucketResponse(session, params)
if err != nil {
return bucketApi.NewListAccessRulesWithBucketDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewListAccessRulesWithBucketOK().WithPayload(policyResponse)
})
api.BucketSetAccessRuleWithBucketHandler = bucketApi.SetAccessRuleWithBucketHandlerFunc(func(params bucketApi.SetAccessRuleWithBucketParams, session *models.Principal) middleware.Responder {
policyResponse, err := getSetAccessRuleWithBucketResponse(session, params)
if err != nil {
return bucketApi.NewSetAccessRuleWithBucketDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewSetAccessRuleWithBucketOK().WithPayload(policyResponse)
})
api.BucketDeleteAccessRuleWithBucketHandler = bucketApi.DeleteAccessRuleWithBucketHandlerFunc(func(params bucketApi.DeleteAccessRuleWithBucketParams, session *models.Principal) middleware.Responder {
policyResponse, err := getDeleteAccessRuleWithBucketResponse(session, params)
if err != nil {
return bucketApi.NewDeleteAccessRuleWithBucketDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewDeleteAccessRuleWithBucketOK().WithPayload(policyResponse)
})
api.PolicyListUsersForPolicyHandler = policyApi.ListUsersForPolicyHandlerFunc(func(params policyApi.ListUsersForPolicyParams, session *models.Principal) middleware.Responder {
policyUsersResponse, err := getListUsersForPolicyResponse(session, params)
if err != nil {
return policyApi.NewListUsersForPolicyDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewListUsersForPolicyOK().WithPayload(policyUsersResponse)
})
api.PolicyListGroupsForPolicyHandler = policyApi.ListGroupsForPolicyHandlerFunc(func(params policyApi.ListGroupsForPolicyParams, session *models.Principal) middleware.Responder {
policyGroupsResponse, err := getListGroupsForPolicyResponse(session, params)
if err != nil {
return policyApi.NewListGroupsForPolicyDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewListGroupsForPolicyOK().WithPayload(policyGroupsResponse)
})
// Gets policies for currently logged in user
api.PolicyGetUserPolicyHandler = policyApi.GetUserPolicyHandlerFunc(func(params policyApi.GetUserPolicyParams, session *models.Principal) middleware.Responder {
userPolicyResponse, err := getUserPolicyResponse(params.HTTPRequest.Context(), session)
if err != nil {
return policyApi.NewGetUserPolicyDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewGetUserPolicyOK().WithPayload(userPolicyResponse)
})
// Gets policies for specified user
api.PolicyGetSAUserPolicyHandler = policyApi.GetSAUserPolicyHandlerFunc(func(params policyApi.GetSAUserPolicyParams, session *models.Principal) middleware.Responder {
userPolicyResponse, err := getSAUserPolicyResponse(session, params)
if err != nil {
return policyApi.NewGetSAUserPolicyDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewGetSAUserPolicyOK().WithPayload(userPolicyResponse)
})
}
func getListAccessRulesWithBucketResponse(session *models.Principal, params bucketApi.ListAccessRulesWithBucketParams) (*models.ListAccessRulesResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
bucket := params.Bucket
client, err := newS3BucketClient(session, bucket, "", getClientIP(params.HTTPRequest))
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
accessRules, _ := client.GetAccessRules(ctx)
var accessRuleList []*models.AccessRule
for k, v := range accessRules {
accessRuleList = append(accessRuleList, &models.AccessRule{Prefix: k[len(bucket)+1 : len(k)-1], Access: v})
}
return &models.ListAccessRulesResponse{AccessRules: accessRuleList}, nil
}
func getSetAccessRuleWithBucketResponse(session *models.Principal, params bucketApi.SetAccessRuleWithBucketParams) (bool, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
prefixAccess := params.Prefixaccess
client, err := newS3BucketClient(session, params.Bucket, prefixAccess.Prefix, getClientIP(params.HTTPRequest))
if err != nil {
return false, ErrorWithContext(ctx, err)
}
errorVal := client.SetAccess(ctx, prefixAccess.Access, false)
if errorVal != nil {
returnError := ErrorWithContext(ctx, errorVal.Cause)
minioError := s3.ToErrorResponse(errorVal.Cause)
if minioError.Code == "NoSuchBucket" {
returnError.Code = 404
}
return false, returnError
}
return true, nil
}
func getDeleteAccessRuleWithBucketResponse(session *models.Principal, params bucketApi.DeleteAccessRuleWithBucketParams) (bool, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
bucket := params.Bucket
prefix := params.Prefix
client, err := newS3BucketClient(session, bucket, prefix.Prefix, getClientIP(params.HTTPRequest))
if err != nil {
return false, ErrorWithContext(ctx, err)
}
errorVal := client.SetAccess(ctx, "none", false)
if errorVal != nil {
return false, ErrorWithContext(ctx, errorVal.Cause)
}
return true, nil
}
func getListPoliciesWithBucketResponse(session *models.Principal, params bucketApi.ListPoliciesWithBucketParams) (*models.ListPoliciesResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
policies, err := listPoliciesWithBucket(ctx, params.Bucket, adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// serialize output
listPoliciesResponse := &models.ListPoliciesResponse{
Policies: policies,
Total: int64(len(policies)),
}
return listPoliciesResponse, nil
}
// listPoliciesWithBucket calls MinIO server to list all policy names present on the server that apply to a particular bucket.
// listPoliciesWithBucket() converts the map[string][]byte returned by client.listPolicies()
// to []*models.Policy by iterating over each key in policyRawMap and
// then using Unmarshal on the raw bytes to create a *models.Policy
func listPoliciesWithBucket(ctx context.Context, bucket string, client MinioAdmin) ([]*models.Policy, error) {
policyMap, err := client.listPolicies(ctx)
var policies []*models.Policy
if err != nil {
return nil, err
}
for name, policy := range policyMap {
policy, err := parsePolicy(name, policy)
if err != nil {
return nil, err
}
if policyMatchesBucket(ctx, policy, bucket) {
policies = append(policies, policy)
}
}
return policies, nil
}
func policyMatchesBucket(ctx context.Context, policy *models.Policy, bucket string) bool {
policyData := &iampolicy.Policy{}
err := json.Unmarshal([]byte(policy.Policy), policyData)
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("error parsing policy: %v", err))
return false
}
policyStatements := policyData.Statements
for i := 0; i < len(policyStatements); i++ {
resources := policyStatements[i].Resources
if resources.Match(bucket, map[string][]string{}) {
return true
}
if resources.Match(fmt.Sprintf("%s/*", bucket), map[string][]string{}) {
return true
}
}
return false
}
// listPolicies calls MinIO server to list all policy names present on the server.
// listPolicies() converts the map[string][]byte returned by client.listPolicies()
// to []*models.Policy by iterating over each key in policyRawMap and
// then using Unmarshal on the raw bytes to create a *models.Policy
func listPolicies(ctx context.Context, client MinioAdmin) ([]*models.Policy, error) {
policyMap, err := client.listPolicies(ctx)
var policies []*models.Policy
if err != nil {
return nil, err
}
for name, policy := range policyMap {
policy, err := parsePolicy(name, policy)
if err != nil {
return nil, err
}
policies = append(policies, policy)
}
return policies, nil
}
// getListPoliciesResponse performs listPolicies() and serializes it to the handler's output
func getListPoliciesResponse(session *models.Principal, params policyApi.ListPoliciesParams) (*models.ListPoliciesResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
policies, err := listPolicies(ctx, adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// serialize output
listPoliciesResponse := &models.ListPoliciesResponse{
Policies: policies,
Total: int64(len(policies)),
}
return listPoliciesResponse, nil
}
// getListUsersForPoliciesResponse performs lists users affected by a given policy.
func getListUsersForPolicyResponse(session *models.Principal, params policyApi.ListUsersForPolicyParams) ([]string, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
policy, err := utils.DecodeBase64(params.Policy)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
policies, err := listPolicies(ctx, adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
found := false
for i := range policies {
if policies[i].Name == policy {
found = true
}
}
if !found {
return nil, ErrorWithContext(ctx, ErrPolicyNotFound, fmt.Errorf("the policy %s does not exist", policy))
}
users, err := listUsers(ctx, adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
var filteredUsers []string
for _, user := range users {
for _, upolicy := range user.Policy {
if upolicy == policy {
filteredUsers = append(filteredUsers, user.AccessKey)
break
}
}
}
sort.Strings(filteredUsers)
return filteredUsers, nil
}
func getUserPolicyResponse(ctx context.Context, session *models.Principal) (string, *CodedAPIError) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// serialize output
if session == nil {
return "nil", ErrorWithContext(ctx, ErrPolicyNotFound)
}
tokenClaims, _ := getClaimsFromToken(session.STSSessionToken)
// initialize admin client
mAdminClient, err := NewMinioAdminClient(ctx, &models.Principal{
STSAccessKeyID: session.STSAccessKeyID,
STSSecretAccessKey: session.STSSecretAccessKey,
STSSessionToken: session.STSSessionToken,
})
if err != nil {
return "nil", ErrorWithContext(ctx, err)
}
userAdminClient := AdminClient{Client: mAdminClient}
// Obtain the current policy assigned to this user
// necessary for generating the list of allowed endpoints
accountInfo, err := getAccountInfo(ctx, userAdminClient)
if err != nil {
return "nil", ErrorWithContext(ctx, err)
}
rawPolicy := policies.ReplacePolicyVariables(tokenClaims, accountInfo)
return string(rawPolicy), nil
}
func getSAUserPolicyResponse(session *models.Principal, params policyApi.GetSAUserPolicyParams) (*models.AUserPolicyResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// serialize output
if session == nil {
return nil, ErrorWithContext(ctx, ErrPolicyNotFound)
}
// initialize admin client
mAdminClient, err := NewMinioAdminClient(params.HTTPRequest.Context(), &models.Principal{
STSAccessKeyID: session.STSAccessKeyID,
STSSecretAccessKey: session.STSSecretAccessKey,
STSSessionToken: session.STSSessionToken,
})
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
userAdminClient := AdminClient{Client: mAdminClient}
userName, err := utils.DecodeBase64(params.Name)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
user, err := getUserInfo(ctx, userAdminClient, userName)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
var userPolicies []string
if len(user.PolicyName) > 0 {
userPolicies = strings.Split(user.PolicyName, ",")
}
for _, group := range user.MemberOf {
groupDesc, err := groupInfo(ctx, userAdminClient, group)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
if groupDesc.Policy != "" {
userPolicies = append(userPolicies, strings.Split(groupDesc.Policy, ",")...)
}
}
allKeys := make(map[string]bool)
var userPolicyList []string
for _, item := range userPolicies {
if _, value := allKeys[item]; !value {
allKeys[item] = true
userPolicyList = append(userPolicyList, item)
}
}
var userStatements []iampolicy.Statement
for _, pol := range userPolicyList {
policy, err := getPolicyStatements(ctx, userAdminClient, pol)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
userStatements = append(userStatements, policy...)
}
combinedPolicy := iampolicy.Policy{
Version: "2012-10-17",
Statements: userStatements,
}
stringPolicy, err := json.Marshal(combinedPolicy)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
parsedPolicy := string(stringPolicy)
getUserPoliciesResponse := &models.AUserPolicyResponse{
Policy: parsedPolicy,
}
return getUserPoliciesResponse, nil
}
func getListGroupsForPolicyResponse(session *models.Principal, params policyApi.ListGroupsForPolicyParams) ([]string, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
policy, err := utils.DecodeBase64(params.Policy)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
policies, err := listPolicies(ctx, adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
found := false
for i := range policies {
if policies[i].Name == policy {
found = true
}
}
if !found {
return nil, ErrorWithContext(ctx, ErrPolicyNotFound, fmt.Errorf("the policy %s does not exist", policy))
}
groups, err := adminClient.listGroups(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
var filteredGroups []string
for _, group := range groups {
info, err := groupInfo(ctx, adminClient, group)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
groupPolicies := strings.Split(info.Policy, ",")
for _, groupPolicy := range groupPolicies {
if groupPolicy == policy {
filteredGroups = append(filteredGroups, group)
}
}
}
sort.Strings(filteredGroups)
return filteredGroups, nil
}
// removePolicy() calls MinIO server to remove a policy based on name.
func removePolicy(ctx context.Context, client MinioAdmin, name string) error {
err := client.removePolicy(ctx, name)
if err != nil {
return err
}
return nil
}
// getRemovePolicyResponse() performs removePolicy() and serializes it to the handler's output
func getRemovePolicyResponse(session *models.Principal, params policyApi.RemovePolicyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params.Name == "" {
return ErrorWithContext(ctx, ErrPolicyNameNotInRequest)
}
policyName, err := utils.DecodeBase64(params.Name)
if err != nil {
return ErrorWithContext(ctx, err)
}
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
if err := removePolicy(ctx, adminClient, policyName); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
// addPolicy calls MinIO server to add a canned policy.
// addPolicy() takes name and policy in string format, policy
// policy must be string in json format, in the future this will change
// to a Policy struct{} - https://github.com/minio/minio/issues/9171
func addPolicy(ctx context.Context, client MinioAdmin, name, policy string) (*models.Policy, error) {
iamp, err := iampolicy.ParseConfig(bytes.NewReader([]byte(policy)))
if err != nil {
return nil, err
}
if err := client.addPolicy(ctx, name, iamp); err != nil {
return nil, err
}
policyObject, err := policyInfo(ctx, client, name)
if err != nil {
return nil, err
}
return policyObject, nil
}
// getAddPolicyResponse performs addPolicy() and serializes it to the handler's output
func getAddPolicyResponse(session *models.Principal, params policyApi.AddPolicyParams) (*models.Policy, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params.Body == nil {
return nil, ErrorWithContext(ctx, ErrPolicyBodyNotInRequest)
}
if strings.Contains(*params.Body.Name, " ") {
return nil, ErrorWithContext(ctx, ErrPolicyNameContainsSpace)
}
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
policy, err := addPolicy(ctx, adminClient, *params.Body.Name, *params.Body.Policy)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return policy, nil
}
// policyInfo calls MinIO server to retrieve information of a canned policy.
// policyInfo() takes a policy name, obtains the []byte (represents a string in JSON format)
// and return it as *models.Policy , in the future this will change
// to a Policy struct{} - https://github.com/minio/minio/issues/9171
func policyInfo(ctx context.Context, client MinioAdmin, name string) (*models.Policy, error) {
policyRaw, err := client.getPolicy(ctx, name)
if err != nil {
return nil, err
}
policy, err := parsePolicy(name, policyRaw)
if err != nil {
return nil, err
}
return policy, nil
}
// getPolicy Statements calls MinIO server to retrieve information of a canned policy.
// and returns the associated Statements
func getPolicyStatements(ctx context.Context, client MinioAdmin, name string) ([]iampolicy.Statement, error) {
policyRaw, err := client.getPolicy(ctx, name)
if err != nil {
return nil, err
}
return policyRaw.Statements, nil
}
// getPolicyInfoResponse performs policyInfo() and serializes it to the handler's output
func getPolicyInfoResponse(session *models.Principal, params policyApi.PolicyInfoParams) (*models.Policy, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
policyName, err := utils.DecodeBase64(params.Name)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
policy, err := policyInfo(ctx, adminClient, policyName)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return policy, nil
}
// SetPolicy calls MinIO server to assign policy to a group or user.
func SetPolicy(ctx context.Context, client MinioAdmin, name, entityName string, entityType models.PolicyEntity) error {
isGroup := false
if entityType == models.PolicyEntityGroup {
isGroup = true
}
return client.setPolicy(ctx, name, entityName, isGroup)
}
// getSetPolicyResponse() performs SetPolicy() and serializes it to the handler's output
func getSetPolicyResponse(session *models.Principal, params policyApi.SetPolicyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// Removing this section
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
if err := SetPolicy(ctx, adminClient, strings.Join(params.Body.Name, ","), *params.Body.EntityName, *params.Body.EntityType); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func getSetPolicyMultipleResponse(session *models.Principal, params policyApi.SetPolicyMultipleParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
if err := setPolicyMultipleEntities(ctx, adminClient, strings.Join(params.Body.Name, ","), params.Body.Users, params.Body.Groups); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
// setPolicyMultipleEntities sets a policy to multiple users/groups
func setPolicyMultipleEntities(ctx context.Context, client MinioAdmin, policyName string, users, groups []models.IamEntity) error {
for _, user := range users {
if err := client.setPolicy(ctx, policyName, string(user), false); err != nil {
return err
}
}
for _, group := range groups {
groupDesc, err := groupInfo(ctx, client, string(group))
if err != nil {
return err
}
allGroupPolicies := ""
if len(groups) > 1 {
allGroupPolicies = groupDesc.Policy + "," + policyName
s := strings.Split(allGroupPolicies, ",")
allGroupPolicies = strings.Join(UniqueKeys(s), ",")
} else {
allGroupPolicies = policyName
}
if err := client.setPolicy(ctx, allGroupPolicies, string(group), true); err != nil {
return err
}
}
return nil
}
// parsePolicy() converts from *rawPolicy to *models.Policy
func parsePolicy(name string, rawPolicy *iampolicy.Policy) (*models.Policy, error) {
stringPolicy, err := json.Marshal(rawPolicy)
if err != nil {
return nil, err
}
policy := &models.Policy{
Name: name,
Policy: string(stringPolicy),
}
return policy, nil
}

View File

@@ -1,382 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"reflect"
"testing"
"github.com/minio/console/models"
iampolicy "github.com/minio/pkg/v2/policy"
"github.com/stretchr/testify/assert"
)
func TestListPolicies(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
funcAssert := assert.New(t)
adminClient := AdminClientMock{}
// mock function response from listPolicies()
minioListPoliciesMock = func() (map[string]*iampolicy.Policy, error) {
var readonly iampolicy.Policy
var readwrite iampolicy.Policy
var diagnostis iampolicy.Policy
for _, p := range iampolicy.DefaultPolicies {
switch p.Name {
case "readonly":
readonly = p.Definition
case "readwrite":
readwrite = p.Definition
case "diagnostics":
diagnostis = p.Definition
}
}
return map[string]*iampolicy.Policy{
"readonly": &readonly,
"readwrite": &readwrite,
"diagnostics": &diagnostis,
}, nil
}
// Test-1 : listPolicies() Get response from minio client with three Canned Policies and return the same number on listPolicies()
function := "listPolicies()"
policiesList, err := listPolicies(ctx, adminClient)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// verify length of Policies is correct
funcAssert.Equal(3, len(policiesList), fmt.Sprintf("Failed on %s: length of Policies's lists is not the same", function))
// Test-2 : listPolicies() Return error and see that the error is handled correctly and returned
minioListPoliciesMock = func() (map[string]*iampolicy.Policy, error) {
return nil, errors.New("error")
}
_, err = listPolicies(ctx, adminClient)
if funcAssert.Error(err) {
funcAssert.Equal("error", err.Error())
}
}
func TestRemovePolicy(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
funcAssert := assert.New(t)
adminClient := AdminClientMock{}
// Test-1 : removePolicy() remove an existing policy
policyToRemove := "console-policy"
minioRemovePolicyMock = func(_ string) error {
return nil
}
function := "removePolicy()"
if err := removePolicy(ctx, adminClient, policyToRemove); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-2 : removePolicy() Return error and see that the error is handled correctly and returned
minioRemovePolicyMock = func(_ string) error {
return errors.New("error")
}
if err := removePolicy(ctx, adminClient, policyToRemove); funcAssert.Error(err) {
funcAssert.Equal("error", err.Error())
}
}
func TestAddPolicy(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
funcAssert := assert.New(t)
adminClient := AdminClientMock{}
policyName := "new-policy"
policyDefinition := "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:GetBucketLocation\",\"s3:GetObject\",\"s3:ListAllMyBuckets\"],\"Resource\":[\"arn:aws:s3:::*\"]}]}"
minioAddPolicyMock = func(_ string, _ *iampolicy.Policy) error {
return nil
}
minioGetPolicyMock = func(_ string) (*iampolicy.Policy, error) {
policy := "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:GetBucketLocation\",\"s3:GetObject\",\"s3:ListAllMyBuckets\"],\"Resource\":[\"arn:aws:s3:::*\"]}]}"
iamp, err := iampolicy.ParseConfig(bytes.NewReader([]byte(policy)))
if err != nil {
return nil, err
}
return iamp, nil
}
assertPolicy := models.Policy{
Name: "new-policy",
Policy: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:GetBucketLocation\",\"s3:GetObject\",\"s3:ListAllMyBuckets\"],\"Resource\":[\"arn:aws:s3:::*\"]}]}",
}
// Test-1 : addPolicy() adds a new policy
function := "addPolicy()"
policy, err := addPolicy(ctx, adminClient, policyName, policyDefinition)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
} else {
funcAssert.Equal(policy.Name, assertPolicy.Name)
var expectedPolicy iampolicy.Policy
var actualPolicy iampolicy.Policy
err1 := json.Unmarshal([]byte(policy.Policy), &expectedPolicy)
funcAssert.NoError(err1)
err2 := json.Unmarshal([]byte(assertPolicy.Policy), &actualPolicy)
funcAssert.NoError(err2)
funcAssert.Equal(expectedPolicy, actualPolicy)
}
// Test-2 : addPolicy() got an error while adding policy
minioAddPolicyMock = func(_ string, _ *iampolicy.Policy) error {
return errors.New("error")
}
if _, err := addPolicy(ctx, adminClient, policyName, policyDefinition); funcAssert.Error(err) {
funcAssert.Equal("error", err.Error())
}
// Test-3 : addPolicy() got an error while retrieving policy
minioAddPolicyMock = func(_ string, _ *iampolicy.Policy) error {
return nil
}
minioGetPolicyMock = func(_ string) (*iampolicy.Policy, error) {
return nil, errors.New("error")
}
if _, err := addPolicy(ctx, adminClient, policyName, policyDefinition); funcAssert.Error(err) {
funcAssert.Equal("error", err.Error())
}
}
func TestSetPolicy(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
funcAssert := assert.New(t)
adminClient := AdminClientMock{}
policyName := "readOnly"
entityName := "alevsk"
entityObject := models.PolicyEntityUser
minioSetPolicyMock = func(_, _ string, _ bool) error {
return nil
}
// Test-1 : SetPolicy() set policy to user
function := "SetPolicy()"
err := SetPolicy(ctx, adminClient, policyName, entityName, entityObject)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-2 : SetPolicy() set policy to group
entityObject = models.PolicyEntityGroup
err = SetPolicy(ctx, adminClient, policyName, entityName, entityObject)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-3 : SetPolicy() set policy to user and get error
entityObject = models.PolicyEntityUser
minioSetPolicyMock = func(_, _ string, _ bool) error {
return errors.New("error")
}
if err := SetPolicy(ctx, adminClient, policyName, entityName, entityObject); funcAssert.Error(err) {
funcAssert.Equal("error", err.Error())
}
// Test-4 : SetPolicy() set policy to group and get error
entityObject = models.PolicyEntityGroup
minioSetPolicyMock = func(_, _ string, _ bool) error {
return errors.New("error")
}
if err := SetPolicy(ctx, adminClient, policyName, entityName, entityObject); funcAssert.Error(err) {
funcAssert.Equal("error", err.Error())
}
}
func Test_SetPolicyMultiple(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
adminClient := AdminClientMock{}
type args struct {
policyName string
users []models.IamEntity
groups []models.IamEntity
setPolicyFunc func(policyName, entityName string, isGroup bool) error
}
tests := []struct {
name string
args args
errorExpected error
}{
{
name: "Set policy to multiple users and groups",
args: args{
policyName: "readonly",
users: []models.IamEntity{"user1", "user2"},
groups: []models.IamEntity{"group1", "group2"},
setPolicyFunc: func(_, _ string, _ bool) error {
return nil
},
},
errorExpected: nil,
},
{
name: "Return error on set policy function",
args: args{
policyName: "readonly",
users: []models.IamEntity{"user1", "user2"},
groups: []models.IamEntity{"group1", "group2"},
setPolicyFunc: func(_, _ string, _ bool) error {
return errors.New("error set")
},
},
errorExpected: errors.New("error set"),
},
{
// Description: Empty lists of users and groups are acceptable
name: "Empty lists of users and groups",
args: args{
policyName: "readonly",
users: []models.IamEntity{},
groups: []models.IamEntity{},
setPolicyFunc: func(_, _ string, _ bool) error {
return nil
},
},
errorExpected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
minioSetPolicyMock = tt.args.setPolicyFunc
got := setPolicyMultipleEntities(ctx, adminClient, tt.args.policyName, tt.args.users, tt.args.groups)
if !reflect.DeepEqual(got, tt.errorExpected) {
ji, _ := json.Marshal(got)
vi, _ := json.Marshal(tt.errorExpected)
t.Errorf("got %s want %s", ji, vi)
}
})
}
}
func Test_policyMatchesBucket(t *testing.T) {
type args struct {
ctx context.Context
policy *models.Policy
bucket string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "Test1",
args: args{ctx: context.Background(), policy: &models.Policy{Name: "consoleAdmin", Policy: `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"admin:*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`}, bucket: "test1"},
want: true,
},
{
name: "Test2",
args: args{ctx: context.Background(), policy: &models.Policy{Name: "consoleAdmin", Policy: `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket1"
]
}
]
}`}, bucket: "test1"},
want: false,
},
{
name: "Test3",
args: args{ctx: context.Background(), policy: &models.Policy{Name: "consoleAdmin", Policy: `{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:ListStorageLensConfigurations",
"s3:GetAccessPoint",
"s3:PutAccountPublicAccessBlock",
"s3:GetAccountPublicAccessBlock",
"s3:ListAllMyBuckets",
"s3:ListAccessPoints",
"s3:ListJobs",
"s3:PutStorageLensConfiguration",
"s3:CreateJob"
],
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::test",
"arn:aws:s3:::test/*",
"arn:aws:s3:::lkasdkljasd090901",
"arn:aws:s3:::lkasdkljasd090901/*"
]
}
]
}`}, bucket: "test1"},
want: false,
},
{
name: "Test4",
args: args{ctx: context.Background(), policy: &models.Policy{Name: "consoleAdmin", Policy: `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket1"
]
}
]
}`}, bucket: "bucket1"},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
if got := policyMatchesBucket(tt.args.ctx, tt.args.policy, tt.args.bucket); got != tt.want {
t.Errorf("policyMatchesBucket() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,63 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"io"
"net/http"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
"github.com/minio/websocket"
)
var items []*models.StartProfilingItem
type profileOptions struct {
Types string
}
func getProfileOptionsFromReq(req *http.Request) (*profileOptions, error) {
pOptions := profileOptions{}
pOptions.Types = req.FormValue("types")
return &pOptions, nil
}
func startProfiling(ctx context.Context, conn WSConn, client MinioAdmin, pOpts *profileOptions) error {
profilingResults, err := client.startProfiling(ctx, madmin.ProfilerType(pOpts.Types))
if err != nil {
return err
}
items = []*models.StartProfilingItem{}
for _, result := range profilingResults {
items = append(items, &models.StartProfilingItem{
Success: result.Success,
Error: result.Error,
NodeName: result.NodeName,
})
}
zippedData, err := client.stopProfiling(ctx)
if err != nil {
return err
}
message, err := io.ReadAll(zippedData)
if err != nil {
return err
}
return conn.writeMessage(websocket.BinaryMessage, message)
}

View File

@@ -1,105 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"bytes"
"context"
"errors"
"io"
"net/http"
"net/url"
"testing"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
)
// Implementing fake closingBuffer to mock stopProfiling() (io.ReadCloser, error)
type ClosingBuffer struct {
*bytes.Buffer
}
// Implementing a fake Close function for io.ReadCloser
func (cb *ClosingBuffer) Close() error {
return nil
}
func TestStartProfiling(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
assert := assert.New(t)
adminClient := AdminClientMock{}
mockWSConn := mockConn{}
function := "startProfiling()"
testOptions := &profileOptions{
Types: "cpu",
}
// Test-1 : startProfiling() Get response from MinIO server with one profiling object without errors
// mock function response from startProfiling()
minioStartProfiling = func(_ madmin.ProfilerType) ([]madmin.StartProfilingResult, error) {
return []madmin.StartProfilingResult{
{
NodeName: "http://127.0.0.1:9000/",
Success: true,
Error: "",
},
{
NodeName: "http://127.0.0.1:9001/",
Success: true,
Error: "",
},
}, nil
}
// mock function response from stopProfiling()
minioStopProfiling = func() (io.ReadCloser, error) {
return &ClosingBuffer{bytes.NewBufferString("In memory string eaeae")}, nil
}
// mock function response from mockConn.writeMessage()
connWriteMessageMock = func(_ int, _ []byte) error {
return nil
}
err := startProfiling(ctx, mockWSConn, adminClient, testOptions)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
assert.Equal(err, nil)
// Test-2 : startProfiling() Correctly handles errors returned by MinIO
// mock function response from startProfiling()
minioStartProfiling = func(_ madmin.ProfilerType) ([]madmin.StartProfilingResult, error) {
return nil, errors.New("error")
}
err = startProfiling(ctx, mockWSConn, adminClient, testOptions)
if assert.Error(err) {
assert.Equal("error", err.Error())
}
// Test-3: getProfileOptionsFromReq() correctly returns profile options from request
u, _ := url.Parse("ws://localhost/ws/profile?types=cpu,mem,block,mutex,trace,threads,goroutines")
req := &http.Request{
URL: u,
}
opts, err := getProfileOptionsFromReq(req)
if assert.NoError(err) {
expectedOptions := profileOptions{
Types: "cpu,mem,block,mutex,trace,threads,goroutines",
}
assert.Equal(expectedOptions.Types, opts.Types)
}
}

View File

@@ -1,116 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"github.com/minio/console/pkg/utils"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
release "github.com/minio/console/api/operations/release"
"github.com/minio/console/models"
"github.com/minio/pkg/v2/env"
)
var (
releaseServiceHostEnvVar = "RELEASE_SERVICE_HOST"
defaultReleaseServiceHost = "https://enterprise-updates.ic.min.dev"
)
func registerReleasesHandlers(api *operations.ConsoleAPI) {
api.ReleaseListReleasesHandler = release.ListReleasesHandlerFunc(func(params release.ListReleasesParams, session *models.Principal) middleware.Responder {
resp, err := GetReleaseListResponse(session, params)
if err != nil {
return release.NewListReleasesDefault(err.Code).WithPayload(err.APIError)
}
return release.NewListReleasesOK().WithPayload(resp)
})
}
func GetReleaseListResponse(_ *models.Principal, params release.ListReleasesParams) (*models.ReleaseListResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
repo := params.Repo
currentRelease := ""
if params.Current != nil {
currentRelease = *params.Current
}
search := ""
if params.Search != nil {
search = *params.Search
}
filter := ""
if params.Filter != nil {
filter = *params.Filter
}
ctx = context.WithValue(ctx, utils.ContextClientIP, getClientIP(params.HTTPRequest))
return releaseList(ctx, repo, currentRelease, search, filter)
}
func releaseList(ctx context.Context, repo, currentRelease, search, filter string) (*models.ReleaseListResponse, *CodedAPIError) {
serviceURL := getReleaseServiceURL()
clientIP := utils.ClientIPFromContext(ctx)
releases, err := getReleases(serviceURL, repo, currentRelease, search, filter, clientIP)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return releases, nil
}
func getReleaseServiceURL() string {
host := env.Get(releaseServiceHostEnvVar, defaultReleaseServiceHost)
return fmt.Sprintf("%s/releases", host)
}
func getReleases(endpoint, repo, currentRelease, search, filter, clientIP string) (*models.ReleaseListResponse, error) {
rl := &models.ReleaseListResponse{}
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
return nil, err
}
q := &url.Values{}
q.Add("repo", repo)
q.Add("search", search)
q.Add("filter", filter)
q.Add("current", currentRelease)
req.URL.RawQuery = q.Encode()
req.Header.Set("Content-Type", "application/json")
client := GetConsoleHTTPClient(clientIP)
client.Timeout = time.Second * 5
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("error getting releases: %s", resp.Status)
}
err = json.NewDecoder(resp.Body).Decode(&rl)
if err != nil {
return nil, err
}
return rl, nil
}

View File

@@ -1,104 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/minio/console/api/operations"
release "github.com/minio/console/api/operations/release"
"github.com/minio/console/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type ReleasesTestSuite struct {
suite.Suite
assert *assert.Assertions
currentServer string
isServerSet bool
getServer *httptest.Server
withError bool
}
func (suite *ReleasesTestSuite) SetupSuite() {
suite.assert = assert.New(suite.T())
suite.getServer = httptest.NewServer(http.HandlerFunc(suite.getHandler))
suite.currentServer, suite.isServerSet = os.LookupEnv(releaseServiceHostEnvVar)
os.Setenv(releaseServiceHostEnvVar, suite.getServer.URL)
}
func (suite *ReleasesTestSuite) TearDownSuite() {
if suite.isServerSet {
os.Setenv(releaseServiceHostEnvVar, suite.currentServer)
} else {
os.Unsetenv(releaseServiceHostEnvVar)
}
}
func (suite *ReleasesTestSuite) getHandler(
w http.ResponseWriter, _ *http.Request,
) {
if suite.withError {
w.WriteHeader(400)
} else {
w.WriteHeader(200)
response := &models.ReleaseListResponse{}
bytes, _ := json.Marshal(response)
fmt.Fprint(w, string(bytes))
}
}
func (suite *ReleasesTestSuite) TestRegisterReleasesHandlers() {
api := &operations.ConsoleAPI{}
suite.assert.Nil(api.ReleaseListReleasesHandler)
registerReleasesHandlers(api)
suite.assert.NotNil(api.ReleaseListReleasesHandler)
}
func (suite *ReleasesTestSuite) TestGetReleasesWithError() {
api := &operations.ConsoleAPI{}
current := "mock"
registerReleasesHandlers(api)
params := release.NewListReleasesParams()
params.Current = &current
params.HTTPRequest = &http.Request{}
suite.withError = true
response := api.ReleaseListReleasesHandler.Handle(params, &models.Principal{})
_, ok := response.(*release.ListReleasesDefault)
suite.assert.True(ok)
}
func (suite *ReleasesTestSuite) TestGetReleasesWithoutError() {
api := &operations.ConsoleAPI{}
registerReleasesHandlers(api)
params := release.NewListReleasesParams()
params.HTTPRequest = &http.Request{}
suite.withError = false
response := api.ReleaseListReleasesHandler.Handle(params, &models.Principal{})
_, ok := response.(*release.ListReleasesOK)
suite.assert.True(ok)
}
func TestReleases(t *testing.T) {
suite.Run(t, new(ReleasesTestSuite))
}

View File

@@ -1,810 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"errors"
"fmt"
"net/url"
"strconv"
"time"
"github.com/minio/console/pkg/utils"
"github.com/minio/madmin-go/v3"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/api/operations"
bucketApi "github.com/minio/console/api/operations/bucket"
"github.com/minio/console/models"
"github.com/minio/minio-go/v7/pkg/replication"
)
type RemoteBucketResult struct {
OriginBucket string
TargetBucket string
Error string
}
func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
// return list of remote buckets
api.BucketListRemoteBucketsHandler = bucketApi.ListRemoteBucketsHandlerFunc(func(params bucketApi.ListRemoteBucketsParams, session *models.Principal) middleware.Responder {
listResp, err := getListRemoteBucketsResponse(session, params)
if err != nil {
return bucketApi.NewListRemoteBucketsDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewListRemoteBucketsOK().WithPayload(listResp)
})
// return information about a specific bucket
api.BucketRemoteBucketDetailsHandler = bucketApi.RemoteBucketDetailsHandlerFunc(func(params bucketApi.RemoteBucketDetailsParams, session *models.Principal) middleware.Responder {
response, err := getRemoteBucketDetailsResponse(session, params)
if err != nil {
return bucketApi.NewRemoteBucketDetailsDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewRemoteBucketDetailsOK().WithPayload(response)
})
// delete remote bucket
api.BucketDeleteRemoteBucketHandler = bucketApi.DeleteRemoteBucketHandlerFunc(func(params bucketApi.DeleteRemoteBucketParams, session *models.Principal) middleware.Responder {
err := getDeleteRemoteBucketResponse(session, params)
if err != nil {
return bucketApi.NewDeleteRemoteBucketDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewDeleteRemoteBucketNoContent()
})
// set remote bucket
api.BucketAddRemoteBucketHandler = bucketApi.AddRemoteBucketHandlerFunc(func(params bucketApi.AddRemoteBucketParams, session *models.Principal) middleware.Responder {
err := getAddRemoteBucketResponse(session, params)
if err != nil {
return bucketApi.NewAddRemoteBucketDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewAddRemoteBucketCreated()
})
// set multi-bucket replication
api.BucketSetMultiBucketReplicationHandler = bucketApi.SetMultiBucketReplicationHandlerFunc(func(params bucketApi.SetMultiBucketReplicationParams, session *models.Principal) middleware.Responder {
response, err := setMultiBucketReplicationResponse(session, params)
if err != nil {
return bucketApi.NewSetMultiBucketReplicationDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewSetMultiBucketReplicationOK().WithPayload(response)
})
// list external buckets
api.BucketListExternalBucketsHandler = bucketApi.ListExternalBucketsHandlerFunc(func(params bucketApi.ListExternalBucketsParams, _ *models.Principal) middleware.Responder {
response, err := listExternalBucketsResponse(params)
if err != nil {
return bucketApi.NewListExternalBucketsDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewListExternalBucketsOK().WithPayload(response)
})
// delete replication rule
api.BucketDeleteBucketReplicationRuleHandler = bucketApi.DeleteBucketReplicationRuleHandlerFunc(func(params bucketApi.DeleteBucketReplicationRuleParams, session *models.Principal) middleware.Responder {
err := deleteReplicationRuleResponse(session, params)
if err != nil {
return bucketApi.NewDeleteBucketReplicationRuleDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewDeleteBucketReplicationRuleNoContent()
})
// delete all replication rules for a bucket
api.BucketDeleteAllReplicationRulesHandler = bucketApi.DeleteAllReplicationRulesHandlerFunc(func(params bucketApi.DeleteAllReplicationRulesParams, session *models.Principal) middleware.Responder {
err := deleteBucketReplicationRulesResponse(session, params)
if err != nil {
if err.Code == 500 && err.APIError.DetailedMessage == "The remote target does not exist" {
// We should ignore this MinIO error when deleting all replication rules
return bucketApi.NewDeleteAllReplicationRulesNoContent() // This will return 204 as per swagger spec
}
// If there is a different error, then we should handle it
// This will return a generic error with err.Code (likely a 500 or 404) and its *err.DetailedMessage
return bucketApi.NewDeleteAllReplicationRulesDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewDeleteAllReplicationRulesNoContent()
})
// delete selected replication rules for a bucket
api.BucketDeleteSelectedReplicationRulesHandler = bucketApi.DeleteSelectedReplicationRulesHandlerFunc(func(params bucketApi.DeleteSelectedReplicationRulesParams, session *models.Principal) middleware.Responder {
err := deleteSelectedReplicationRulesResponse(session, params)
if err != nil {
return bucketApi.NewDeleteSelectedReplicationRulesDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewDeleteSelectedReplicationRulesNoContent()
})
// update local bucket replication config item
api.BucketUpdateMultiBucketReplicationHandler = bucketApi.UpdateMultiBucketReplicationHandlerFunc(func(params bucketApi.UpdateMultiBucketReplicationParams, session *models.Principal) middleware.Responder {
err := updateBucketReplicationResponse(session, params)
if err != nil {
return bucketApi.NewUpdateMultiBucketReplicationDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewUpdateMultiBucketReplicationCreated()
})
}
func getListRemoteBucketsResponse(session *models.Principal, params bucketApi.ListRemoteBucketsParams) (*models.ListRemoteBucketsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
}
adminClient := AdminClient{Client: mAdmin}
return listRemoteBuckets(ctx, adminClient)
}
func getRemoteBucketDetailsResponse(session *models.Principal, params bucketApi.RemoteBucketDetailsParams) (*models.RemoteBucket, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
}
adminClient := AdminClient{Client: mAdmin}
return getRemoteBucket(ctx, adminClient, params.Name)
}
func getDeleteRemoteBucketResponse(session *models.Principal, params bucketApi.DeleteRemoteBucketParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
}
adminClient := AdminClient{Client: mAdmin}
err = deleteRemoteBucket(ctx, adminClient, params.SourceBucketName, params.Arn)
if err != nil {
return ErrorWithContext(ctx, fmt.Errorf("error deleting remote bucket: %v", err))
}
return nil
}
func getAddRemoteBucketResponse(session *models.Principal, params bucketApi.AddRemoteBucketParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
}
adminClient := AdminClient{Client: mAdmin}
_, err = addRemoteBucket(ctx, adminClient, *params.Body)
if err != nil {
return ErrorWithContext(ctx, fmt.Errorf("error adding remote bucket: %v", err))
}
return nil
}
func listRemoteBuckets(ctx context.Context, client MinioAdmin) (*models.ListRemoteBucketsResponse, *CodedAPIError) {
var remoteBuckets []*models.RemoteBucket
buckets, err := client.listRemoteBuckets(ctx, "", "")
if err != nil {
return nil, ErrorWithContext(ctx, fmt.Errorf("error listing remote buckets: %v", err))
}
for _, bucket := range buckets {
remoteBucket := &models.RemoteBucket{
AccessKey: swag.String(bucket.Credentials.AccessKey),
RemoteARN: swag.String(bucket.Arn),
SecretKey: bucket.Credentials.SecretKey,
Service: "replication",
SourceBucket: swag.String(bucket.SourceBucket),
Status: "",
TargetBucket: bucket.TargetBucket,
TargetURL: bucket.Endpoint,
SyncMode: "async",
Bandwidth: bucket.BandwidthLimit,
HealthCheckPeriod: int64(bucket.HealthCheckDuration.Seconds()),
}
if bucket.ReplicationSync {
remoteBucket.SyncMode = "sync"
}
remoteBuckets = append(remoteBuckets, remoteBucket)
}
return &models.ListRemoteBucketsResponse{
Buckets: remoteBuckets,
Total: int64(len(remoteBuckets)),
}, nil
}
func getRemoteBucket(ctx context.Context, client MinioAdmin, name string) (*models.RemoteBucket, *CodedAPIError) {
remoteBucket, err := client.getRemoteBucket(ctx, name, "")
if err != nil {
return nil, ErrorWithContext(ctx, fmt.Errorf("error getting remote bucket details: %v", err))
}
if remoteBucket == nil {
return nil, ErrorWithContext(ctx, "error getting remote bucket details: bucket not found")
}
return &models.RemoteBucket{
AccessKey: &remoteBucket.Credentials.AccessKey,
RemoteARN: &remoteBucket.Arn,
SecretKey: remoteBucket.Credentials.SecretKey,
Service: "replication",
SourceBucket: &remoteBucket.SourceBucket,
Status: "",
TargetBucket: remoteBucket.TargetBucket,
TargetURL: remoteBucket.Endpoint,
}, nil
}
func deleteRemoteBucket(ctx context.Context, client MinioAdmin, sourceBucketName, arn string) error {
return client.removeRemoteBucket(ctx, sourceBucketName, arn)
}
func addRemoteBucket(ctx context.Context, client MinioAdmin, params models.CreateRemoteBucket) (string, error) {
TargetURL := *params.TargetURL
accessKey := *params.AccessKey
secretKey := *params.SecretKey
u, err := url.Parse(TargetURL)
if err != nil {
return "", errors.New("malformed Remote target URL")
}
secure := u.Scheme == "https"
host := u.Host
if u.Port() == "" {
port := 80
if secure {
port = 443
}
host = host + ":" + strconv.Itoa(port)
}
creds := &madmin.Credentials{AccessKey: accessKey, SecretKey: secretKey}
remoteBucket := &madmin.BucketTarget{
TargetBucket: *params.TargetBucket,
Secure: secure,
Credentials: creds,
Endpoint: host,
Path: "",
API: "s3v4",
Type: "replication",
Region: params.Region,
ReplicationSync: *params.SyncMode == "sync",
}
if *params.SyncMode == "async" {
remoteBucket.BandwidthLimit = params.Bandwidth
}
if params.HealthCheckPeriod > 0 {
remoteBucket.HealthCheckDuration = time.Duration(params.HealthCheckPeriod) * time.Second
}
bucketARN, err := client.addRemoteBucket(ctx, *params.SourceBucket, remoteBucket)
return bucketARN, err
}
func addBucketReplicationItem(ctx context.Context, session *models.Principal, minClient minioClient, bucketName, prefix, destinationARN string, repExistingObj, repDelMark, repDels, repMeta bool, tags string, priority int32, storageClass string) error {
// we will tolerate this call failing
cfg, err := minClient.getBucketReplication(ctx, bucketName)
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("error fetching replication configuration for bucket %s: %v", bucketName, err))
}
// add rule
maxPrio := 0
if priority <= 0 { // We pick next priority by default
for _, r := range cfg.Rules {
if r.Priority > maxPrio {
maxPrio = r.Priority
}
}
maxPrio++
} else { // User picked priority, we try to set this manually
maxPrio = int(priority)
}
clientIP := utils.ClientIPFromContext(ctx)
s3Client, err := newS3BucketClient(session, bucketName, prefix, clientIP)
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("error creating S3Client: %v", err))
return err
}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient{client: s3Client}
repDelMarkStatus := "disable"
if repDelMark {
repDelMarkStatus = "enable"
}
repDelsStatus := "disable"
if repDels {
repDelsStatus = "enable"
}
repMetaStatus := "disable"
if repMeta {
repMetaStatus = "enable"
}
existingRepStatus := "disable"
if repExistingObj {
existingRepStatus = "enable"
}
opts := replication.Options{
Priority: fmt.Sprintf("%d", maxPrio),
RuleStatus: "enable",
DestBucket: destinationARN,
Op: replication.AddOption,
TagString: tags,
ExistingObjectReplicate: existingRepStatus,
ReplicateDeleteMarkers: repDelMarkStatus,
ReplicateDeletes: repDelsStatus,
ReplicaSync: repMetaStatus,
StorageClass: storageClass,
}
err2 := mcClient.setReplication(ctx, &cfg, opts)
if err2 != nil {
ErrorWithContext(ctx, fmt.Errorf("error creating replication for bucket: %v", err2.Cause))
return err2.Cause
}
return nil
}
func editBucketReplicationItem(ctx context.Context, session *models.Principal, minClient minioClient, ruleID, bucketName, prefix, destinationARN string, ruleStatus, repDelMark, repDels, repMeta, existingObjectRep bool, tags string, priority int32, storageClass string) error {
// we will tolerate this call failing
cfg, err := minClient.getBucketReplication(ctx, bucketName)
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("error fetching replication configuration for bucket %s: %v", bucketName, err))
}
maxPrio := int(priority)
clientIP := utils.ClientIPFromContext(ctx)
s3Client, err := newS3BucketClient(session, bucketName, prefix, clientIP)
if err != nil {
return fmt.Errorf("error creating S3Client: %v", err)
}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient{client: s3Client}
ruleState := "disable"
if ruleStatus {
ruleState = "enable"
}
repDelMarkStatus := "disable"
if repDelMark {
repDelMarkStatus = "enable"
}
repDelsStatus := "disable"
if repDels {
repDelsStatus = "enable"
}
repMetaStatus := "disable"
if repMeta {
repMetaStatus = "enable"
}
existingRepStatus := "disable"
if existingObjectRep {
existingRepStatus = "enable"
}
opts := replication.Options{
ID: ruleID,
Priority: fmt.Sprintf("%d", maxPrio),
RuleStatus: ruleState,
DestBucket: destinationARN,
Op: replication.SetOption,
TagString: tags,
IsTagSet: true,
ExistingObjectReplicate: existingRepStatus,
ReplicateDeleteMarkers: repDelMarkStatus,
ReplicateDeletes: repDelsStatus,
ReplicaSync: repMetaStatus,
StorageClass: storageClass,
IsSCSet: true,
}
err2 := mcClient.setReplication(ctx, &cfg, opts)
if err2 != nil {
return fmt.Errorf("error modifying replication for bucket: %v", err2.Cause)
}
return nil
}
func setMultiBucketReplication(ctx context.Context, session *models.Principal, client MinioAdmin, minClient minioClient, params bucketApi.SetMultiBucketReplicationParams) []RemoteBucketResult {
bucketsRelation := params.Body.BucketsRelation
// Parallel remote bucket adding
parallelRemoteBucket := func(bucketRelationData *models.MultiBucketsRelation) chan RemoteBucketResult {
remoteProc := make(chan RemoteBucketResult)
sourceBucket := bucketRelationData.OriginBucket
targetBucket := bucketRelationData.DestinationBucket
go func() {
defer close(remoteProc)
createRemoteBucketParams := models.CreateRemoteBucket{
AccessKey: params.Body.AccessKey,
SecretKey: params.Body.SecretKey,
SourceBucket: &sourceBucket,
TargetBucket: &targetBucket,
Region: params.Body.Region,
TargetURL: params.Body.TargetURL,
SyncMode: params.Body.SyncMode,
Bandwidth: params.Body.Bandwidth,
HealthCheckPeriod: params.Body.HealthCheckPeriod,
}
// We add the remote bucket reference & store the arn or errors returned
arn, err := addRemoteBucket(ctx, client, createRemoteBucketParams)
if err == nil {
err = addBucketReplicationItem(
ctx,
session,
minClient,
sourceBucket,
params.Body.Prefix,
arn,
params.Body.ReplicateExistingObjects,
params.Body.ReplicateDeleteMarkers,
params.Body.ReplicateDeletes,
params.Body.ReplicateMetadata,
params.Body.Tags,
params.Body.Priority,
params.Body.StorageClass)
}
errorReturn := ""
if err != nil {
deleteRemoteBucket(ctx, client, sourceBucket, arn)
errorReturn = err.Error()
}
retParams := RemoteBucketResult{
OriginBucket: sourceBucket,
TargetBucket: targetBucket,
Error: errorReturn,
}
remoteProc <- retParams
}()
return remoteProc
}
var bucketsManagement []chan RemoteBucketResult
for _, bucketName := range bucketsRelation {
// We generate the ARNs for each bucket
rBucket := parallelRemoteBucket(bucketName)
bucketsManagement = append(bucketsManagement, rBucket)
}
resultsList := []RemoteBucketResult{}
for _, result := range bucketsManagement {
res := <-result
resultsList = append(resultsList, res)
}
return resultsList
}
func setMultiBucketReplicationResponse(session *models.Principal, params bucketApi.SetMultiBucketReplicationParams) (*models.MultiBucketResponseState, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
}
adminClient := AdminClient{Client: mAdmin}
mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest))
if err != nil {
return nil, ErrorWithContext(ctx, fmt.Errorf("error creating MinIO Client: %v", err))
}
// create a minioClient interface implementation
// defining the client to be used
mnClient := minioClient{client: mClient}
replicationResults := setMultiBucketReplication(ctx, session, adminClient, mnClient, params)
if replicationResults == nil {
return nil, ErrorWithContext(ctx, errors.New("error setting buckets replication"))
}
resParsed := []*models.MultiBucketResponseItem{}
for _, repResult := range replicationResults {
responseItem := models.MultiBucketResponseItem{
ErrorString: repResult.Error,
OriginBucket: repResult.OriginBucket,
TargetBucket: repResult.TargetBucket,
}
resParsed = append(resParsed, &responseItem)
}
resultsParsed := models.MultiBucketResponseState{
ReplicationState: resParsed,
}
return &resultsParsed, nil
}
func listExternalBucketsResponse(params bucketApi.ListExternalBucketsParams) (*models.ListBucketsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
remoteAdmin, err := newAdminFromCreds(*params.Body.AccessKey, *params.Body.SecretKey, *params.Body.TargetURL, *params.Body.UseTLS)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return listExternalBuckets(ctx, AdminClient{Client: remoteAdmin})
}
func listExternalBuckets(ctx context.Context, client MinioAdmin) (*models.ListBucketsResponse, *CodedAPIError) {
buckets, err := getAccountBuckets(ctx, client)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.ListBucketsResponse{
Buckets: buckets,
Total: int64(len(buckets)),
}, nil
}
func getARNFromID(conf *replication.Config, rule string) string {
for i := range conf.Rules {
if conf.Rules[i].ID == rule {
return conf.Rules[i].Destination.Bucket
}
}
return ""
}
func getARNsFromIDs(conf *replication.Config, rules []string) []string {
temp := make(map[string]string)
for i := range conf.Rules {
temp[conf.Rules[i].ID] = conf.Rules[i].Destination.Bucket
}
var retval []string
for i := range rules {
if val, ok := temp[rules[i]]; ok {
retval = append(retval, val)
}
}
return retval
}
func deleteReplicationRule(ctx context.Context, session *models.Principal, bucketName, ruleID string) error {
clientIP := utils.ClientIPFromContext(ctx)
mClient, err := newMinioClient(session, clientIP)
if err != nil {
return fmt.Errorf("error creating MinIO Client: %v", err)
}
// create a minioClient interface implementation
// defining the client to be used
minClient := minioClient{client: mClient}
cfg, err := minClient.getBucketReplication(ctx, bucketName)
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
}
s3Client, err := newS3BucketClient(session, bucketName, "", clientIP)
if err != nil {
return fmt.Errorf("error creating S3Client: %v", err)
}
mAdmin, err := NewMinioAdminClient(ctx, session)
if err != nil {
return fmt.Errorf("error creating Admin Client: %v", err)
}
admClient := AdminClient{Client: mAdmin}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient{client: s3Client}
opts := replication.Options{
ID: ruleID,
Op: replication.RemoveOption,
}
err2 := mcClient.setReplication(ctx, &cfg, opts)
if err2 != nil {
return err2.Cause
}
// Replication rule was successfully deleted. We remove remote bucket
err3 := deleteRemoteBucket(ctx, admClient, bucketName, getARNFromID(&cfg, ruleID))
if err3 != nil {
return err3
}
return nil
}
func deleteAllReplicationRules(ctx context.Context, session *models.Principal, bucketName string) error {
clientIP := utils.ClientIPFromContext(ctx)
s3Client, err := newS3BucketClient(session, bucketName, "", clientIP)
if err != nil {
return fmt.Errorf("error creating S3Client: %v", err)
}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient{client: s3Client}
mClient, err := newMinioClient(session, clientIP)
if err != nil {
return fmt.Errorf("error creating MinIO Client: %v", err)
}
// create a minioClient interface implementation
// defining the client to be used
minClient := minioClient{client: mClient}
cfg, err := minClient.getBucketReplication(ctx, bucketName)
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
}
mAdmin, err := NewMinioAdminClient(ctx, session)
if err != nil {
return fmt.Errorf("error creating Admin Client: %v", err)
}
admClient := AdminClient{Client: mAdmin}
err2 := mcClient.deleteAllReplicationRules(ctx)
if err2 != nil {
return err2.ToGoError()
}
for i := range cfg.Rules {
err3 := deleteRemoteBucket(ctx, admClient, bucketName, cfg.Rules[i].Destination.Bucket)
if err3 != nil {
return err3
}
}
return nil
}
func deleteSelectedReplicationRules(ctx context.Context, session *models.Principal, bucketName string, rules []string) error {
clientIP := utils.ClientIPFromContext(ctx)
mClient, err := newMinioClient(session, clientIP)
if err != nil {
return fmt.Errorf("error creating MinIO Client: %v", err)
}
// create a minioClient interface implementation
// defining the client to be used
minClient := minioClient{client: mClient}
cfg, err := minClient.getBucketReplication(ctx, bucketName)
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
}
s3Client, err := newS3BucketClient(session, bucketName, "", clientIP)
if err != nil {
return fmt.Errorf("error creating S3Client: %v", err)
}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient{client: s3Client}
mAdmin, err := NewMinioAdminClient(ctx, session)
if err != nil {
return fmt.Errorf("error creating Admin Client: %v", err)
}
admClient := AdminClient{Client: mAdmin}
ARNs := getARNsFromIDs(&cfg, rules)
for i := range rules {
opts := replication.Options{
ID: rules[i],
Op: replication.RemoveOption,
}
err2 := mcClient.setReplication(ctx, &cfg, opts)
if err2 != nil {
return err2.Cause
}
// In case replication rule was deleted successfully, we remove the remote bucket ARN
err3 := deleteRemoteBucket(ctx, admClient, bucketName, ARNs[i])
if err3 != nil {
return err3
}
}
return nil
}
func deleteReplicationRuleResponse(session *models.Principal, params bucketApi.DeleteBucketReplicationRuleParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
ctx = context.WithValue(ctx, utils.ContextClientIP, getClientIP(params.HTTPRequest))
err := deleteReplicationRule(ctx, session, params.BucketName, params.RuleID)
if err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func deleteBucketReplicationRulesResponse(session *models.Principal, params bucketApi.DeleteAllReplicationRulesParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
ctx = context.WithValue(ctx, utils.ContextClientIP, getClientIP(params.HTTPRequest))
err := deleteAllReplicationRules(ctx, session, params.BucketName)
if err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func deleteSelectedReplicationRulesResponse(session *models.Principal, params bucketApi.DeleteSelectedReplicationRulesParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
ctx = context.WithValue(ctx, utils.ContextClientIP, getClientIP(params.HTTPRequest))
err := deleteSelectedReplicationRules(ctx, session, params.BucketName, params.Rules.Rules)
if err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func updateBucketReplicationResponse(session *models.Principal, params bucketApi.UpdateMultiBucketReplicationParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest))
if err != nil {
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minClient := minioClient{client: mClient}
err = editBucketReplicationItem(
ctx,
session,
minClient,
params.RuleID,
params.BucketName,
params.Body.Prefix,
params.Body.Arn,
params.Body.RuleState,
params.Body.ReplicateDeleteMarkers,
params.Body.ReplicateDeletes,
params.Body.ReplicateMetadata,
params.Body.ReplicateExistingObjects,
params.Body.Tags,
params.Body.Priority,
params.Body.StorageClass)
if err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}

View File

@@ -1,386 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/minio/console/pkg/utils"
"github.com/go-openapi/swag"
"github.com/minio/console/api/operations"
bucketApi "github.com/minio/console/api/operations/bucket"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type RemoteBucketsTestSuite struct {
suite.Suite
assert *assert.Assertions
currentServer string
isServerSet bool
server *httptest.Server
adminClient AdminClientMock
minioClient minioClientMock
mockRemoteBucket *models.RemoteBucket
mockBucketTarget *madmin.BucketTarget
mockListBuckets *models.ListBucketsResponse
}
func (suite *RemoteBucketsTestSuite) SetupSuite() {
suite.assert = assert.New(suite.T())
suite.adminClient = AdminClientMock{}
suite.minioClient = minioClientMock{}
suite.mockObjects()
}
func (suite *RemoteBucketsTestSuite) mockObjects() {
suite.mockListBuckets = &models.ListBucketsResponse{
Buckets: []*models.Bucket{},
Total: 0,
}
suite.mockRemoteBucket = &models.RemoteBucket{
AccessKey: swag.String("accessKey"),
SecretKey: "secretKey",
RemoteARN: swag.String("remoteARN"),
Service: "replication",
SourceBucket: swag.String("sourceBucket"),
TargetBucket: "targetBucket",
TargetURL: "targetURL",
Status: "",
}
suite.mockBucketTarget = &madmin.BucketTarget{
Credentials: &madmin.Credentials{
AccessKey: *suite.mockRemoteBucket.AccessKey,
SecretKey: suite.mockRemoteBucket.SecretKey,
},
Arn: *suite.mockRemoteBucket.RemoteARN,
SourceBucket: *suite.mockRemoteBucket.SourceBucket,
TargetBucket: suite.mockRemoteBucket.TargetBucket,
Endpoint: suite.mockRemoteBucket.TargetURL,
}
}
func (suite *RemoteBucketsTestSuite) SetupTest() {
suite.server = httptest.NewServer(http.HandlerFunc(suite.serverHandler))
suite.currentServer, suite.isServerSet = os.LookupEnv(ConsoleMinIOServer)
os.Setenv(ConsoleMinIOServer, suite.server.URL)
}
func (suite *RemoteBucketsTestSuite) serverHandler(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(400)
}
func (suite *RemoteBucketsTestSuite) TearDownSuite() {
}
func (suite *RemoteBucketsTestSuite) TearDownTest() {
if suite.isServerSet {
os.Setenv(ConsoleMinIOServer, suite.currentServer)
} else {
os.Unsetenv(ConsoleMinIOServer)
}
}
func (suite *RemoteBucketsTestSuite) TestRegisterRemoteBucketsHandlers() {
api := &operations.ConsoleAPI{}
suite.assertHandlersAreNil(api)
registerAdminBucketRemoteHandlers(api)
suite.assertHandlersAreNotNil(api)
}
func (suite *RemoteBucketsTestSuite) assertHandlersAreNil(api *operations.ConsoleAPI) {
suite.assert.Nil(api.BucketListRemoteBucketsHandler)
suite.assert.Nil(api.BucketRemoteBucketDetailsHandler)
suite.assert.Nil(api.BucketDeleteRemoteBucketHandler)
suite.assert.Nil(api.BucketAddRemoteBucketHandler)
suite.assert.Nil(api.BucketSetMultiBucketReplicationHandler)
suite.assert.Nil(api.BucketListExternalBucketsHandler)
suite.assert.Nil(api.BucketDeleteBucketReplicationRuleHandler)
suite.assert.Nil(api.BucketDeleteAllReplicationRulesHandler)
suite.assert.Nil(api.BucketDeleteSelectedReplicationRulesHandler)
suite.assert.Nil(api.BucketUpdateMultiBucketReplicationHandler)
}
func (suite *RemoteBucketsTestSuite) assertHandlersAreNotNil(api *operations.ConsoleAPI) {
suite.assert.NotNil(api.BucketListRemoteBucketsHandler)
suite.assert.NotNil(api.BucketRemoteBucketDetailsHandler)
suite.assert.NotNil(api.BucketDeleteRemoteBucketHandler)
suite.assert.NotNil(api.BucketAddRemoteBucketHandler)
suite.assert.NotNil(api.BucketSetMultiBucketReplicationHandler)
suite.assert.NotNil(api.BucketListExternalBucketsHandler)
suite.assert.NotNil(api.BucketDeleteBucketReplicationRuleHandler)
suite.assert.NotNil(api.BucketDeleteAllReplicationRulesHandler)
suite.assert.NotNil(api.BucketDeleteSelectedReplicationRulesHandler)
suite.assert.NotNil(api.BucketUpdateMultiBucketReplicationHandler)
}
func (suite *RemoteBucketsTestSuite) TestListRemoteBucketsHandlerWithError() {
params, api := suite.initListRemoteBucketsRequest()
response := api.BucketListRemoteBucketsHandler.Handle(params, &models.Principal{})
_, ok := response.(*bucketApi.ListRemoteBucketsDefault)
suite.assert.True(ok)
}
func (suite *RemoteBucketsTestSuite) initListRemoteBucketsRequest() (params bucketApi.ListRemoteBucketsParams, api operations.ConsoleAPI) {
registerAdminBucketRemoteHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *RemoteBucketsTestSuite) TestListRemoteBucketsWithoutError() {
ctx := context.Background()
minioListRemoteBucketsMock = func(_ context.Context, _, _ string) (targets []madmin.BucketTarget, err error) {
return []madmin.BucketTarget{{
Credentials: &madmin.Credentials{
AccessKey: "accessKey",
SecretKey: "secretKey",
},
}}, nil
}
res, err := listRemoteBuckets(ctx, &suite.adminClient)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *RemoteBucketsTestSuite) TestRemoteBucketDetailsHandlerWithError() {
params, api := suite.initRemoteBucketDetailsRequest()
response := api.BucketRemoteBucketDetailsHandler.Handle(params, &models.Principal{})
_, ok := response.(*bucketApi.RemoteBucketDetailsDefault)
suite.assert.True(ok)
}
func (suite *RemoteBucketsTestSuite) initRemoteBucketDetailsRequest() (params bucketApi.RemoteBucketDetailsParams, api operations.ConsoleAPI) {
registerAdminBucketRemoteHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *RemoteBucketsTestSuite) TestGetRemoteBucketWithoutError() {
ctx := context.Background()
minioGetRemoteBucketMock = func(_ context.Context, _, _ string) (targets *madmin.BucketTarget, err error) {
return suite.mockBucketTarget, nil
}
res, err := getRemoteBucket(ctx, &suite.adminClient, "bucketName")
suite.assert.Nil(err)
suite.assert.NotNil(res)
suite.assert.Equal(suite.mockRemoteBucket, res)
}
func (suite *RemoteBucketsTestSuite) TestDeleteRemoteBucketHandlerWithError() {
params, api := suite.initDeleteRemoteBucketRequest()
response := api.BucketDeleteRemoteBucketHandler.Handle(params, &models.Principal{})
_, ok := response.(*bucketApi.DeleteRemoteBucketDefault)
suite.assert.True(ok)
}
func (suite *RemoteBucketsTestSuite) initDeleteRemoteBucketRequest() (params bucketApi.DeleteRemoteBucketParams, api operations.ConsoleAPI) {
registerAdminBucketRemoteHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *RemoteBucketsTestSuite) TestAddRemoteBucketHandlerWithError() {
params, api := suite.initAddRemoteBucketRequest()
response := api.BucketAddRemoteBucketHandler.Handle(params, &models.Principal{})
_, ok := response.(*bucketApi.AddRemoteBucketDefault)
suite.assert.True(ok)
}
func (suite *RemoteBucketsTestSuite) initAddRemoteBucketRequest() (params bucketApi.AddRemoteBucketParams, api operations.ConsoleAPI) {
registerAdminBucketRemoteHandlers(&api)
url := "^&*&^%^"
accessKey := "accessKey"
secretKey := "secretKey"
params.HTTPRequest = &http.Request{}
params.Body = &models.CreateRemoteBucket{
TargetURL: &url,
AccessKey: &accessKey,
SecretKey: &secretKey,
}
return params, api
}
func (suite *RemoteBucketsTestSuite) TestAddRemoteBucketWithoutError() {
ctx := context.Background()
minioAddRemoteBucketMock = func(_ context.Context, _ string, _ *madmin.BucketTarget) (string, error) {
return "bucketName", nil
}
url := "https://localhost"
accessKey := "accessKey"
secretKey := "secretKey"
targetBucket := "targetBucket"
syncMode := "async"
sourceBucket := "sourceBucket"
data := models.CreateRemoteBucket{
TargetURL: &url,
TargetBucket: &targetBucket,
AccessKey: &accessKey,
SecretKey: &secretKey,
SyncMode: &syncMode,
HealthCheckPeriod: 10,
SourceBucket: &sourceBucket,
}
res, err := addRemoteBucket(ctx, &suite.adminClient, data)
suite.assert.NotNil(res)
suite.assert.Nil(err)
}
func (suite *RemoteBucketsTestSuite) TestSetMultiBucketReplicationHandlerWithError() {
params, api := suite.initSetMultiBucketReplicationRequest()
response := api.BucketSetMultiBucketReplicationHandler.Handle(params, &models.Principal{})
_, ok := response.(*bucketApi.SetMultiBucketReplicationOK)
suite.assert.True(ok)
}
func (suite *RemoteBucketsTestSuite) initSetMultiBucketReplicationRequest() (params bucketApi.SetMultiBucketReplicationParams, api operations.ConsoleAPI) {
registerAdminBucketRemoteHandlers(&api)
accessKey := "accessKey"
secretKey := "secretKey"
targetURL := "https://localhost"
syncMode := "async"
params.HTTPRequest = &http.Request{}
params.Body = &models.MultiBucketReplication{
BucketsRelation: []*models.MultiBucketsRelation{{}},
AccessKey: &accessKey,
SecretKey: &secretKey,
Region: "region",
TargetURL: &targetURL,
SyncMode: &syncMode,
Bandwidth: 10,
HealthCheckPeriod: 10,
}
return params, api
}
func (suite *RemoteBucketsTestSuite) TestListExternalBucketsHandlerWithError() {
params, api := suite.initListExternalBucketsRequest()
response := api.BucketListExternalBucketsHandler.Handle(params, &models.Principal{})
_, ok := response.(*bucketApi.ListExternalBucketsDefault)
suite.assert.True(ok)
}
func (suite *RemoteBucketsTestSuite) initListExternalBucketsRequest() (params bucketApi.ListExternalBucketsParams, api operations.ConsoleAPI) {
registerAdminBucketRemoteHandlers(&api)
url := "http://localhost:9000"
accessKey := "accessKey"
secretKey := "secretKey"
tls := false
params.HTTPRequest = &http.Request{}
params.Body = &models.ListExternalBucketsParams{
TargetURL: &url,
AccessKey: &accessKey,
SecretKey: &secretKey,
UseTLS: &tls,
}
return params, api
}
func (suite *RemoteBucketsTestSuite) TestListExternalBucketsWithError() {
ctx := context.Background()
minioAccountInfoMock = func(_ context.Context) (madmin.AccountInfo, error) {
return madmin.AccountInfo{}, errors.New("error")
}
res, err := listExternalBuckets(ctx, &suite.adminClient)
suite.assert.NotNil(err)
suite.assert.Nil(res)
}
func (suite *RemoteBucketsTestSuite) TestListExternalBucketsWithoutError() {
ctx := context.Background()
minioAccountInfoMock = func(_ context.Context) (madmin.AccountInfo, error) {
return madmin.AccountInfo{
Buckets: []madmin.BucketAccessInfo{},
}, nil
}
res, err := listExternalBuckets(ctx, &suite.adminClient)
suite.assert.Nil(err)
suite.assert.NotNil(res)
suite.assert.Equal(suite.mockListBuckets, res)
}
func (suite *RemoteBucketsTestSuite) TestDeleteBucketReplicationRuleHandlerWithError() {
params, api := suite.initDeleteBucketReplicationRuleRequest()
response := api.BucketDeleteBucketReplicationRuleHandler.Handle(params, &models.Principal{})
_, ok := response.(*bucketApi.DeleteBucketReplicationRuleDefault)
suite.assert.True(ok)
}
func (suite *RemoteBucketsTestSuite) initDeleteBucketReplicationRuleRequest() (params bucketApi.DeleteBucketReplicationRuleParams, api operations.ConsoleAPI) {
registerAdminBucketRemoteHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *RemoteBucketsTestSuite) TestDeleteAllReplicationRulesHandlerWithError() {
params, api := suite.initDeleteAllReplicationRulesRequest()
response := api.BucketDeleteAllReplicationRulesHandler.Handle(params, &models.Principal{})
_, ok := response.(*bucketApi.DeleteAllReplicationRulesDefault)
suite.assert.True(ok)
}
func (suite *RemoteBucketsTestSuite) initDeleteAllReplicationRulesRequest() (params bucketApi.DeleteAllReplicationRulesParams, api operations.ConsoleAPI) {
registerAdminBucketRemoteHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *RemoteBucketsTestSuite) TestDeleteSelectedReplicationRulesHandlerWithError() {
params, api := suite.initDeleteSelectedReplicationRulesRequest()
response := api.BucketDeleteSelectedReplicationRulesHandler.Handle(params, &models.Principal{})
_, ok := response.(*bucketApi.DeleteSelectedReplicationRulesDefault)
suite.assert.True(ok)
}
func (suite *RemoteBucketsTestSuite) initDeleteSelectedReplicationRulesRequest() (params bucketApi.DeleteSelectedReplicationRulesParams, api operations.ConsoleAPI) {
registerAdminBucketRemoteHandlers(&api)
params.HTTPRequest = &http.Request{}
params.BucketName = "bucketName"
params.Rules = &models.BucketReplicationRuleList{
Rules: []string{"rule1", "rule2"},
}
return params, api
}
func (suite *RemoteBucketsTestSuite) TestUpdateMultiBucketReplicationHandlerWithError() {
params, api := suite.initUpdateMultiBucketReplicationRequest()
response := api.BucketUpdateMultiBucketReplicationHandler.Handle(params, &models.Principal{})
_, ok := response.(*bucketApi.UpdateMultiBucketReplicationDefault)
suite.assert.True(ok)
}
func (suite *RemoteBucketsTestSuite) initUpdateMultiBucketReplicationRequest() (params bucketApi.UpdateMultiBucketReplicationParams, api operations.ConsoleAPI) {
registerAdminBucketRemoteHandlers(&api)
r := &http.Request{}
ctx := context.WithValue(context.Background(), utils.ContextClientIP, "127.0.0.1")
rc := r.WithContext(ctx)
params.HTTPRequest = rc
params.Body = &models.MultiBucketReplicationEdit{}
return params, api
}
func TestRemoteBuckets(t *testing.T) {
suite.Run(t, new(RemoteBucketsTestSuite))
}

View File

@@ -1,86 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
siteRepApi "github.com/minio/console/api/operations/site_replication"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
)
func registerSiteReplicationStatusHandler(api *operations.ConsoleAPI) {
api.SiteReplicationGetSiteReplicationStatusHandler = siteRepApi.GetSiteReplicationStatusHandlerFunc(func(params siteRepApi.GetSiteReplicationStatusParams, session *models.Principal) middleware.Responder {
rInfo, err := getSRStatusResponse(session, params)
if err != nil {
return siteRepApi.NewGetSiteReplicationStatusDefault(err.Code).WithPayload(err.APIError)
}
return siteRepApi.NewGetSiteReplicationStatusOK().WithPayload(rInfo)
})
}
func getSRStatusResponse(session *models.Principal, params siteRepApi.GetSiteReplicationStatusParams) (*models.SiteReplicationStatusResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
res, err := getSRStats(ctx, adminClient, params)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return res, nil
}
func getSRStats(ctx context.Context, client MinioAdmin, params siteRepApi.GetSiteReplicationStatusParams) (info *models.SiteReplicationStatusResponse, err error) {
srParams := madmin.SRStatusOptions{
Buckets: *params.Buckets,
Policies: *params.Policies,
Users: *params.Users,
Groups: *params.Groups,
}
if params.EntityType != nil && params.EntityValue != nil {
srParams.Entity = madmin.GetSREntityType(*params.EntityType)
srParams.EntityValue = *params.EntityValue
}
srInfo, err := client.getSiteReplicationStatus(ctx, srParams)
retInfo := models.SiteReplicationStatusResponse{
BucketStats: &srInfo.BucketStats,
Enabled: srInfo.Enabled,
GroupStats: srInfo.GroupStats,
MaxBuckets: int64(srInfo.MaxBuckets),
MaxGroups: int64(srInfo.MaxGroups),
MaxPolicies: int64(srInfo.MaxPolicies),
MaxUsers: int64(srInfo.MaxUsers),
PolicyStats: &srInfo.PolicyStats,
Sites: &srInfo.Sites,
StatsSummary: srInfo.StatsSummary,
UserStats: &srInfo.UserStats,
}
if err != nil {
return nil, err
}
return &retInfo, nil
}

View File

@@ -1,77 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"time"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
"github.com/minio/console/models"
svcApi "github.com/minio/console/api/operations/service"
)
func registerServiceHandlers(api *operations.ConsoleAPI) {
// Restart Service
api.ServiceRestartServiceHandler = svcApi.RestartServiceHandlerFunc(func(params svcApi.RestartServiceParams, session *models.Principal) middleware.Responder {
if err := getRestartServiceResponse(session, params); err != nil {
return svcApi.NewRestartServiceDefault(err.Code).WithPayload(err.APIError)
}
return svcApi.NewRestartServiceNoContent()
})
}
// serviceRestart - restarts the MinIO cluster
func serviceRestart(ctx context.Context, client MinioAdmin) error {
if err := client.serviceRestart(ctx); err != nil {
return err
}
// copy behavior from minio/mc mainAdminServiceRestart()
//
// Max. time taken by the server to shutdown is 5 seconds.
// This can happen when there are lot of s3 requests pending when the server
// receives a restart command.
// Sleep for 6 seconds and then check if the server is online.
time.Sleep(6 * time.Second)
// Fetch the service status of the specified MinIO server
_, err := client.serverInfo(ctx)
if err != nil {
return err
}
return nil
}
// getRestartServiceResponse performs serviceRestart()
func getRestartServiceResponse(session *models.Principal, params svcApi.RestartServiceParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
if err := serviceRestart(ctx, adminClient); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}

View File

@@ -1,68 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"errors"
"testing"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
)
func TestServiceRestart(t *testing.T) {
assert := assert.New(t)
adminClient := AdminClientMock{}
ctx := context.Background()
function := "serviceRestart()"
// Test-1 : serviceRestart() restart services no errors
// mock function response from listGroups()
minioServiceRestartMock = func(_ context.Context) error {
return nil
}
MinioServerInfoMock = func(_ context.Context) (madmin.InfoMessage, error) {
return madmin.InfoMessage{}, nil
}
if err := serviceRestart(ctx, adminClient); err != nil {
t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error())
}
// Test-2 : serviceRestart() returns errors on client.serviceRestart call
// and see that the errors is handled correctly and returned
minioServiceRestartMock = func(_ context.Context) error {
return errors.New("error")
}
MinioServerInfoMock = func(_ context.Context) (madmin.InfoMessage, error) {
return madmin.InfoMessage{}, nil
}
if err := serviceRestart(ctx, adminClient); assert.Error(err) {
assert.Equal("error", err.Error())
}
// Test-3 : serviceRestart() returns errors on client.serverInfo() call
// and see that the errors is handled correctly and returned
minioServiceRestartMock = func(_ context.Context) error {
return nil
}
MinioServerInfoMock = func(_ context.Context) (madmin.InfoMessage, error) {
return madmin.InfoMessage{}, errors.New("error on server info")
}
if err := serviceRestart(ctx, adminClient); assert.Error(err) {
assert.Equal("error on server info", err.Error())
}
}

View File

@@ -1,242 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
siteRepApi "github.com/minio/console/api/operations/site_replication"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
)
func registerSiteReplicationHandler(api *operations.ConsoleAPI) {
api.SiteReplicationGetSiteReplicationInfoHandler = siteRepApi.GetSiteReplicationInfoHandlerFunc(func(params siteRepApi.GetSiteReplicationInfoParams, session *models.Principal) middleware.Responder {
rInfo, err := getSRInfoResponse(session, params)
if err != nil {
return siteRepApi.NewGetSiteReplicationInfoDefault(err.Code).WithPayload(err.APIError)
}
return siteRepApi.NewGetSiteReplicationInfoOK().WithPayload(rInfo)
})
api.SiteReplicationSiteReplicationInfoAddHandler = siteRepApi.SiteReplicationInfoAddHandlerFunc(func(params siteRepApi.SiteReplicationInfoAddParams, session *models.Principal) middleware.Responder {
eInfo, err := getSRAddResponse(session, params)
if err != nil {
return siteRepApi.NewSiteReplicationInfoAddDefault(err.Code).WithPayload(err.APIError)
}
return siteRepApi.NewSiteReplicationInfoAddOK().WithPayload(eInfo)
})
api.SiteReplicationSiteReplicationRemoveHandler = siteRepApi.SiteReplicationRemoveHandlerFunc(func(params siteRepApi.SiteReplicationRemoveParams, session *models.Principal) middleware.Responder {
remRes, err := getSRRemoveResponse(session, params)
if err != nil {
return siteRepApi.NewSiteReplicationRemoveDefault(err.Code).WithPayload(err.APIError)
}
return siteRepApi.NewSiteReplicationRemoveNoContent().WithPayload(remRes)
})
api.SiteReplicationSiteReplicationEditHandler = siteRepApi.SiteReplicationEditHandlerFunc(func(params siteRepApi.SiteReplicationEditParams, session *models.Principal) middleware.Responder {
eInfo, err := getSREditResponse(session, params)
if err != nil {
return siteRepApi.NewSiteReplicationRemoveDefault(err.Code).WithPayload(err.APIError)
}
return siteRepApi.NewSiteReplicationEditOK().WithPayload(eInfo)
})
}
func getSRInfoResponse(session *models.Principal, params siteRepApi.GetSiteReplicationInfoParams) (*models.SiteReplicationInfoResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
res, err := getSRConfig(ctx, adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return res, nil
}
func getSRAddResponse(session *models.Principal, params siteRepApi.SiteReplicationInfoAddParams) (*models.SiteReplicationAddResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
res, err := addSiteReplication(ctx, adminClient, &params)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return res, nil
}
func getSREditResponse(session *models.Principal, params siteRepApi.SiteReplicationEditParams) (*models.PeerSiteEditResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
eRes, err := editSiteReplication(ctx, adminClient, &params)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return eRes, nil
}
func getSRRemoveResponse(session *models.Principal, params siteRepApi.SiteReplicationRemoveParams) (*models.PeerSiteRemoveResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
rRes, err := removeSiteReplication(ctx, adminClient, &params)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return rRes, nil
}
func getSRConfig(ctx context.Context, client MinioAdmin) (info *models.SiteReplicationInfoResponse, err error) {
srInfo, err := client.getSiteReplicationInfo(ctx)
if err != nil {
return nil, err
}
var sites []*models.PeerInfo
if len(srInfo.Sites) > 0 {
for _, s := range srInfo.Sites {
pInfo := &models.PeerInfo{
DeploymentID: s.DeploymentID,
Endpoint: s.Endpoint,
Name: s.Name,
}
sites = append(sites, pInfo)
}
}
res := &models.SiteReplicationInfoResponse{
Enabled: srInfo.Enabled,
Name: srInfo.Name,
ServiceAccountAccessKey: srInfo.ServiceAccountAccessKey,
Sites: sites,
}
return res, nil
}
func addSiteReplication(ctx context.Context, client MinioAdmin, params *siteRepApi.SiteReplicationInfoAddParams) (info *models.SiteReplicationAddResponse, err error) {
var rSites []madmin.PeerSite
if len(params.Body) > 0 {
for _, aSite := range params.Body {
pInfo := &madmin.PeerSite{
AccessKey: aSite.AccessKey,
Name: aSite.Name,
SecretKey: aSite.SecretKey,
Endpoint: aSite.Endpoint,
}
rSites = append(rSites, *pInfo)
}
}
qs := runtime.Values(params.HTTPRequest.URL.Query())
_, qhkReplicateILMExpiry, _ := qs.GetOK("replicate-ilm-expiry")
var opts madmin.SRAddOptions
if qhkReplicateILMExpiry {
opts.ReplicateILMExpiry = true
}
cc, err := client.addSiteReplicationInfo(ctx, rSites, opts)
if err != nil {
return nil, err
}
res := &models.SiteReplicationAddResponse{
ErrorDetail: cc.ErrDetail,
InitialSyncErrorMessage: cc.InitialSyncErrorMessage,
Status: cc.Status,
Success: cc.Success,
}
return res, nil
}
func editSiteReplication(ctx context.Context, client MinioAdmin, params *siteRepApi.SiteReplicationEditParams) (info *models.PeerSiteEditResponse, err error) {
peerSiteInfo := &madmin.PeerInfo{
Endpoint: params.Body.Endpoint, // only endpoint can be edited.
Name: params.Body.Name, // does not get updated.
DeploymentID: params.Body.DeploymentID, // readonly
}
qs := runtime.Values(params.HTTPRequest.URL.Query())
_, qhkDisableILMExpiryReplication, _ := qs.GetOK("disable-ilm-expiry-replication")
_, qhkEnableILMExpiryReplication, _ := qs.GetOK("enable-ilm-expiry-replication")
var opts madmin.SREditOptions
if qhkDisableILMExpiryReplication {
opts.DisableILMExpiryReplication = true
}
if qhkEnableILMExpiryReplication {
opts.EnableILMExpiryReplication = true
}
eRes, err := client.editSiteReplicationInfo(ctx, *peerSiteInfo, opts)
if err != nil {
return nil, err
}
editRes := &models.PeerSiteEditResponse{
ErrorDetail: eRes.ErrDetail,
Status: eRes.Status,
Success: eRes.Success,
}
return editRes, nil
}
func removeSiteReplication(ctx context.Context, client MinioAdmin, params *siteRepApi.SiteReplicationRemoveParams) (info *models.PeerSiteRemoveResponse, err error) {
delAll := params.Body.All
siteNames := params.Body.Sites
var req *madmin.SRRemoveReq
if delAll {
req = &madmin.SRRemoveReq{
RemoveAll: delAll,
}
} else {
req = &madmin.SRRemoveReq{
SiteNames: siteNames,
RemoveAll: delAll,
}
}
rRes, err := client.deleteSiteReplicationInfo(ctx, *req)
if err != nil {
return nil, err
}
removeRes := &models.PeerSiteRemoveResponse{
ErrorDetail: rRes.ErrDetail,
Status: rRes.Status,
}
return removeRes, nil
}

View File

@@ -1,255 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// These tests are for AdminAPI Tag based on swagger-console.yml
package api
import (
"context"
"fmt"
"testing"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
)
func TestGetSiteReplicationInfo(t *testing.T) {
assert := assert.New(t)
// mock minIO client
adminClient := AdminClientMock{}
function := "getSiteReplicationInfo()"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
retValueMock := madmin.SiteReplicationInfo{
Enabled: true,
Name: "site1",
Sites: []madmin.PeerInfo{
{
Endpoint: "http://localhost:9000",
Name: "site1",
DeploymentID: "12345",
},
{
Endpoint: "http://localhost:9001",
Name: "site2",
DeploymentID: "123456",
},
},
ServiceAccountAccessKey: "test-key",
}
expValueMock := &madmin.SiteReplicationInfo{
Enabled: true,
Name: "site1",
Sites: []madmin.PeerInfo{
{
Endpoint: "http://localhost:9000",
Name: "site1",
DeploymentID: "12345",
},
{
Endpoint: "http://localhost:9001",
Name: "site2",
DeploymentID: "123456",
},
},
ServiceAccountAccessKey: "test-key",
}
getSiteReplicationInfo = func(_ context.Context) (info *madmin.SiteReplicationInfo, err error) {
return &retValueMock, nil
}
srInfo, err := adminClient.getSiteReplicationInfo(ctx)
assert.Nil(err)
assert.Equal(expValueMock, srInfo, fmt.Sprintf("Failed on %s: length of lists is not the same", function))
}
func TestAddSiteReplicationInfo(t *testing.T) {
assert := assert.New(t)
// mock minIO client
adminClient := AdminClientMock{}
function := "addSiteReplicationInfo()"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
retValueMock := &madmin.ReplicateAddStatus{
Success: true,
Status: "success",
ErrDetail: "",
InitialSyncErrorMessage: "",
}
expValueMock := &madmin.ReplicateAddStatus{
Success: true,
Status: "success",
ErrDetail: "",
InitialSyncErrorMessage: "",
}
addSiteReplicationInfo = func(_ context.Context, _ []madmin.PeerSite) (res *madmin.ReplicateAddStatus, err error) {
return retValueMock, nil
}
sites := []madmin.PeerSite{
{
Name: "site1",
Endpoint: "http://localhost:9000",
AccessKey: "test",
SecretKey: "test",
},
{
Name: "site2",
Endpoint: "http://localhost:9001",
AccessKey: "test",
SecretKey: "test",
},
}
srInfo, err := adminClient.addSiteReplicationInfo(ctx, sites, madmin.SRAddOptions{})
assert.Nil(err)
assert.Equal(expValueMock, srInfo, fmt.Sprintf("Failed on %s: length of lists is not the same", function))
}
func TestEditSiteReplicationInfo(t *testing.T) {
assert := assert.New(t)
// mock minIO client
adminClient := AdminClientMock{}
function := "editSiteReplicationInfo()"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
retValueMock := &madmin.ReplicateEditStatus{
Success: true,
Status: "success",
ErrDetail: "",
}
expValueMock := &madmin.ReplicateEditStatus{
Success: true,
Status: "success",
ErrDetail: "",
}
editSiteReplicationInfo = func(_ context.Context, _ madmin.PeerInfo) (res *madmin.ReplicateEditStatus, err error) {
return retValueMock, nil
}
site := madmin.PeerInfo{
Name: "",
Endpoint: "",
DeploymentID: "12345",
}
srInfo, err := adminClient.editSiteReplicationInfo(ctx, site, madmin.SREditOptions{})
assert.Nil(err)
assert.Equal(expValueMock, srInfo, fmt.Sprintf("Failed on %s: length of lists is not the same", function))
}
func TestDeleteSiteReplicationInfo(t *testing.T) {
assert := assert.New(t)
// mock minIO client
adminClient := AdminClientMock{}
function := "deleteSiteReplicationInfo()"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
retValueMock := &madmin.ReplicateRemoveStatus{
Status: "success",
ErrDetail: "",
}
expValueMock := &madmin.ReplicateRemoveStatus{
Status: "success",
ErrDetail: "",
}
deleteSiteReplicationInfoMock = func(_ context.Context, _ madmin.SRRemoveReq) (res *madmin.ReplicateRemoveStatus, err error) {
return retValueMock, nil
}
remReq := madmin.SRRemoveReq{
SiteNames: []string{
"test1",
},
RemoveAll: false,
}
srInfo, err := adminClient.deleteSiteReplicationInfo(ctx, remReq)
assert.Nil(err)
assert.Equal(expValueMock, srInfo, fmt.Sprintf("Failed on %s: length of lists is not the same", function))
}
func TestSiteReplicationStatus(t *testing.T) {
assert := assert.New(t)
// mock minIO client
adminClient := AdminClientMock{}
function := "getSiteReplicationStatus()"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
retValueMock := madmin.SRStatusInfo{
Enabled: true,
MaxBuckets: 0,
MaxUsers: 0,
MaxGroups: 0,
MaxPolicies: 0,
Sites: nil,
StatsSummary: nil,
BucketStats: nil,
PolicyStats: nil,
UserStats: nil,
GroupStats: nil,
}
expValueMock := &madmin.SRStatusInfo{
Enabled: true,
MaxBuckets: 0,
MaxUsers: 0,
MaxGroups: 0,
MaxPolicies: 0,
Sites: nil,
StatsSummary: nil,
BucketStats: nil,
PolicyStats: nil,
UserStats: nil,
GroupStats: nil,
}
getSiteReplicationStatus = func(_ context.Context, _ madmin.SRStatusOptions) (info *madmin.SRStatusInfo, err error) {
return &retValueMock, nil
}
reqValues := madmin.SRStatusOptions{
Buckets: true,
Policies: true,
Users: true,
Groups: true,
}
srInfo, err := adminClient.getSiteReplicationStatus(ctx, reqValues)
if err != nil {
assert.Error(err)
}
assert.Equal(expValueMock, srInfo, fmt.Sprintf("Failed on %s: expected result is not same", function))
}

View File

@@ -1,118 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"github.com/dustin/go-humanize"
"github.com/minio/madmin-go/v3"
"github.com/minio/websocket"
)
// getSpeedtesthOptionsFromReq gets duration, size & concurrent requests from a websocket
// path come as : `/speedtest?duration=2h&size=12MiB&concurrent=10`
func getSpeedtestOptionsFromReq(req *http.Request) (*madmin.SpeedtestOpts, error) {
optionsSet := madmin.SpeedtestOpts{}
queryPairs := req.URL.Query()
paramDuration := queryPairs.Get("duration")
if paramDuration == "" {
paramDuration = "10s"
}
duration, err := time.ParseDuration(paramDuration)
if err != nil {
return nil, fmt.Errorf("unable to parse duration: %s", paramDuration)
}
if duration <= 0 {
return nil, fmt.Errorf("duration cannot be 0 or negative")
}
optionsSet.Duration = duration
paramSize := queryPairs.Get("size")
if paramSize == "" {
paramSize = "64MiB"
}
size, err := humanize.ParseBytes(paramSize)
if err != nil {
return nil, fmt.Errorf("unable to parse object size")
}
optionsSet.Size = int(size)
paramConcurrent := queryPairs.Get("concurrent")
if paramConcurrent == "" {
paramConcurrent = "32"
}
concurrent, err := strconv.Atoi(paramConcurrent)
if err != nil {
return nil, fmt.Errorf("invalid concurrent value: %s", paramConcurrent)
}
if concurrent <= 0 {
return nil, fmt.Errorf("concurrency cannot be '0' or negative")
}
optionsSet.Concurrency = concurrent
autotune := queryPairs.Get("autotune")
if autotune == "true" {
optionsSet.Autotune = true
}
return &optionsSet, nil
}
func startSpeedtest(ctx context.Context, conn WSConn, client MinioAdmin, speedtestOpts *madmin.SpeedtestOpts) error {
speedtestRes, err := client.speedtest(ctx, *speedtestOpts)
if err != nil {
LogError("error initializing speedtest: %v", err)
return err
}
for result := range speedtestRes {
// Serializing message
bytes, err := json.Marshal(result)
if err != nil {
LogError("error serializing json: %v", err)
return err
}
// Send Message through websocket connection
err = conn.writeMessage(websocket.TextMessage, bytes)
if err != nil {
LogError("error writing speedtest response: %v", err)
return err
}
}
return nil
}

View File

@@ -1,435 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package api
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"github.com/minio/console/pkg/utils"
xhttp "github.com/minio/console/pkg/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
subnetApi "github.com/minio/console/api/operations/subnet"
"github.com/minio/console/models"
"github.com/minio/console/pkg/subnet"
"github.com/minio/madmin-go/v3"
)
func registerSubnetHandlers(api *operations.ConsoleAPI) {
// Get subnet login handler
api.SubnetSubnetLoginHandler = subnetApi.SubnetLoginHandlerFunc(func(params subnetApi.SubnetLoginParams, session *models.Principal) middleware.Responder {
resp, err := GetSubnetLoginResponse(session, params)
if err != nil {
return subnetApi.NewSubnetLoginDefault(err.Code).WithPayload(err.APIError)
}
return subnetApi.NewSubnetLoginOK().WithPayload(resp)
})
// Get subnet login with MFA handler
api.SubnetSubnetLoginMFAHandler = subnetApi.SubnetLoginMFAHandlerFunc(func(params subnetApi.SubnetLoginMFAParams, session *models.Principal) middleware.Responder {
resp, err := GetSubnetLoginWithMFAResponse(session, params)
if err != nil {
return subnetApi.NewSubnetLoginMFADefault(err.Code).WithPayload(err.APIError)
}
return subnetApi.NewSubnetLoginMFAOK().WithPayload(resp)
})
// Get subnet register
api.SubnetSubnetRegisterHandler = subnetApi.SubnetRegisterHandlerFunc(func(params subnetApi.SubnetRegisterParams, session *models.Principal) middleware.Responder {
err := GetSubnetRegisterResponse(session, params)
if err != nil {
return subnetApi.NewSubnetRegisterDefault(err.Code).WithPayload(err.APIError)
}
return subnetApi.NewSubnetRegisterOK()
})
// Get subnet info
api.SubnetSubnetInfoHandler = subnetApi.SubnetInfoHandlerFunc(func(params subnetApi.SubnetInfoParams, session *models.Principal) middleware.Responder {
resp, err := GetSubnetInfoResponse(session, params)
if err != nil {
return subnetApi.NewSubnetInfoDefault(err.Code).WithPayload(err.APIError)
}
return subnetApi.NewSubnetInfoOK().WithPayload(resp)
})
// Get subnet registration token
api.SubnetSubnetRegTokenHandler = subnetApi.SubnetRegTokenHandlerFunc(func(params subnetApi.SubnetRegTokenParams, session *models.Principal) middleware.Responder {
resp, err := GetSubnetRegTokenResponse(session, params)
if err != nil {
return subnetApi.NewSubnetRegTokenDefault(err.Code).WithPayload(err.APIError)
}
return subnetApi.NewSubnetRegTokenOK().WithPayload(resp)
})
api.SubnetSubnetAPIKeyHandler = subnetApi.SubnetAPIKeyHandlerFunc(func(params subnetApi.SubnetAPIKeyParams, session *models.Principal) middleware.Responder {
resp, err := GetSubnetAPIKeyResponse(session, params)
if err != nil {
return subnetApi.NewSubnetAPIKeyDefault(err.Code).WithPayload(err.APIError)
}
return subnetApi.NewSubnetAPIKeyOK().WithPayload(resp)
})
}
const EnvSubnetLicense = "CONSOLE_SUBNET_LICENSE"
func SubnetRegisterWithAPIKey(ctx context.Context, minioClient MinioAdmin, apiKey string) (bool, error) {
serverInfo, err := minioClient.serverInfo(ctx)
if err != nil {
return false, err
}
clientIP := utils.ClientIPFromContext(ctx)
registerResult, err := subnet.Register(GetConsoleHTTPClient(clientIP), serverInfo, apiKey, "", "")
if err != nil {
return false, err
}
// Keep existing subnet proxy if exists
subnetKey, err := GetSubnetKeyFromMinIOConfig(ctx, minioClient)
if err != nil {
return false, err
}
configStr := fmt.Sprintf("subnet license=%s api_key=%s proxy=%s", registerResult.License, registerResult.APIKey, subnetKey.Proxy)
_, err = minioClient.setConfigKV(ctx, configStr)
if err != nil {
return false, err
}
// cluster registered correctly
return true, nil
}
func SubnetLogin(client xhttp.ClientI, username, password string) (string, string, error) {
tokens, err := subnet.Login(client, username, password)
if err != nil {
return "", "", err
}
if tokens.MfaToken != "" {
// user needs to complete login flow using mfa
return "", tokens.MfaToken, nil
}
if tokens.AccessToken != "" {
// register token to minio
return tokens.AccessToken, "", nil
}
return "", "", errors.New("something went wrong")
}
func GetSubnetLoginResponse(session *models.Principal, params subnetApi.SubnetLoginParams) (*models.SubnetLoginResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return subnetLoginResponse(ctx, AdminClient{Client: mAdmin}, params)
}
func subnetLoginResponse(ctx context.Context, minioClient MinioAdmin, params subnetApi.SubnetLoginParams) (*models.SubnetLoginResponse, *CodedAPIError) {
subnetHTTPClient, err := GetSubnetHTTPClient(ctx, minioClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
apiKey := params.Body.APIKey
if apiKey != "" {
registered, err := SubnetRegisterWithAPIKey(ctx, minioClient, apiKey)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.SubnetLoginResponse{
Registered: registered,
Organizations: []*models.SubnetOrganization{},
}, nil
}
username := params.Body.Username
password := params.Body.Password
if username != "" && password != "" {
token, mfa, err := SubnetLogin(subnetHTTPClient, username, password)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.SubnetLoginResponse{
MfaToken: mfa,
AccessToken: token,
Organizations: []*models.SubnetOrganization{},
}, nil
}
return nil, ErrorWithContext(ctx, ErrDefault)
}
type SubnetRegistration struct {
AccessToken string
MFAToken string
Organizations []models.SubnetOrganization
}
func SubnetLoginWithMFA(client xhttp.ClientI, username, mfaToken, otp string) (*models.SubnetLoginResponse, error) {
tokens, err := subnet.LoginWithMFA(client, username, mfaToken, otp)
if err != nil {
return nil, err
}
if tokens.AccessToken != "" {
organizations, errOrg := subnet.GetOrganizations(client, tokens.AccessToken)
if errOrg != nil {
return nil, errOrg
}
return &models.SubnetLoginResponse{
AccessToken: tokens.AccessToken,
Organizations: organizations,
}, nil
}
return nil, errors.New("something went wrong")
}
// GetSubnetHTTPClient will return a client with proxy if configured, otherwise will return the default console http client
func GetSubnetHTTPClient(ctx context.Context, minioClient MinioAdmin) (*xhttp.Client, error) {
clientIP := utils.ClientIPFromContext(ctx)
subnetKey, err := GetSubnetKeyFromMinIOConfig(ctx, minioClient)
if err != nil {
return nil, err
}
proxy := getSubnetProxy()
if subnetKey.Proxy != "" {
proxy = subnetKey.Proxy
}
tr := GlobalTransport.Clone()
if proxy != "" {
u, err := url.Parse(proxy)
if err != nil {
return nil, err
}
tr.Proxy = http.ProxyURL(u)
}
return &xhttp.Client{
Client: &http.Client{
Transport: &ConsoleTransport{
Transport: tr,
ClientIP: clientIP,
},
},
}, nil
}
func GetSubnetLoginWithMFAResponse(session *models.Principal, params subnetApi.SubnetLoginMFAParams) (*models.SubnetLoginResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
minioClient := AdminClient{Client: mAdmin}
return subnetLoginWithMFAResponse(ctx, minioClient, params)
}
func subnetLoginWithMFAResponse(ctx context.Context, minioClient MinioAdmin, params subnetApi.SubnetLoginMFAParams) (*models.SubnetLoginResponse, *CodedAPIError) {
subnetHTTPClient, err := GetSubnetHTTPClient(ctx, minioClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
resp, err := SubnetLoginWithMFA(subnetHTTPClient, *params.Body.Username, *params.Body.MfaToken, *params.Body.Otp)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return resp, nil
}
func GetSubnetKeyFromMinIOConfig(ctx context.Context, minioClient MinioAdmin) (*subnet.LicenseTokenConfig, error) {
buf, err := minioClient.getConfigKV(ctx, madmin.SubnetSubSys)
if err != nil {
return nil, err
}
subSysConfigs, err := madmin.ParseServerConfigOutput(string(buf))
if err != nil {
return nil, err
}
for _, scfg := range subSysConfigs {
if scfg.Target == "" {
res := subnet.LicenseTokenConfig{}
res.APIKey, _ = scfg.Lookup("api_key")
res.License, _ = scfg.Lookup("license")
res.Proxy, _ = scfg.Lookup("proxy")
return &res, nil
}
}
return nil, errors.New("unable to find subnet configuration")
}
func GetSubnetRegister(ctx context.Context, minioClient MinioAdmin, httpClient xhttp.ClientI, params subnetApi.SubnetRegisterParams) error {
serverInfo, err := minioClient.serverInfo(ctx)
if err != nil {
return err
}
registerResult, err := subnet.Register(httpClient, serverInfo, "", *params.Body.Token, *params.Body.AccountID)
if err != nil {
return err
}
// Keep existing subnet proxy if exists
subnetKey, err := GetSubnetKeyFromMinIOConfig(ctx, minioClient)
if err != nil {
return err
}
configStr := fmt.Sprintf("subnet license=%s api_key=%s proxy=%s", registerResult.License, registerResult.APIKey, subnetKey.Proxy)
_, err = minioClient.setConfigKV(ctx, configStr)
if err != nil {
return err
}
return nil
}
func GetSubnetRegisterResponse(session *models.Principal, params subnetApi.SubnetRegisterParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
return subnetRegisterResponse(ctx, adminClient, params)
}
func subnetRegisterResponse(ctx context.Context, minioClient MinioAdmin, params subnetApi.SubnetRegisterParams) *CodedAPIError {
subnetHTTPClient, err := GetSubnetHTTPClient(ctx, minioClient)
if err != nil {
return ErrorWithContext(ctx, err)
}
err = GetSubnetRegister(ctx, minioClient, subnetHTTPClient, params)
if err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
var ErrSubnetLicenseNotFound = errors.New("license not found")
func GetSubnetInfoResponse(session *models.Principal, params subnetApi.SubnetInfoParams) (*models.License, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
clientIP := utils.ClientIPFromContext(ctx)
client := &xhttp.Client{
Client: GetConsoleHTTPClient(clientIP),
}
// license gets seeded to us by MinIO
seededLicense := os.Getenv(EnvSubnetLicense)
// if it's missing, we will gracefully fallback to attempt to fetch it from MinIO
if seededLicense == "" {
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
configBytes, err := adminClient.getConfigKV(params.HTTPRequest.Context(), "subnet")
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
subSysConfigs, err := madmin.ParseServerConfigOutput(string(configBytes))
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// search for licese
for _, v := range subSysConfigs {
for _, sv := range v.KV {
if sv.Key == "license" {
seededLicense = sv.Value
}
}
}
}
// still empty means not found
if seededLicense == "" {
return nil, ErrorWithContext(ctx, ErrSubnetLicenseNotFound)
}
licenseInfo, err := getLicenseInfo(*client.Client, seededLicense)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
license := &models.License{
Email: licenseInfo.Email,
AccountID: licenseInfo.AccountID,
StorageCapacity: licenseInfo.StorageCapacity,
Plan: licenseInfo.Plan,
ExpiresAt: licenseInfo.ExpiresAt.String(),
Organization: licenseInfo.Organization,
}
return license, nil
}
func GetSubnetRegToken(ctx context.Context, minioClient MinioAdmin) (string, error) {
serverInfo, err := minioClient.serverInfo(ctx)
if err != nil {
return "", err
}
regInfo := subnet.GetClusterRegInfo(serverInfo)
regToken, err := subnet.GenerateRegToken(regInfo)
if err != nil {
return "", err
}
return regToken, nil
}
func GetSubnetRegTokenResponse(session *models.Principal, params subnetApi.SubnetRegTokenParams) (*models.SubnetRegTokenResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
return subnetRegTokenResponse(ctx, adminClient)
}
func subnetRegTokenResponse(ctx context.Context, minioClient MinioAdmin) (*models.SubnetRegTokenResponse, *CodedAPIError) {
token, err := GetSubnetRegToken(ctx, minioClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.SubnetRegTokenResponse{
RegToken: token,
}, nil
}
func GetSubnetAPIKeyResponse(session *models.Principal, params subnetApi.SubnetAPIKeyParams) (*models.APIKey, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
return subnetAPIKeyResponse(ctx, adminClient, params)
}
func subnetAPIKeyResponse(ctx context.Context, minioClient MinioAdmin, params subnetApi.SubnetAPIKeyParams) (*models.APIKey, *CodedAPIError) {
subnetHTTPClient, err := GetSubnetHTTPClient(ctx, minioClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
token := params.HTTPRequest.URL.Query().Get("token")
apiKey, err := subnet.GetAPIKey(subnetHTTPClient, token)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return &models.APIKey{APIKey: apiKey}, nil
}

View File

@@ -1,233 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"github.com/minio/console/api/operations"
subnetApi "github.com/minio/console/api/operations/subnet"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type AdminSubnetTestSuite struct {
suite.Suite
assert *assert.Assertions
currentServer string
isServerSet bool
server *httptest.Server
adminClient AdminClientMock
}
func (suite *AdminSubnetTestSuite) SetupSuite() {
suite.assert = assert.New(suite.T())
suite.adminClient = AdminClientMock{}
minioGetConfigKVMock = func(_ string) ([]byte, error) {
return []byte("subnet license=mock api_key=mock proxy=http://mock.com"), nil
}
MinioServerInfoMock = func(_ context.Context) (madmin.InfoMessage, error) {
return madmin.InfoMessage{Servers: []madmin.ServerProperties{{}}}, nil
}
}
func (suite *AdminSubnetTestSuite) SetupTest() {
suite.server = httptest.NewServer(http.HandlerFunc(suite.serverHandler))
suite.currentServer, suite.isServerSet = os.LookupEnv(ConsoleMinIOServer)
os.Setenv(ConsoleMinIOServer, suite.server.URL)
}
func (suite *AdminSubnetTestSuite) serverHandler(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(400)
}
func (suite *AdminSubnetTestSuite) TearDownSuite() {
}
func (suite *AdminSubnetTestSuite) TearDownTest() {
if suite.isServerSet {
os.Setenv(ConsoleMinIOServer, suite.currentServer)
} else {
os.Unsetenv(ConsoleMinIOServer)
}
}
func (suite *AdminSubnetTestSuite) TestRegisterSubnetHandlers() {
api := &operations.ConsoleAPI{}
suite.assertHandlersAreNil(api)
registerSubnetHandlers(api)
suite.assertHandlersAreNotNil(api)
}
func (suite *AdminSubnetTestSuite) assertHandlersAreNil(api *operations.ConsoleAPI) {
suite.assert.Nil(api.SubnetSubnetLoginHandler)
suite.assert.Nil(api.SubnetSubnetLoginMFAHandler)
suite.assert.Nil(api.SubnetSubnetRegisterHandler)
suite.assert.Nil(api.SubnetSubnetInfoHandler)
suite.assert.Nil(api.SubnetSubnetRegTokenHandler)
suite.assert.Nil(api.SubnetSubnetAPIKeyHandler)
}
func (suite *AdminSubnetTestSuite) assertHandlersAreNotNil(api *operations.ConsoleAPI) {
suite.assert.NotNil(api.SubnetSubnetLoginHandler)
suite.assert.NotNil(api.SubnetSubnetLoginMFAHandler)
suite.assert.NotNil(api.SubnetSubnetRegisterHandler)
suite.assert.NotNil(api.SubnetSubnetInfoHandler)
suite.assert.NotNil(api.SubnetSubnetRegTokenHandler)
suite.assert.NotNil(api.SubnetSubnetAPIKeyHandler)
}
func (suite *AdminSubnetTestSuite) TestSubnetLoginWithSubnetClientError() {
params, api := suite.initSubnetLoginRequest("", "", "")
response := api.SubnetSubnetLoginHandler.Handle(params, &models.Principal{})
_, ok := response.(*subnetApi.SubnetLoginDefault)
suite.assert.True(ok)
}
func (suite *AdminSubnetTestSuite) TestSubnetLoginResponseWithApiKeyError() {
params, _ := suite.initSubnetLoginRequest("mock", "", "")
res, err := subnetLoginResponse(context.TODO(), suite.adminClient, params)
suite.assert.NotNil(err)
suite.assert.Nil(res)
}
func (suite *AdminSubnetTestSuite) TestSubnetLoginResponseWithCredentialsError() {
params, _ := suite.initSubnetLoginRequest("", "mock", "mock")
res, err := subnetLoginResponse(context.TODO(), suite.adminClient, params)
suite.assert.NotNil(err)
suite.assert.Nil(res)
}
func (suite *AdminSubnetTestSuite) initSubnetLoginRequest(apiKey, username, password string) (params subnetApi.SubnetLoginParams, api operations.ConsoleAPI) {
registerSubnetHandlers(&api)
params.HTTPRequest = &http.Request{}
params.Body = &models.SubnetLoginRequest{}
params.Body.APIKey = apiKey
params.Body.Username = username
params.Body.Password = password
return params, api
}
func (suite *AdminSubnetTestSuite) TestSubnetLoginMFAWithSubnetClientError() {
params, api := suite.initSubnetLoginMFARequest("", "", "")
response := api.SubnetSubnetLoginMFAHandler.Handle(params, &models.Principal{})
_, ok := response.(*subnetApi.SubnetLoginMFADefault)
suite.assert.True(ok)
}
func (suite *AdminSubnetTestSuite) TestSubnetLoginWithMFAResponseError() {
params, _ := suite.initSubnetLoginMFARequest("mock", "mock", "mock")
res, err := subnetLoginWithMFAResponse(context.TODO(), suite.adminClient, params)
suite.assert.NotNil(err)
suite.assert.Nil(res)
}
func (suite *AdminSubnetTestSuite) initSubnetLoginMFARequest(username, mfaToken, otp string) (params subnetApi.SubnetLoginMFAParams, api operations.ConsoleAPI) {
registerSubnetHandlers(&api)
params.HTTPRequest = &http.Request{}
params.Body = &models.SubnetLoginMFARequest{}
params.Body.Username = &username
params.Body.MfaToken = &mfaToken
params.Body.Otp = &otp
return params, api
}
func (suite *AdminSubnetTestSuite) TestSubnetRegisterClientError() {
params, api := suite.initSubnetRegisterRequest("", "")
response := api.SubnetSubnetRegisterHandler.Handle(params, &models.Principal{})
_, ok := response.(*subnetApi.SubnetRegisterDefault)
suite.assert.True(ok)
}
func (suite *AdminSubnetTestSuite) TestSubnetRegisterResponseError() {
params, _ := suite.initSubnetRegisterRequest("mock", "mock")
err := subnetRegisterResponse(context.TODO(), suite.adminClient, params)
suite.assert.NotNil(err)
}
func (suite *AdminSubnetTestSuite) initSubnetRegisterRequest(token, accountID string) (params subnetApi.SubnetRegisterParams, api operations.ConsoleAPI) {
registerSubnetHandlers(&api)
params.HTTPRequest = &http.Request{}
params.Body = &models.SubnetRegisterRequest{}
params.Body.Token = &token
params.Body.AccountID = &accountID
return params, api
}
func (suite *AdminSubnetTestSuite) TestSubnetInfoError() {
params, api := suite.initSubnetInfoRequest()
response := api.SubnetSubnetInfoHandler.Handle(params, &models.Principal{})
_, ok := response.(*subnetApi.SubnetInfoDefault)
suite.assert.True(ok)
}
func (suite *AdminSubnetTestSuite) initSubnetInfoRequest() (params subnetApi.SubnetInfoParams, api operations.ConsoleAPI) {
registerSubnetHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *AdminSubnetTestSuite) TestSubnetRegTokenError() {
params, api := suite.initSubnetRegTokenRequest()
response := api.SubnetSubnetRegTokenHandler.Handle(params, &models.Principal{})
_, ok := response.(*subnetApi.SubnetRegTokenDefault)
suite.assert.True(ok)
}
func (suite *AdminSubnetTestSuite) TestSubnetRegTokenResponse() {
res, err := subnetRegTokenResponse(context.TODO(), suite.adminClient)
suite.assert.Nil(err)
suite.assert.NotEqual("", res)
}
func (suite *AdminSubnetTestSuite) initSubnetRegTokenRequest() (params subnetApi.SubnetRegTokenParams, api operations.ConsoleAPI) {
registerSubnetHandlers(&api)
params.HTTPRequest = &http.Request{}
return params, api
}
func (suite *AdminSubnetTestSuite) TestSubnetAPIKeyWithClientError() {
params, api := suite.initSubnetAPIKeyRequest()
response := api.SubnetSubnetAPIKeyHandler.Handle(params, &models.Principal{})
_, ok := response.(*subnetApi.SubnetAPIKeyDefault)
suite.assert.True(ok)
}
func (suite *AdminSubnetTestSuite) TestSubnetAPIKeyResponseError() {
params, _ := suite.initSubnetAPIKeyRequest()
res, err := subnetAPIKeyResponse(context.TODO(), suite.adminClient, params)
suite.assert.NotNil(err)
suite.assert.Nil(res)
}
func (suite *AdminSubnetTestSuite) initSubnetAPIKeyRequest() (params subnetApi.SubnetAPIKeyParams, api operations.ConsoleAPI) {
registerSubnetHandlers(&api)
params.HTTPRequest = &http.Request{}
params.HTTPRequest.URL = &url.URL{}
return params, api
}
func TestAdminSubnet(t *testing.T) {
suite.Run(t, new(AdminSubnetTestSuite))
}

View File

@@ -1,411 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"encoding/base64"
"strconv"
"github.com/dustin/go-humanize"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
tieringApi "github.com/minio/console/api/operations/tiering"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
)
func registerAdminTiersHandlers(api *operations.ConsoleAPI) {
// return a list of notification endpoints
api.TieringTiersListHandler = tieringApi.TiersListHandlerFunc(func(params tieringApi.TiersListParams, session *models.Principal) middleware.Responder {
tierList, err := getTiersResponse(session, params)
if err != nil {
return tieringApi.NewTiersListDefault(err.Code).WithPayload(err.APIError)
}
return tieringApi.NewTiersListOK().WithPayload(tierList)
})
// add a new tiers
api.TieringAddTierHandler = tieringApi.AddTierHandlerFunc(func(params tieringApi.AddTierParams, session *models.Principal) middleware.Responder {
err := getAddTierResponse(session, params)
if err != nil {
return tieringApi.NewAddTierDefault(err.Code).WithPayload(err.APIError)
}
return tieringApi.NewAddTierCreated()
})
// get a tier
api.TieringGetTierHandler = tieringApi.GetTierHandlerFunc(func(params tieringApi.GetTierParams, session *models.Principal) middleware.Responder {
notifEndpoints, err := getGetTierResponse(session, params)
if err != nil {
return tieringApi.NewGetTierDefault(err.Code).WithPayload(err.APIError)
}
return tieringApi.NewGetTierOK().WithPayload(notifEndpoints)
})
// edit credentials for a tier
api.TieringEditTierCredentialsHandler = tieringApi.EditTierCredentialsHandlerFunc(func(params tieringApi.EditTierCredentialsParams, session *models.Principal) middleware.Responder {
err := getEditTierCredentialsResponse(session, params)
if err != nil {
return tieringApi.NewEditTierCredentialsDefault(err.Code).WithPayload(err.APIError)
}
return tieringApi.NewEditTierCredentialsOK()
})
}
// getNotificationEndpoints invokes admin info and returns a list of notification endpoints
func getTiers(ctx context.Context, client MinioAdmin) (*models.TierListResponse, error) {
tiers, err := client.listTiers(ctx)
if err != nil {
return nil, err
}
tiersInfo, err := client.tierStats(ctx)
if err != nil {
return nil, err
}
var tiersList []*models.Tier
for _, tierData := range tiers {
// Default Tier Stats
stats := madmin.TierStats{
NumObjects: 0,
NumVersions: 0,
TotalSize: 0,
}
// We look for the correct tier stats & set the values.
for _, stat := range tiersInfo {
if stat.Name == tierData.Name {
stats = stat.Stats
break
}
}
switch tierData.Type {
case madmin.S3:
tiersList = append(tiersList, &models.Tier{
Type: models.TierTypeS3,
S3: &models.TierS3{
Accesskey: tierData.S3.AccessKey,
Bucket: tierData.S3.Bucket,
Endpoint: tierData.S3.Endpoint,
Name: tierData.Name,
Prefix: tierData.S3.Prefix,
Region: tierData.S3.Region,
Secretkey: tierData.S3.SecretKey,
Storageclass: tierData.S3.StorageClass,
Usage: humanize.IBytes(stats.TotalSize),
Objects: strconv.Itoa(stats.NumObjects),
Versions: strconv.Itoa(stats.NumVersions),
},
Status: client.verifyTierStatus(ctx, tierData.Name) == nil,
})
case madmin.MinIO:
tiersList = append(tiersList, &models.Tier{
Type: models.TierTypeMinio,
Minio: &models.TierMinio{
Accesskey: tierData.MinIO.AccessKey,
Bucket: tierData.MinIO.Bucket,
Endpoint: tierData.MinIO.Endpoint,
Name: tierData.Name,
Prefix: tierData.MinIO.Prefix,
Region: tierData.MinIO.Region,
Secretkey: tierData.MinIO.SecretKey,
Usage: humanize.IBytes(stats.TotalSize),
Objects: strconv.Itoa(stats.NumObjects),
Versions: strconv.Itoa(stats.NumVersions),
},
Status: client.verifyTierStatus(ctx, tierData.Name) == nil,
})
case madmin.GCS:
tiersList = append(tiersList, &models.Tier{
Type: models.TierTypeGcs,
Gcs: &models.TierGcs{
Bucket: tierData.GCS.Bucket,
Creds: tierData.GCS.Creds,
Endpoint: tierData.GCS.Endpoint,
Name: tierData.Name,
Prefix: tierData.GCS.Prefix,
Region: tierData.GCS.Region,
Usage: humanize.IBytes(stats.TotalSize),
Objects: strconv.Itoa(stats.NumObjects),
Versions: strconv.Itoa(stats.NumVersions),
},
Status: client.verifyTierStatus(ctx, tierData.Name) == nil,
})
case madmin.Azure:
tiersList = append(tiersList, &models.Tier{
Type: models.TierTypeAzure,
Azure: &models.TierAzure{
Accountkey: tierData.Azure.AccountKey,
Accountname: tierData.Azure.AccountName,
Bucket: tierData.Azure.Bucket,
Endpoint: tierData.Azure.Endpoint,
Name: tierData.Name,
Prefix: tierData.Azure.Prefix,
Region: tierData.Azure.Region,
Usage: humanize.IBytes(stats.TotalSize),
Objects: strconv.Itoa(stats.NumObjects),
Versions: strconv.Itoa(stats.NumVersions),
},
Status: client.verifyTierStatus(ctx, tierData.Name) == nil,
})
case madmin.Unsupported:
tiersList = append(tiersList, &models.Tier{
Type: models.TierTypeUnsupported,
Status: client.verifyTierStatus(ctx, tierData.Name) == nil,
})
}
}
// build response
return &models.TierListResponse{
Items: tiersList,
}, nil
}
// getTiersResponse returns a response with a list of tiers
func getTiersResponse(session *models.Principal, params tieringApi.TiersListParams) (*models.TierListResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
// serialize output
tiersResp, err := getTiers(ctx, adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return tiersResp, nil
}
func addTier(ctx context.Context, client MinioAdmin, params *tieringApi.AddTierParams) error {
var cfg *madmin.TierConfig
var err error
switch params.Body.Type {
case models.TierTypeS3:
cfg, err = madmin.NewTierS3(
params.Body.S3.Name,
params.Body.S3.Accesskey,
params.Body.S3.Secretkey,
params.Body.S3.Bucket,
madmin.S3Region(params.Body.S3.Region),
madmin.S3Prefix(params.Body.S3.Prefix),
madmin.S3Endpoint(params.Body.S3.Endpoint),
madmin.S3StorageClass(params.Body.S3.Storageclass),
)
if err != nil {
return err
}
case models.TierTypeMinio:
cfg, err = madmin.NewTierMinIO(
params.Body.Minio.Name,
params.Body.Minio.Endpoint,
params.Body.Minio.Accesskey,
params.Body.Minio.Secretkey,
params.Body.Minio.Bucket,
madmin.MinIORegion(params.Body.Minio.Region),
madmin.MinIOPrefix(params.Body.Minio.Prefix),
)
if err != nil {
return err
}
case models.TierTypeGcs:
gcsOpts := []madmin.GCSOptions{}
prefix := params.Body.Gcs.Prefix
if prefix != "" {
gcsOpts = append(gcsOpts, madmin.GCSPrefix(prefix))
}
region := params.Body.Gcs.Region
if region != "" {
gcsOpts = append(gcsOpts, madmin.GCSRegion(region))
}
base64Text := make([]byte, base64.StdEncoding.EncodedLen(len(params.Body.Gcs.Creds)))
l, _ := base64.StdEncoding.Decode(base64Text, []byte(params.Body.Gcs.Creds))
cfg, err = madmin.NewTierGCS(
params.Body.Gcs.Name,
base64Text[:l],
params.Body.Gcs.Bucket,
gcsOpts...,
)
if err != nil {
return err
}
case models.TierTypeAzure:
cfg, err = madmin.NewTierAzure(
params.Body.Azure.Name,
params.Body.Azure.Accountname,
params.Body.Azure.Accountkey,
params.Body.Azure.Bucket,
madmin.AzurePrefix(params.Body.Azure.Prefix),
madmin.AzureEndpoint(params.Body.Azure.Endpoint),
madmin.AzureRegion(params.Body.Azure.Region),
)
if err != nil {
return err
}
case models.TierTypeUnsupported:
cfg = &madmin.TierConfig{
Type: madmin.Unsupported,
}
}
err = client.addTier(ctx, cfg)
if err != nil {
return err
}
return nil
}
// getAddTierResponse returns the response of admin tier
func getAddTierResponse(session *models.Principal, params tieringApi.AddTierParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
// serialize output
errTier := addTier(ctx, adminClient, &params)
if errTier != nil {
return ErrorWithContext(ctx, errTier)
}
return nil
}
func getTier(ctx context.Context, client MinioAdmin, params *tieringApi.GetTierParams) (*models.Tier, error) {
tiers, err := client.listTiers(ctx)
if err != nil {
return nil, err
}
for i := range tiers {
switch tiers[i].Type {
case madmin.S3:
if params.Type != models.TierTypeS3 || tiers[i].Name != params.Name {
continue
}
return &models.Tier{
Type: models.TierTypeS3,
S3: &models.TierS3{
Accesskey: tiers[i].S3.AccessKey,
Bucket: tiers[i].S3.Bucket,
Endpoint: tiers[i].S3.Endpoint,
Name: tiers[i].Name,
Prefix: tiers[i].S3.Prefix,
Region: tiers[i].S3.Region,
Secretkey: tiers[i].S3.SecretKey,
Storageclass: tiers[i].S3.StorageClass,
},
}, err
case madmin.GCS:
if params.Type != models.TierTypeGcs || tiers[i].Name != params.Name {
continue
}
return &models.Tier{
Type: models.TierTypeGcs,
Gcs: &models.TierGcs{
Bucket: tiers[i].GCS.Bucket,
Creds: tiers[i].GCS.Creds,
Endpoint: tiers[i].GCS.Endpoint,
Name: tiers[i].Name,
Prefix: tiers[i].GCS.Prefix,
Region: tiers[i].GCS.Region,
},
}, nil
case madmin.Azure:
if params.Type != models.TierTypeAzure || tiers[i].Name != params.Name {
continue
}
return &models.Tier{
Type: models.TierTypeAzure,
Azure: &models.TierAzure{
Accountkey: tiers[i].Azure.AccountKey,
Accountname: tiers[i].Azure.AccountName,
Bucket: tiers[i].Azure.Bucket,
Endpoint: tiers[i].Azure.Endpoint,
Name: tiers[i].Name,
Prefix: tiers[i].Azure.Prefix,
Region: tiers[i].Azure.Region,
},
}, nil
}
}
// build response
return nil, ErrNotFound
}
// getGetTierResponse returns a tier
func getGetTierResponse(session *models.Principal, params tieringApi.GetTierParams) (*models.Tier, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
// serialize output
addTierResp, err := getTier(ctx, adminClient, &params)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return addTierResp, nil
}
func editTierCredentials(ctx context.Context, client MinioAdmin, params *tieringApi.EditTierCredentialsParams) error {
base64Text := make([]byte, base64.StdEncoding.EncodedLen(len(params.Body.Creds)))
l, err := base64.StdEncoding.Decode(base64Text, []byte(params.Body.Creds))
if err != nil {
return err
}
creds := madmin.TierCreds{
AccessKey: params.Body.AccessKey,
SecretKey: params.Body.SecretKey,
CredsJSON: base64Text[:l],
}
return client.editTierCreds(ctx, params.Name, creds)
}
// getEditTierCredentialsResponse returns the result of editing credentials for a tier
func getEditTierCredentialsResponse(session *models.Principal, params tieringApi.EditTierCredentialsParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
// serialize output
err = editTierCredentials(ctx, adminClient, &params)
if err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}

View File

@@ -1,230 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"errors"
"fmt"
"testing"
tieringApi "github.com/minio/console/api/operations/tiering"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
)
func TestGetTiers(t *testing.T) {
assert := assert.New(t)
// mock minIO client
adminClient := AdminClientMock{}
function := "getTiers()"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : getBucketLifecycle() get list of tiers
// mock lifecycle response from MinIO
returnListMock := []*madmin.TierConfig{
{
Version: "V1",
Type: madmin.TierType(0),
Name: "S3 Tier",
S3: &madmin.TierS3{
Endpoint: "https://s3tier.test.com/",
AccessKey: "Access Key",
SecretKey: "Secret Key",
Bucket: "buckets3",
Prefix: "pref1",
Region: "us-west-1",
StorageClass: "TT1",
},
},
}
returnStatsMock := []madmin.TierInfo{
{
Name: "STANDARD",
Type: "internal",
Stats: madmin.TierStats{NumObjects: 2, NumVersions: 2, TotalSize: 228915},
},
{
Name: "S3 Tier",
Type: "s3",
Stats: madmin.TierStats{NumObjects: 0, NumVersions: 0, TotalSize: 0},
},
}
expectedOutput := &models.TierListResponse{
Items: []*models.Tier{
{
Type: "S3",
S3: &models.TierS3{
Accesskey: "Access Key",
Secretkey: "Secret Key",
Bucket: "buckets3",
Endpoint: "https://s3tier.test.com/",
Name: "S3 Tier",
Prefix: "pref1",
Region: "us-west-1",
Storageclass: "TT1",
Usage: "0 B",
Objects: "0",
Versions: "0",
},
},
},
}
minioListTiersMock = func(_ context.Context) ([]*madmin.TierConfig, error) {
return returnListMock, nil
}
minioTierStatsMock = func(_ context.Context) ([]madmin.TierInfo, error) {
return returnStatsMock, nil
}
tiersList, err := getTiers(ctx, adminClient)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// verify length of tiers list is correct
assert.Equal(len(tiersList.Items), len(returnListMock), fmt.Sprintf("Failed on %s: length of lists is not the same", function))
for i, conf := range returnListMock {
switch conf.Type {
case madmin.TierType(0):
// S3
assert.Equal(expectedOutput.Items[i].S3.Name, conf.Name)
assert.Equal(expectedOutput.Items[i].S3.Bucket, conf.S3.Bucket)
assert.Equal(expectedOutput.Items[i].S3.Prefix, conf.S3.Prefix)
assert.Equal(expectedOutput.Items[i].S3.Accesskey, conf.S3.AccessKey)
assert.Equal(expectedOutput.Items[i].S3.Secretkey, conf.S3.SecretKey)
assert.Equal(expectedOutput.Items[i].S3.Endpoint, conf.S3.Endpoint)
assert.Equal(expectedOutput.Items[i].S3.Region, conf.S3.Region)
assert.Equal(expectedOutput.Items[i].S3.Storageclass, conf.S3.StorageClass)
case madmin.TierType(1):
// Azure
assert.Equal(expectedOutput.Items[i].Azure.Name, conf.Name)
assert.Equal(expectedOutput.Items[i].Azure.Bucket, conf.Azure.Bucket)
assert.Equal(expectedOutput.Items[i].Azure.Prefix, conf.Azure.Prefix)
assert.Equal(expectedOutput.Items[i].Azure.Accountkey, conf.Azure.AccountKey)
assert.Equal(expectedOutput.Items[i].Azure.Accountname, conf.Azure.AccountName)
assert.Equal(expectedOutput.Items[i].Azure.Endpoint, conf.Azure.Endpoint)
assert.Equal(expectedOutput.Items[i].Azure.Region, conf.Azure.Region)
case madmin.TierType(2):
// GCS
assert.Equal(expectedOutput.Items[i].Gcs.Name, conf.Name)
assert.Equal(expectedOutput.Items[i].Gcs.Bucket, conf.GCS.Bucket)
assert.Equal(expectedOutput.Items[i].Gcs.Prefix, conf.GCS.Prefix)
assert.Equal(expectedOutput.Items[i].Gcs.Creds, conf.GCS.Creds)
assert.Equal(expectedOutput.Items[i].Gcs.Endpoint, conf.GCS.Endpoint)
assert.Equal(expectedOutput.Items[i].Gcs.Region, conf.GCS.Region)
}
}
// Test-2 : getBucketLifecycle() list is empty
returnListMockT2 := []*madmin.TierConfig{}
minioListTiersMock = func(_ context.Context) ([]*madmin.TierConfig, error) {
return returnListMockT2, nil
}
tiersListT2, err := getTiers(ctx, adminClient)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
if len(tiersListT2.Items) != 0 {
t.Errorf("Failed on %s:, returned list was not empty", function)
}
}
func TestAddTier(t *testing.T) {
assert := assert.New(t)
// mock minIO client
adminClient := AdminClientMock{}
function := "addTier()"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1: addTier() add new Tier
minioAddTiersMock = func(_ context.Context, _ *madmin.TierConfig) error {
return nil
}
paramsToAdd := tieringApi.AddTierParams{
Body: &models.Tier{
Type: "S3",
S3: &models.TierS3{
Accesskey: "TestAK",
Bucket: "bucket1",
Endpoint: "https://test.com/",
Name: "TIERS3",
Prefix: "Pr1",
Region: "us-west-1",
Secretkey: "SecretK",
Storageclass: "STCLASS",
},
},
}
err := addTier(ctx, adminClient, &paramsToAdd)
assert.Equal(nil, err, fmt.Sprintf("Failed on %s: Error returned", function))
// Test-2: addTier() error adding Tier
minioAddTiersMock = func(_ context.Context, _ *madmin.TierConfig) error {
return errors.New("error setting new tier")
}
err2 := addTier(ctx, adminClient, &paramsToAdd)
assert.Equal(errors.New("error setting new tier"), err2, fmt.Sprintf("Failed on %s: Error returned", function))
}
func TestUpdateTierCreds(t *testing.T) {
assert := assert.New(t)
// mock minIO client
adminClient := AdminClientMock{}
function := "editTierCredentials()"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1: editTierCredentials() update Tier configuration
minioEditTiersMock = func(_ context.Context, _ string, _ madmin.TierCreds) error {
return nil
}
params := &tieringApi.EditTierCredentialsParams{
Name: "TESTTIER",
Body: &models.TierCredentialsRequest{
AccessKey: "New Key",
SecretKey: "Secret Key",
},
}
err := editTierCredentials(ctx, adminClient, params)
assert.Equal(nil, err, fmt.Sprintf("Failed on %s: Error returned", function))
// Test-2: editTierCredentials() update Tier configuration failure
minioEditTiersMock = func(_ context.Context, _ string, _ madmin.TierCreds) error {
return errors.New("error message")
}
errT2 := editTierCredentials(ctx, adminClient, params)
assert.Equal(errors.New("error message"), errT2, fmt.Sprintf("Failed on %s: Error returned", function))
}

View File

@@ -1,154 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"encoding/json"
"net/http"
"strings"
"time"
"github.com/minio/madmin-go/v3"
"github.com/minio/websocket"
)
// shortTraceMsg Short trace record
type shortTraceMsg struct {
Host string `json:"host"`
Time string `json:"time"`
Client string `json:"client"`
CallStats callStats `json:"callStats"`
FuncName string `json:"api"`
Path string `json:"path"`
Query string `json:"query"`
StatusCode int `json:"statusCode"`
StatusMsg string `json:"statusMsg"`
}
type callStats struct {
Rx int `json:"rx"`
Tx int `json:"tx"`
Duration string `json:"duration"`
Ttfb string `json:"timeToFirstByte"`
}
// trace filters
func matchTrace(opts TraceRequest, traceInfo madmin.ServiceTraceInfo) bool {
statusCode := int(opts.statusCode)
method := opts.method
funcName := opts.funcName
apiPath := opts.path
if statusCode == 0 && method == "" && funcName == "" && apiPath == "" {
// no specific filtering found trace all the requests
return true
}
// Filter request path if passed by the user
if apiPath != "" {
pathToLookup := strings.ToLower(apiPath)
pathFromTrace := strings.ToLower(traceInfo.Trace.Path)
return strings.Contains(pathFromTrace, pathToLookup)
}
// Filter response status codes if passed by the user
if statusCode > 0 && traceInfo.Trace.HTTP != nil {
statusCodeFromTrace := traceInfo.Trace.HTTP.RespInfo.StatusCode
return statusCodeFromTrace == statusCode
}
// Filter request method if passed by the user
if method != "" && traceInfo.Trace.HTTP != nil {
methodFromTrace := traceInfo.Trace.HTTP.ReqInfo.Method
return methodFromTrace == method
}
if funcName != "" {
funcToLookup := strings.ToLower(funcName)
funcFromTrace := strings.ToLower(traceInfo.Trace.FuncName)
return strings.Contains(funcFromTrace, funcToLookup)
}
return true
}
// startTraceInfo starts trace of the servers
func startTraceInfo(ctx context.Context, conn WSConn, client MinioAdmin, opts TraceRequest) error {
// Start listening on all trace activity.
traceCh := client.serviceTrace(ctx, opts.threshold, opts.s3, opts.internal, opts.storage, opts.os, opts.onlyErrors)
for {
select {
case <-ctx.Done():
return nil
case traceInfo, ok := <-traceCh:
// zero value returned because the channel is closed and empty
if !ok {
return nil
}
if traceInfo.Err != nil {
LogError("error on serviceTrace: %v", traceInfo.Err)
return traceInfo.Err
}
if matchTrace(opts, traceInfo) {
// Serialize message to be sent
traceInfoBytes, err := json.Marshal(shortTrace(&traceInfo))
if err != nil {
LogError("error on json.Marshal: %v", err)
return err
}
// Send Message through websocket connection
err = conn.writeMessage(websocket.TextMessage, traceInfoBytes)
if err != nil {
LogError("error writeMessage: %v", err)
return err
}
}
}
}
}
// shortTrace creates a shorter Trace Info message.
// Same implementation as github/minio/mc/cmd/admin-trace.go
func shortTrace(info *madmin.ServiceTraceInfo) shortTraceMsg {
t := info.Trace
s := shortTraceMsg{}
s.Time = t.Time.Format(time.RFC3339)
s.Path = t.Path
s.FuncName = t.FuncName
s.CallStats.Duration = t.Duration.String()
if info.Trace.HTTP != nil {
s.Query = t.HTTP.ReqInfo.RawQuery
s.StatusCode = t.HTTP.RespInfo.StatusCode
s.StatusMsg = http.StatusText(t.HTTP.RespInfo.StatusCode)
s.CallStats.Rx = t.HTTP.CallStats.InputBytes
s.CallStats.Tx = t.HTTP.CallStats.OutputBytes
s.CallStats.Ttfb = t.HTTP.CallStats.TimeToFirstByte.String()
if host, ok := t.HTTP.ReqInfo.Headers["Host"]; ok {
s.Host = strings.Join(host, "")
}
cSlice := strings.Split(t.HTTP.ReqInfo.Client, ":")
s.Client = cSlice[0]
}
return s
}

View File

@@ -1,119 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"encoding/json"
"fmt"
"testing"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
)
func TestAdminTrace(t *testing.T) {
assert := assert.New(t)
adminClient := AdminClientMock{}
mockWSConn := mockConn{}
function := "startTraceInfo(ctx, )"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
testReceiver := make(chan shortTraceMsg, 5)
textToReceive := "test"
testStreamSize := 5
isClosed := false // testReceiver is closed?
// Test-1: Serve Trace with no errors until trace finishes sending
// define mock function behavior for minio server Trace
minioServiceTraceMock = func(_ context.Context, _ int64, _, _, _, _, _ bool) <-chan madmin.ServiceTraceInfo {
ch := make(chan madmin.ServiceTraceInfo)
// Only success, start a routine to start reading line by line.
go func(ch chan<- madmin.ServiceTraceInfo) {
defer close(ch)
lines := make([]int, testStreamSize)
// mocking sending 5 lines of info
for range lines {
info := madmin.TraceInfo{
FuncName: textToReceive,
}
ch <- madmin.ServiceTraceInfo{Trace: info}
}
}(ch)
return ch
}
writesCount := 1
// mock connection WriteMessage() no error
connWriteMessageMock = func(_ int, data []byte) error {
// emulate that receiver gets the message written
var t shortTraceMsg
_ = json.Unmarshal(data, &t)
if writesCount == testStreamSize {
// for testing we need to close the receiver channel
if !isClosed {
close(testReceiver)
isClosed = true
}
return nil
}
testReceiver <- t
writesCount++
return nil
}
if err := startTraceInfo(ctx, mockWSConn, adminClient, TraceRequest{s3: true, internal: true, storage: true, os: true, onlyErrors: false}); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// check that the TestReceiver got the same number of data from trace.
for i := range testReceiver {
assert.Equal(textToReceive, i.FuncName)
}
// Test-2: if error happens while writing, return error
connWriteMessageMock = func(_ int, _ []byte) error {
return fmt.Errorf("error on write")
}
if err := startTraceInfo(ctx, mockWSConn, adminClient, TraceRequest{}); assert.Error(err) {
assert.Equal("error on write", err.Error())
}
// Test-3: error happens on serviceTrace Minio, trace should stop
// and error shall be returned.
minioServiceTraceMock = func(_ context.Context, _ int64, _, _, _, _, _ bool) <-chan madmin.ServiceTraceInfo {
ch := make(chan madmin.ServiceTraceInfo)
// Only success, start a routine to start reading line by line.
go func(ch chan<- madmin.ServiceTraceInfo) {
defer close(ch)
lines := make([]int, 2)
// mocking sending 5 lines of info
for range lines {
info := madmin.TraceInfo{
NodeName: "test",
}
ch <- madmin.ServiceTraceInfo{Trace: info}
}
ch <- madmin.ServiceTraceInfo{Err: fmt.Errorf("error on trace")}
}(ch)
return ch
}
connWriteMessageMock = func(_ int, _ []byte) error {
return nil
}
if err := startTraceInfo(ctx, mockWSConn, adminClient, TraceRequest{}); assert.Error(err) {
assert.Equal("error on trace", err.Error())
}
}

View File

@@ -1,742 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"context"
"fmt"
"sort"
"strings"
"github.com/minio/console/pkg/utils"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
accountApi "github.com/minio/console/api/operations/account"
bucketApi "github.com/minio/console/api/operations/bucket"
userApi "github.com/minio/console/api/operations/user"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
iampolicy "github.com/minio/pkg/v2/policy"
)
// Policy evaluated constants
const (
Unknown = 0
Allow = 1
Deny = -1
)
func registerUsersHandlers(api *operations.ConsoleAPI) {
// List Users
api.UserListUsersHandler = userApi.ListUsersHandlerFunc(func(params userApi.ListUsersParams, session *models.Principal) middleware.Responder {
listUsersResponse, err := getListUsersResponse(session, params)
if err != nil {
return userApi.NewListUsersDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewListUsersOK().WithPayload(listUsersResponse)
})
// Add User
api.UserAddUserHandler = userApi.AddUserHandlerFunc(func(params userApi.AddUserParams, session *models.Principal) middleware.Responder {
userResponse, err := getUserAddResponse(session, params)
if err != nil {
return userApi.NewAddUserDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewAddUserCreated().WithPayload(userResponse)
})
// Remove User
api.UserRemoveUserHandler = userApi.RemoveUserHandlerFunc(func(params userApi.RemoveUserParams, session *models.Principal) middleware.Responder {
err := getRemoveUserResponse(session, params)
if err != nil {
return userApi.NewRemoveUserDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewRemoveUserNoContent()
})
// Update User-Groups
api.UserUpdateUserGroupsHandler = userApi.UpdateUserGroupsHandlerFunc(func(params userApi.UpdateUserGroupsParams, session *models.Principal) middleware.Responder {
userUpdateResponse, err := getUpdateUserGroupsResponse(session, params)
if err != nil {
return userApi.NewUpdateUserGroupsDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewUpdateUserGroupsOK().WithPayload(userUpdateResponse)
})
// Get User
api.UserGetUserInfoHandler = userApi.GetUserInfoHandlerFunc(func(params userApi.GetUserInfoParams, session *models.Principal) middleware.Responder {
userInfoResponse, err := getUserInfoResponse(session, params)
if err != nil {
return userApi.NewGetUserInfoDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewGetUserInfoOK().WithPayload(userInfoResponse)
})
// Update User
api.UserUpdateUserInfoHandler = userApi.UpdateUserInfoHandlerFunc(func(params userApi.UpdateUserInfoParams, session *models.Principal) middleware.Responder {
userUpdateResponse, err := getUpdateUserResponse(session, params)
if err != nil {
return userApi.NewUpdateUserInfoDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewUpdateUserInfoOK().WithPayload(userUpdateResponse)
})
// Update User-Groups Bulk
api.UserBulkUpdateUsersGroupsHandler = userApi.BulkUpdateUsersGroupsHandlerFunc(func(params userApi.BulkUpdateUsersGroupsParams, session *models.Principal) middleware.Responder {
err := getAddUsersListToGroupsResponse(session, params)
if err != nil {
return userApi.NewBulkUpdateUsersGroupsDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewBulkUpdateUsersGroupsOK()
})
api.BucketListUsersWithAccessToBucketHandler = bucketApi.ListUsersWithAccessToBucketHandlerFunc(func(params bucketApi.ListUsersWithAccessToBucketParams, session *models.Principal) middleware.Responder {
response, err := getListUsersWithAccessToBucketResponse(session, params)
if err != nil {
return bucketApi.NewListUsersWithAccessToBucketDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewListUsersWithAccessToBucketOK().WithPayload(response)
})
// Change User Password
api.AccountChangeUserPasswordHandler = accountApi.ChangeUserPasswordHandlerFunc(func(params accountApi.ChangeUserPasswordParams, session *models.Principal) middleware.Responder {
err := getChangeUserPasswordResponse(session, params)
if err != nil {
return accountApi.NewChangeUserPasswordDefault(err.Code).WithPayload(err.APIError)
}
return accountApi.NewChangeUserPasswordCreated()
})
// Check number of Service Accounts for listed users
api.UserCheckUserServiceAccountsHandler = userApi.CheckUserServiceAccountsHandlerFunc(func(params userApi.CheckUserServiceAccountsParams, session *models.Principal) middleware.Responder {
userSAList, err := getCheckUserSAResponse(session, params)
if err != nil {
return userApi.NewCheckUserServiceAccountsDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewCheckUserServiceAccountsOK().WithPayload(userSAList)
})
}
func listUsers(ctx context.Context, client MinioAdmin) ([]*models.User, error) {
// Get list of all users in the MinIO
// This call requires explicit authentication, no anonymous requests are
// allowed for listing users.
userMap, err := client.listUsers(ctx)
if err != nil {
return []*models.User{}, err
}
var users []*models.User
for accessKey, user := range userMap {
userElem := &models.User{
AccessKey: accessKey,
Status: string(user.Status),
Policy: strings.Split(user.PolicyName, ","),
MemberOf: user.MemberOf,
}
users = append(users, userElem)
}
return users, nil
}
// getListUsersResponse performs listUsers() and serializes it to the handler's output
func getListUsersResponse(session *models.Principal, params userApi.ListUsersParams) (*models.ListUsersResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
users, err := listUsers(ctx, adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// serialize output
listUsersResponse := &models.ListUsersResponse{
Users: users,
}
return listUsersResponse, nil
}
// addUser invokes adding a users on `MinioAdmin` and builds the response `models.User`
func addUser(ctx context.Context, client MinioAdmin, accessKey, secretKey *string, groups []string, policies []string) (*models.User, error) {
// Calls into MinIO to add a new user if there's an errors return it
if err := client.addUser(ctx, *accessKey, *secretKey); err != nil {
return nil, err
}
// set groups for the newly created user
var userWithGroups *models.User
if len(groups) > 0 {
var errUG error
userWithGroups, errUG = updateUserGroups(ctx, client, *accessKey, groups)
if errUG != nil {
return nil, errUG
}
}
// set policies for the newly created user
if len(policies) > 0 {
policyString := strings.Join(policies, ",")
if err := SetPolicy(ctx, client, policyString, *accessKey, "user"); err != nil {
return nil, err
}
}
memberOf := []string{}
status := "enabled"
if userWithGroups != nil {
memberOf = userWithGroups.MemberOf
status = userWithGroups.Status
}
userRet := &models.User{
AccessKey: *accessKey,
MemberOf: memberOf,
Policy: policies,
Status: status,
}
return userRet, nil
}
func getUserAddResponse(session *models.Principal, params userApi.AddUserParams) (*models.User, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
var userExists bool
_, err = adminClient.getUserInfo(ctx, *params.Body.AccessKey)
userExists = err == nil
if userExists {
return nil, ErrorWithContext(ctx, ErrNonUniqueAccessKey)
}
user, err := addUser(
ctx,
adminClient,
params.Body.AccessKey,
params.Body.SecretKey,
params.Body.Groups,
params.Body.Policies,
)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return user, nil
}
// removeUser invokes removing an user on `MinioAdmin`, then we return the response from API
func removeUser(ctx context.Context, client MinioAdmin, accessKey string) error {
return client.removeUser(ctx, accessKey)
}
func getRemoveUserResponse(session *models.Principal, params userApi.RemoveUserParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
userName, err := utils.DecodeBase64(params.Name)
if err != nil {
return ErrorWithContext(ctx, err)
}
if session.AccountAccessKey == userName {
return ErrorWithContext(ctx, ErrAvoidSelfAccountDelete)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
if err := removeUser(ctx, adminClient, userName); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
// getUserInfo calls MinIO server get the User Information
func getUserInfo(ctx context.Context, client MinioAdmin, accessKey string) (*madmin.UserInfo, error) {
userInfo, err := client.getUserInfo(ctx, accessKey)
if err != nil {
return nil, err
}
return &userInfo, nil
}
func getUserInfoResponse(session *models.Principal, params userApi.GetUserInfoParams) (*models.User, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
userName, err := utils.DecodeBase64(params.Name)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
user, err := getUserInfo(ctx, adminClient, userName)
if err != nil {
// User doesn't exist, return 404
if madmin.ToErrorResponse(err).Code == "XMinioAdminNoSuchUser" {
errorCode := 404
errorMessage := "User doesn't exist"
return nil, &CodedAPIError{Code: errorCode, APIError: &models.APIError{Message: errorMessage, DetailedMessage: err.Error()}}
}
return nil, ErrorWithContext(ctx, err)
}
var policies []string
if user.PolicyName == "" {
policies = []string{}
} else {
policies = strings.Split(user.PolicyName, ",")
}
hasPolicy := true
if len(policies) == 0 {
hasPolicy = false
for i := 0; i < len(user.MemberOf); i++ {
group, err := adminClient.getGroupDescription(ctx, user.MemberOf[i])
if err != nil {
continue
}
if group.Policy != "" {
hasPolicy = true
break
}
}
}
userInformation := &models.User{
AccessKey: userName,
MemberOf: user.MemberOf,
Policy: policies,
Status: string(user.Status),
HasPolicy: hasPolicy,
}
return userInformation, nil
}
// updateUserGroups invokes getUserInfo() to get the old groups from the user,
// then we merge the list with the new groups list to have a shorter iteration between groups and we do a comparison between the current and old groups.
// We delete or update the groups according the location in each list and send the user with the new groups from `MinioAdmin` to the client
func updateUserGroups(ctx context.Context, client MinioAdmin, user string, groupsToAssign []string) (*models.User, error) {
parallelUserUpdate := func(groupName string, originGroups []string) chan error {
chProcess := make(chan error)
go func() error {
defer close(chProcess)
// Compare if groupName is in the arrays
isGroupPersistent := IsElementInArray(groupsToAssign, groupName)
isInOriginGroups := IsElementInArray(originGroups, groupName)
if isGroupPersistent && isInOriginGroups { // Group is already assigned and doesn't need to be updated
chProcess <- nil
return nil
}
isRemove := false // User is added by default
// User is deleted from the group
if !isGroupPersistent {
isRemove = true
}
userToAddRemove := []string{user}
updateReturn := updateGroupMembers(ctx, client, groupName, userToAddRemove, isRemove)
chProcess <- updateReturn
return updateReturn
}()
return chProcess
}
userInfoOr, err := getUserInfo(ctx, client, user)
if err != nil {
return nil, err
}
memberOf := userInfoOr.MemberOf
mergedGroupArray := UniqueKeys(append(memberOf, groupsToAssign...))
var listOfUpdates []chan error
// Each group must be updated individually because there is no way to update all the groups at once for a user,
// we are using the same logic as 'mc admin group add' command
for _, groupN := range mergedGroupArray {
proc := parallelUserUpdate(groupN, memberOf)
listOfUpdates = append(listOfUpdates, proc)
}
channelHasError := false
for _, chanRet := range listOfUpdates {
locError := <-chanRet
if locError != nil {
channelHasError = true
}
}
if channelHasError {
errRt := errors.New(500, "there was an error updating the groups")
return nil, errRt
}
userInfo, err := getUserInfo(ctx, client, user)
if err != nil {
return nil, err
}
policies := strings.Split(userInfo.PolicyName, ",")
userReturn := &models.User{
AccessKey: user,
MemberOf: userInfo.MemberOf,
Policy: policies,
Status: string(userInfo.Status),
}
return userReturn, nil
}
func getUpdateUserGroupsResponse(session *models.Principal, params userApi.UpdateUserGroupsParams) (*models.User, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
userName, err := utils.DecodeBase64(params.Name)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
user, err := updateUserGroups(ctx, adminClient, userName, params.Body.Groups)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return user, nil
}
// setUserStatus invokes setUserStatus from madmin to update user status
func setUserStatus(ctx context.Context, client MinioAdmin, user string, status string) error {
var setStatus madmin.AccountStatus
switch status {
case "enabled":
setStatus = madmin.AccountEnabled
case "disabled":
setStatus = madmin.AccountDisabled
default:
return errors.New(500, "status not valid")
}
return client.setUserStatus(ctx, user, setStatus)
}
func getUpdateUserResponse(session *models.Principal, params userApi.UpdateUserInfoParams) (*models.User, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
userName, err := utils.DecodeBase64(params.Name)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
status := *params.Body.Status
groups := params.Body.Groups
if err := setUserStatus(ctx, adminClient, userName, status); err != nil {
return nil, ErrorWithContext(ctx, err)
}
userElem, errUG := updateUserGroups(ctx, adminClient, userName, groups)
if errUG != nil {
return nil, ErrorWithContext(ctx, errUG)
}
return userElem, nil
}
// addUsersListToGroups iterates over the user list & assigns the requested groups to each user.
func addUsersListToGroups(ctx context.Context, client MinioAdmin, usersToUpdate []string, groupsToAssign []string) error {
// We update each group with the complete usersList
parallelGroupsUpdate := func(groupToAssign string) chan error {
groupProcess := make(chan error)
go func() {
defer close(groupProcess)
// We add the users array to the group.
err := updateGroupMembers(ctx, client, groupToAssign, usersToUpdate, false)
groupProcess <- err
}()
return groupProcess
}
var groupsUpdateList []chan error
// We get each group name & add users accordingly
for _, groupName := range groupsToAssign {
// We update the group
proc := parallelGroupsUpdate(groupName)
groupsUpdateList = append(groupsUpdateList, proc)
}
errorsList := []string{} // We get the errors list because we want to have all errors at once.
for _, err := range groupsUpdateList {
errorFromUpdate := <-err // We store the errors to avoid Data Race
if errorFromUpdate != nil {
// If there is an errors, we store the errors strings so we can join them after we receive all errors
errorsList = append(errorsList, errorFromUpdate.Error()) // We wait until all the channels have been closed.
}
}
// If there are errors, we throw the final errors with the errors inside
if len(errorsList) > 0 {
errGen := fmt.Errorf("error in users-groups assignation: %q", strings.Join(errorsList, ","))
return errGen
}
return nil
}
func getAddUsersListToGroupsResponse(session *models.Principal, params userApi.BulkUpdateUsersGroupsParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
usersList := params.Body.Users
groupsList := params.Body.Groups
if err := addUsersListToGroups(ctx, adminClient, usersList, groupsList); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func getListUsersWithAccessToBucketResponse(session *models.Principal, params bucketApi.ListUsersWithAccessToBucketParams) ([]string, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
list, err := listUsersWithAccessToBucket(ctx, adminClient, params.Bucket)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return list, nil
}
func policyAllowsAndMatchesBucket(policy *iampolicy.Policy, bucket string) int {
policyStatements := policy.Statements
for i := 0; i < len(policyStatements); i++ {
resources := policyStatements[i].Resources
effect := policyStatements[i].Effect
if resources.Match(bucket, map[string][]string{}) {
if effect.IsValid() {
if effect.IsAllowed(true) {
return Allow
}
return Deny
}
}
}
return Unknown
}
func listUsersWithAccessToBucket(ctx context.Context, adminClient MinioAdmin, bucket string) ([]string, error) {
users, err := adminClient.listUsers(ctx)
if err != nil {
return nil, err
}
var retval []string
akHasAccess := make(map[string]struct{})
akIsDenied := make(map[string]struct{})
for k, v := range users {
for _, policyName := range strings.Split(v.PolicyName, ",") {
policyName = strings.TrimSpace(policyName)
if policyName == "" {
continue
}
policy, err := adminClient.getPolicy(ctx, policyName)
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("unable to fetch policy %s: %v", policyName, err))
continue
}
if _, ok := akIsDenied[k]; !ok {
switch policyAllowsAndMatchesBucket(policy, bucket) {
case Allow:
if _, ok := akHasAccess[k]; !ok {
akHasAccess[k] = struct{}{}
}
case Deny:
akIsDenied[k] = struct{}{}
delete(akHasAccess, k)
}
}
}
}
groups, err := adminClient.listGroups(ctx)
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("unable to list groups: %v", err))
return retval, nil
}
for _, groupName := range groups {
info, err := groupInfo(ctx, adminClient, groupName)
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("unable to fetch group info %s: %v", groupName, err))
continue
}
policy, err := adminClient.getPolicy(ctx, info.Policy)
if err != nil {
ErrorWithContext(ctx, fmt.Errorf("unable to fetch group policy %s: %v", info.Policy, err))
continue
}
for _, member := range info.Members {
if _, ok := akIsDenied[member]; !ok {
switch policyAllowsAndMatchesBucket(policy, bucket) {
case Allow:
if _, ok := akHasAccess[member]; !ok {
akHasAccess[member] = struct{}{}
}
case Deny:
akIsDenied[member] = struct{}{}
delete(akHasAccess, member)
}
}
}
}
for k := range akHasAccess {
retval = append(retval, k)
}
sort.Strings(retval)
return retval, nil
}
// changeUserPassword changes password of selectedUser to newSecretKey
func changeUserPassword(ctx context.Context, client MinioAdmin, selectedUser string, newSecretKey string) error {
return client.changePassword(ctx, selectedUser, newSecretKey)
}
// getChangeUserPasswordResponse will change the password of selctedUser to newSecretKey
func getChangeUserPasswordResponse(session *models.Principal, params accountApi.ChangeUserPasswordParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
// params will contain selectedUser and newSecretKey credentials for the user
user := *params.Body.SelectedUser
newSecretKey := *params.Body.NewSecretKey
// changes password of user to newSecretKey
if err := changeUserPassword(ctx, adminClient, user, newSecretKey); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func getCheckUserSAResponse(session *models.Principal, params userApi.CheckUserServiceAccountsParams) (*models.UserServiceAccountSummary, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
var userServiceAccountList []*models.UserServiceAccountItem
hasSA := false
for _, user := range params.SelectedUsers {
listServAccs, err := adminClient.listServiceAccounts(ctx, user)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
numSAs := int64(len(listServAccs.Accounts))
if numSAs > 0 {
hasSA = true
}
userAccountItem := &models.UserServiceAccountItem{
UserName: user,
NumSAs: numSAs,
}
userServiceAccountList = append(userServiceAccountList, userAccountItem)
}
userAccountList := &models.UserServiceAccountSummary{
UserServiceAccountList: userServiceAccountList,
HasSA: hasSA,
}
return userAccountList, nil
}

View File

@@ -1,535 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"bytes"
"context"
"errors"
"fmt"
"strings"
"testing"
"github.com/minio/madmin-go/v3"
iampolicy "github.com/minio/pkg/v2/policy"
asrt "github.com/stretchr/testify/assert"
)
func TestListUsers(t *testing.T) {
assert := asrt.New(t)
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : listUsers() Get response from minio client with two users and return the same number on listUsers()
// mock minIO client
mockUserMap := map[string]madmin.UserInfo{
"ABCDEFGHI": {
SecretKey: "",
PolicyName: "ABCDEFGHI-policy",
Status: "enabled",
MemberOf: []string{"group1", "group2"},
},
"ZBCDEFGHI": {
SecretKey: "",
PolicyName: "ZBCDEFGHI-policy",
Status: "enabled",
MemberOf: []string{"group1", "group2"},
},
}
// mock function response from listUsersWithContext(ctx)
minioListUsersMock = func() (map[string]madmin.UserInfo, error) {
return mockUserMap, nil
}
// get list users response this response should have Name, CreationDate, Size and Access
// as part of of each user
function := "listUsers()"
userMap, err := listUsers(ctx, adminClient)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// verify length of users is correct
assert.Equal(len(mockUserMap), len(userMap), fmt.Sprintf("Failed on %s: length of user's lists is not the same", function))
for _, b := range userMap {
assert.Contains(mockUserMap, b.AccessKey)
assert.Equal(string(mockUserMap[b.AccessKey].Status), b.Status)
assert.Equal(mockUserMap[b.AccessKey].PolicyName, strings.Join(b.Policy, ","))
assert.ElementsMatch(mockUserMap[b.AccessKey].MemberOf, []string{"group1", "group2"})
}
// Test-2 : listUsers() Return and see that the error is handled correctly and returned
minioListUsersMock = func() (map[string]madmin.UserInfo, error) {
return nil, errors.New("error")
}
_, err = listUsers(ctx, adminClient)
if assert.Error(err) {
assert.Equal("error", err.Error())
}
}
func TestAddUser(t *testing.T) {
assert := asrt.New(t)
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1: valid case of adding a user with a proper access key
accessKey := "ABCDEFGHI"
secretKey := "ABCDEFGHIABCDEFGHI"
groups := []string{"group1", "group2", "group3"}
policies := []string{}
emptyGroupTest := []string{}
mockResponse := &madmin.UserInfo{
MemberOf: []string{"group1", "group2", "gropup3"},
PolicyName: "",
Status: "enabled",
SecretKey: "",
}
// mock function response from addUser() return no error
minioAddUserMock = func(_, _ string) error {
return nil
}
minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) {
return *mockResponse, nil
}
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return nil
}
// Test-1: Add a user
function := "addUser()"
user, err := addUser(ctx, adminClient, &accessKey, &secretKey, groups, policies)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// no error should have been returned
assert.Nil(err, "Error is not null")
// the same access key should be in the model users
assert.Equal(user.AccessKey, accessKey)
// Test-2 Add a user with empty groups list
user, err = addUser(ctx, adminClient, &accessKey, &secretKey, emptyGroupTest, policies)
// no error should have been returned
assert.Nil(err, "Error is not null")
// the same access key should be in the model users
assert.Equal(user.AccessKey, accessKey)
// Test-3: valid case
accessKey = "AB"
secretKey = "ABCDEFGHIABCDEFGHI"
// mock function response from addUser() return no error
minioAddUserMock = func(_, _ string) error {
return errors.New("error")
}
user, err = addUser(ctx, adminClient, &accessKey, &secretKey, groups, policies)
// no error should have been returned
assert.Nil(user, "User is not null")
assert.NotNil(err, "An error should have been returned")
if assert.Error(err) {
assert.Equal("error", err.Error())
}
// Test-4: add groups function returns an error
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return errors.New("error")
}
user, err = addUser(ctx, adminClient, &accessKey, &secretKey, groups, policies)
// no error should have been returned
assert.Nil(user, "User is not null")
assert.NotNil(err, "An error should have been returned")
if assert.Error(err) {
assert.Equal("error", err.Error())
}
}
func TestRemoveUser(t *testing.T) {
assert := asrt.New(t)
// mock minIO client
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
function := "removeUser()"
// Test-1: removeUser() delete a user
// mock function response from removeUser(accessKey)
minioRemoveUserMock = func(_ string) error {
return nil
}
if err := removeUser(ctx, adminClient, "ABCDEFGHI"); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-2: removeUser() make sure errors are handled correctly when error on DeleteUser()
// mock function response from removeUser(accessKey)
minioRemoveUserMock = func(_ string) error {
return errors.New("error")
}
if err := removeUser(ctx, adminClient, "notexistentuser"); assert.Error(err) {
assert.Equal("error", err.Error())
}
}
func TestUserGroups(t *testing.T) {
assert := asrt.New(t)
// mock minIO client
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
function := "updateUserGroups()"
mockUserGroups := []string{"group1", "group2", "group3"}
mockUserName := "testUser"
mockResponse := &madmin.UserInfo{
MemberOf: []string{"group1", "group2", "gropup3"},
PolicyName: "",
Status: "enabled",
SecretKey: mockUserName,
}
mockEmptyResponse := &madmin.UserInfo{
MemberOf: nil,
PolicyName: "",
Status: "",
SecretKey: "",
}
// Test-1: updateUserGroups() updates the groups for a user
// mock function response from updateUserGroups(accessKey, groupsToAssign)
minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) {
return *mockResponse, nil
}
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return nil
}
if _, err := updateUserGroups(ctx, adminClient, mockUserName, mockUserGroups); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-2: updateUserGroups() make sure errors are handled correctly when error on UpdateGroupMembersMock()
// mock function response from removeUser(accessKey)
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return errors.New("error")
}
if _, err := updateUserGroups(ctx, adminClient, mockUserName, mockUserGroups); assert.Error(err) {
assert.Equal("there was an error updating the groups", err.Error())
}
// Test-3: updateUserGroups() make sure we return the correct error when getUserInfo returns error
minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) {
return *mockEmptyResponse, errors.New("error getting user ")
}
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return nil
}
if _, err := updateUserGroups(ctx, adminClient, mockUserName, mockUserGroups); assert.Error(err) {
assert.Equal("error getting user ", err.Error())
}
}
func TestGetUserInfo(t *testing.T) {
assert := asrt.New(t)
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : getUserInfo() get user info
userName := "userNameTest"
mockResponse := &madmin.UserInfo{
SecretKey: userName,
PolicyName: "",
MemberOf: []string{"group1", "group2", "group3"},
Status: "enabled",
}
emptyMockResponse := &madmin.UserInfo{
SecretKey: "",
PolicyName: "",
Status: "",
MemberOf: nil,
}
// mock function response from getUserInfo()
minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) {
return *mockResponse, nil
}
function := "getUserInfo()"
info, err := getUserInfo(ctx, adminClient, userName)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
assert.Equal(userName, info.SecretKey)
assert.Equal("", info.PolicyName)
assert.ElementsMatch([]string{"group1", "group2", "group3"}, info.MemberOf)
assert.Equal(mockResponse.Status, info.Status)
// Test-2 : getUserInfo() Return error and see that the error is handled correctly and returned
minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) {
return *emptyMockResponse, errors.New("error")
}
_, err = getUserInfo(ctx, adminClient, userName)
if assert.Error(err) {
assert.Equal("error", err.Error())
}
}
func TestSetUserStatus(t *testing.T) {
assert := asrt.New(t)
adminClient := AdminClientMock{}
function := "setUserStatus()"
userName := "userName123"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1: setUserStatus() update valid disabled status
expectedStatus := "disabled"
minioSetUserStatusMock = func(_ string, _ madmin.AccountStatus) error {
return nil
}
if err := setUserStatus(ctx, adminClient, userName, expectedStatus); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-2: setUserStatus() update valid enabled status
expectedStatus = "enabled"
minioSetUserStatusMock = func(_ string, _ madmin.AccountStatus) error {
return nil
}
if err := setUserStatus(ctx, adminClient, userName, expectedStatus); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-3: setUserStatus() update invalid status, should send error
expectedStatus = "invalid"
minioSetUserStatusMock = func(_ string, _ madmin.AccountStatus) error {
return nil
}
if err := setUserStatus(ctx, adminClient, userName, expectedStatus); assert.Error(err) {
assert.Equal("status not valid", err.Error())
}
// Test-4: setUserStatus() handler error correctly
expectedStatus = "enabled"
minioSetUserStatusMock = func(_ string, _ madmin.AccountStatus) error {
return errors.New("error")
}
if err := setUserStatus(ctx, adminClient, userName, expectedStatus); assert.Error(err) {
assert.Equal("error", err.Error())
}
}
func TestUserGroupsBulk(t *testing.T) {
assert := asrt.New(t)
// mock minIO client
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
function := "updateUserGroups()"
mockUserGroups := []string{"group1", "group2", "group3"}
mockUsers := []string{"testUser", "testUser2"}
// Test-1: addUsersListToGroups() updates the groups for a users list
// mock function response from updateUserGroups(accessKey, groupsToAssign)
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return nil
}
if err := addUsersListToGroups(ctx, adminClient, mockUsers, mockUserGroups); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-2: addUsersListToGroups() make sure errors are handled correctly when error on updateGroupMembers()
// mock function response from removeUser(accessKey)
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return errors.New("error")
}
if err := addUsersListToGroups(ctx, adminClient, mockUsers, mockUserGroups); assert.Error(err) {
assert.Equal("error in users-groups assignation: \"error,error,error\"", err.Error())
}
}
func TestListUsersWithAccessToBucket(t *testing.T) {
assert := asrt.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
adminClient := AdminClientMock{}
user1 := madmin.UserInfo{
SecretKey: "testtest",
PolicyName: "consoleAdmin,testPolicy,redundantPolicy",
Status: "enabled",
MemberOf: []string{"group1"},
}
user2 := madmin.UserInfo{
SecretKey: "testtest",
PolicyName: "testPolicy, otherPolicy",
Status: "enabled",
MemberOf: []string{"group1"},
}
mockUsers := map[string]madmin.UserInfo{"testuser1": user1, "testuser2": user2}
minioListUsersMock = func() (map[string]madmin.UserInfo, error) {
return mockUsers, nil
}
policyMap := map[string]string{
"consoleAdmin": `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"admin:*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`,
"testPolicy": `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket1"
]
}
]
}`,
"otherPolicy": `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket2"
]
}
]
}`,
"thirdPolicy": `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket3"
]
}
]
}`, "RedundantPolicy": `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket1"
]
}
]
}`,
}
minioGetPolicyMock = func(name string) (*iampolicy.Policy, error) {
iamp, err := iampolicy.ParseConfig(bytes.NewReader([]byte(policyMap[name])))
if err != nil {
return nil, err
}
return iamp, nil
}
minioListGroupsMock = func() ([]string, error) {
return []string{"group1"}, nil
}
minioGetGroupDescriptionMock = func(name string) (*madmin.GroupDesc, error) {
if name == "group1" {
mockResponse := &madmin.GroupDesc{
Name: "group1",
Policy: "thirdPolicy",
Members: []string{"testuser1", "testuser2"},
Status: "enabled",
}
return mockResponse, nil
}
return nil, ErrDefault
}
type args struct {
bucket string
}
tests := []struct {
name string
args args
want []string
}{
{
name: "Test1",
args: args{bucket: "bucket0"},
want: []string{"testuser1"},
},
{
name: "Test2",
args: args{bucket: "bucket1"},
want: []string(nil),
},
{
name: "Test3",
args: args{bucket: "bucket2"},
want: []string{"testuser1", "testuser2"},
},
{
name: "Test4",
args: args{bucket: "bucket3"},
want: []string{"testuser1", "testuser2"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
got, _ := listUsersWithAccessToBucket(ctx, adminClient, tt.args.bucket)
assert.Equal(got, tt.want)
})
}
}

View File

@@ -17,16 +17,12 @@
package api
import (
"bytes"
"context"
"encoding/json"
"io"
"net"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"github.com/minio/console/pkg"
@@ -35,7 +31,6 @@ import (
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7/pkg/credentials"
iampolicy "github.com/minio/pkg/v2/policy"
)
const globalAppName = "MinIO Console"
@@ -44,98 +39,9 @@ const globalAppName = "MinIO Console"
// by mock when testing, it should include all MinioAdmin respective api calls
// that are used within this project.
type MinioAdmin interface {
listUsers(ctx context.Context) (map[string]madmin.UserInfo, error)
addUser(ctx context.Context, acessKey, SecretKey string) error
removeUser(ctx context.Context, accessKey string) error
getUserInfo(ctx context.Context, accessKey string) (madmin.UserInfo, error)
setUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) error
listGroups(ctx context.Context) ([]string, error)
updateGroupMembers(ctx context.Context, greq madmin.GroupAddRemove) error
getGroupDescription(ctx context.Context, group string) (*madmin.GroupDesc, error)
setGroupStatus(ctx context.Context, group string, status madmin.GroupStatus) error
listPolicies(ctx context.Context) (map[string]*iampolicy.Policy, error)
getPolicy(ctx context.Context, name string) (*iampolicy.Policy, error)
removePolicy(ctx context.Context, name string) error
addPolicy(ctx context.Context, name string, policy *iampolicy.Policy) error
setPolicy(ctx context.Context, policyName, entityName string, isGroup bool) error
getConfigKV(ctx context.Context, key string) ([]byte, error)
helpConfigKV(ctx context.Context, subSys, key string, envOnly bool) (madmin.Help, error)
helpConfigKVGlobal(ctx context.Context, envOnly bool) (madmin.Help, error)
setConfigKV(ctx context.Context, kv string) (restart bool, err error)
delConfigKV(ctx context.Context, kv string) (err error)
serviceRestart(ctx context.Context) error
serverInfo(ctx context.Context) (madmin.InfoMessage, error)
startProfiling(ctx context.Context, profiler madmin.ProfilerType) ([]madmin.StartProfilingResult, error)
stopProfiling(ctx context.Context) (io.ReadCloser, error)
serviceTrace(ctx context.Context, threshold int64, s3, internal, storage, os, errTrace bool) <-chan madmin.ServiceTraceInfo
getLogs(ctx context.Context, node string, lineCnt int, logKind string) <-chan madmin.LogInfo
AccountInfo(ctx context.Context) (madmin.AccountInfo, error)
heal(ctx context.Context, bucket, prefix string, healOpts madmin.HealOpts, clientToken string,
forceStart, forceStop bool) (healStart madmin.HealStartSuccess, healTaskStatus madmin.HealTaskStatus, err error)
// Service Accounts
addServiceAccount(ctx context.Context, policy string, user string, accessKey string, secretKey string, name string, description string, expiry *time.Time, comment string) (madmin.Credentials, error)
listServiceAccounts(ctx context.Context, user string) (madmin.ListServiceAccountsResp, error)
deleteServiceAccount(ctx context.Context, serviceAccount string) error
infoServiceAccount(ctx context.Context, serviceAccount string) (madmin.InfoServiceAccountResp, error)
updateServiceAccount(ctx context.Context, serviceAccount string, opts madmin.UpdateServiceAccountReq) error
// Remote Buckets
listRemoteBuckets(ctx context.Context, bucket, arnType string) (targets []madmin.BucketTarget, err error)
getRemoteBucket(ctx context.Context, bucket, arnType string) (targets *madmin.BucketTarget, err error)
removeRemoteBucket(ctx context.Context, bucket, arn string) error
addRemoteBucket(ctx context.Context, bucket string, target *madmin.BucketTarget) (string, error)
// Account password management
changePassword(ctx context.Context, accessKey, secretKey string) error
serverHealthInfo(ctx context.Context, healthDataTypes []madmin.HealthDataType, deadline time.Duration) (interface{}, string, error)
// List Tiers
listTiers(ctx context.Context) ([]*madmin.TierConfig, error)
// Tier Info
tierStats(ctx context.Context) ([]madmin.TierInfo, error)
// Add Tier
addTier(ctx context.Context, tier *madmin.TierConfig) error
// Edit Tier Credentials
editTierCreds(ctx context.Context, tierName string, creds madmin.TierCreds) error
// verify Tier status
verifyTierStatus(ctx context.Context, tierName string) error
// Speedtest
speedtest(ctx context.Context, opts madmin.SpeedtestOpts) (chan madmin.SpeedTestResult, error)
// Site Relication
getSiteReplicationInfo(ctx context.Context) (*madmin.SiteReplicationInfo, error)
addSiteReplicationInfo(ctx context.Context, sites []madmin.PeerSite, opts madmin.SRAddOptions) (*madmin.ReplicateAddStatus, error)
editSiteReplicationInfo(ctx context.Context, site madmin.PeerInfo, opts madmin.SREditOptions) (*madmin.ReplicateEditStatus, error)
deleteSiteReplicationInfo(ctx context.Context, removeReq madmin.SRRemoveReq) (*madmin.ReplicateRemoveStatus, error)
// Replication status
getSiteReplicationStatus(ctx context.Context, params madmin.SRStatusOptions) (*madmin.SRStatusInfo, error)
// KMS
kmsStatus(ctx context.Context) (madmin.KMSStatus, error)
kmsMetrics(ctx context.Context) (*madmin.KMSMetrics, error)
kmsAPIs(ctx context.Context) ([]madmin.KMSAPI, error)
kmsVersion(ctx context.Context) (*madmin.KMSVersion, error)
createKey(ctx context.Context, key string) error
importKey(ctx context.Context, key string, content []byte) error
listKeys(ctx context.Context, pattern string) ([]madmin.KMSKeyInfo, error)
keyStatus(ctx context.Context, key string) (*madmin.KMSKeyStatus, error)
deleteKey(ctx context.Context, key string) error
setKMSPolicy(ctx context.Context, policy string, content []byte) error
assignPolicy(ctx context.Context, policy string, content []byte) error
describePolicy(ctx context.Context, policy string) (*madmin.KMSDescribePolicy, error)
getKMSPolicy(ctx context.Context, policy string) (*madmin.KMSPolicy, error)
listKMSPolicies(ctx context.Context, pattern string) ([]madmin.KMSPolicyInfo, error)
deletePolicy(ctx context.Context, policy string) error
describeIdentity(ctx context.Context, identity string) (*madmin.KMSDescribeIdentity, error)
describeSelfIdentity(ctx context.Context) (*madmin.KMSDescribeSelfIdentity, error)
deleteIdentity(ctx context.Context, identity string) error
listIdentities(ctx context.Context, pattern string) ([]madmin.KMSIdentityInfo, error)
// IDP
addOrUpdateIDPConfig(ctx context.Context, idpType, cfgName, cfgData string, update bool) (restart bool, err error)
listIDPConfig(ctx context.Context, idpType string) ([]madmin.IDPListItem, error)
deleteIDPConfig(ctx context.Context, idpType, cfgName string) (restart bool, err error)
getIDPConfig(ctx context.Context, cfgType, cfgName string) (c madmin.IDPConfig, err error)
// LDAP
getLDAPPolicyEntities(ctx context.Context, query madmin.PolicyEntitiesQuery) (madmin.PolicyEntitiesResult, error)
}
// Interface implementation
@@ -146,305 +52,17 @@ type AdminClient struct {
Client *madmin.AdminClient
}
func (ac AdminClient) changePassword(ctx context.Context, accessKey, secretKey string) error {
return ac.Client.SetUser(ctx, accessKey, secretKey, madmin.AccountEnabled)
}
// implements madmin.ListUsers()
func (ac AdminClient) listUsers(ctx context.Context) (map[string]madmin.UserInfo, error) {
return ac.Client.ListUsers(ctx)
}
// implements madmin.AddUser()
func (ac AdminClient) addUser(ctx context.Context, accessKey, secretKey string) error {
return ac.Client.AddUser(ctx, accessKey, secretKey)
}
// implements madmin.RemoveUser()
func (ac AdminClient) removeUser(ctx context.Context, accessKey string) error {
return ac.Client.RemoveUser(ctx, accessKey)
}
// implements madmin.GetUserInfo()
func (ac AdminClient) getUserInfo(ctx context.Context, accessKey string) (madmin.UserInfo, error) {
return ac.Client.GetUserInfo(ctx, accessKey)
}
// implements madmin.SetUserStatus()
func (ac AdminClient) setUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) error {
return ac.Client.SetUserStatus(ctx, accessKey, status)
}
// implements madmin.ListGroups()
func (ac AdminClient) listGroups(ctx context.Context) ([]string, error) {
return ac.Client.ListGroups(ctx)
}
// implements madmin.UpdateGroupMembers()
func (ac AdminClient) updateGroupMembers(ctx context.Context, greq madmin.GroupAddRemove) error {
return ac.Client.UpdateGroupMembers(ctx, greq)
}
// implements madmin.GetGroupDescription(group)
func (ac AdminClient) getGroupDescription(ctx context.Context, group string) (*madmin.GroupDesc, error) {
return ac.Client.GetGroupDescription(ctx, group)
}
// implements madmin.SetGroupStatus(group, status)
func (ac AdminClient) setGroupStatus(ctx context.Context, group string, status madmin.GroupStatus) error {
return ac.Client.SetGroupStatus(ctx, group, status)
}
// implements madmin.ListCannedPolicies()
func (ac AdminClient) listPolicies(ctx context.Context) (map[string]*iampolicy.Policy, error) {
policyMap, err := ac.Client.ListCannedPolicies(ctx)
if err != nil {
return nil, err
}
policies := make(map[string]*iampolicy.Policy, len(policyMap))
for k, v := range policyMap {
p, err := iampolicy.ParseConfig(bytes.NewReader(v))
if err != nil {
return nil, err
}
policies[k] = p
}
return policies, nil
}
// implements madmin.ListCannedPolicies()
func (ac AdminClient) getPolicy(ctx context.Context, name string) (*iampolicy.Policy, error) {
info, err := ac.Client.InfoCannedPolicyV2(ctx, name)
if err != nil {
return nil, err
}
return iampolicy.ParseConfig(bytes.NewReader(info.Policy))
}
// implements madmin.RemoveCannedPolicy()
func (ac AdminClient) removePolicy(ctx context.Context, name string) error {
return ac.Client.RemoveCannedPolicy(ctx, name)
}
// implements madmin.AddCannedPolicy()
func (ac AdminClient) addPolicy(ctx context.Context, name string, policy *iampolicy.Policy) error {
buf, err := json.Marshal(policy)
if err != nil {
return err
}
return ac.Client.AddCannedPolicy(ctx, name, buf)
}
// implements madmin.SetPolicy()
func (ac AdminClient) setPolicy(ctx context.Context, policyName, entityName string, isGroup bool) error {
// nolint:staticcheck // ignore SA1019
return ac.Client.SetPolicy(ctx, policyName, entityName, isGroup)
}
// implements madmin.GetConfigKV()
func (ac AdminClient) getConfigKV(ctx context.Context, key string) ([]byte, error) {
return ac.Client.GetConfigKV(ctx, key)
}
// implements madmin.HelpConfigKV()
func (ac AdminClient) helpConfigKV(ctx context.Context, subSys, key string, envOnly bool) (madmin.Help, error) {
return ac.Client.HelpConfigKV(ctx, subSys, key, envOnly)
}
// implements madmin.helpConfigKVGlobal()
func (ac AdminClient) helpConfigKVGlobal(ctx context.Context, envOnly bool) (madmin.Help, error) {
return ac.Client.HelpConfigKV(ctx, "", "", envOnly)
}
// implements madmin.SetConfigKV()
func (ac AdminClient) setConfigKV(ctx context.Context, kv string) (restart bool, err error) {
return ac.Client.SetConfigKV(ctx, kv)
}
// implements madmin.DelConfigKV()
func (ac AdminClient) delConfigKV(ctx context.Context, kv string) (err error) {
_, err = ac.Client.DelConfigKV(ctx, kv)
return err
}
// implements madmin.ServiceRestart()
func (ac AdminClient) serviceRestart(ctx context.Context) (err error) {
return ac.Client.ServiceRestart(ctx)
}
// implements madmin.ServerInfo()
func (ac AdminClient) serverInfo(ctx context.Context) (madmin.InfoMessage, error) {
return ac.Client.ServerInfo(ctx)
}
// implements madmin.StartProfiling()
func (ac AdminClient) startProfiling(ctx context.Context, profiler madmin.ProfilerType) ([]madmin.StartProfilingResult, error) {
return ac.Client.StartProfiling(ctx, profiler)
}
// implements madmin.DownloadProfilingData()
func (ac AdminClient) stopProfiling(ctx context.Context) (io.ReadCloser, error) {
return ac.Client.DownloadProfilingData(ctx)
}
// implements madmin.ServiceTrace()
func (ac AdminClient) serviceTrace(ctx context.Context, threshold int64, _, internal, storage, os, errTrace bool) <-chan madmin.ServiceTraceInfo {
thresholdT := time.Duration(threshold)
tracingOptions := madmin.ServiceTraceOpts{
S3: true,
OnlyErrors: errTrace,
Internal: internal,
Storage: storage,
OS: os,
Threshold: thresholdT,
}
return ac.Client.ServiceTrace(ctx, tracingOptions)
}
// implements madmin.GetLogs()
func (ac AdminClient) getLogs(ctx context.Context, node string, lineCnt int, logKind string) <-chan madmin.LogInfo {
return ac.Client.GetLogs(ctx, node, lineCnt, logKind)
}
// implements madmin.AddServiceAccount()
func (ac AdminClient) addServiceAccount(ctx context.Context, policy string, user string, accessKey string, secretKey string, name string, description string, expiry *time.Time, comment string) (madmin.Credentials, error) {
return ac.Client.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
Policy: []byte(policy),
TargetUser: user,
AccessKey: accessKey,
SecretKey: secretKey,
Name: name,
Description: description,
Expiration: expiry,
Comment: comment,
})
}
// implements madmin.ListServiceAccounts()
func (ac AdminClient) listServiceAccounts(ctx context.Context, user string) (madmin.ListServiceAccountsResp, error) {
return ac.Client.ListServiceAccounts(ctx, user)
}
// implements madmin.DeleteServiceAccount()
func (ac AdminClient) deleteServiceAccount(ctx context.Context, serviceAccount string) error {
return ac.Client.DeleteServiceAccount(ctx, serviceAccount)
}
// implements madmin.InfoServiceAccount()
func (ac AdminClient) infoServiceAccount(ctx context.Context, serviceAccount string) (madmin.InfoServiceAccountResp, error) {
return ac.Client.InfoServiceAccount(ctx, serviceAccount)
}
// implements madmin.UpdateServiceAccount()
func (ac AdminClient) updateServiceAccount(ctx context.Context, serviceAccount string, opts madmin.UpdateServiceAccountReq) error {
return ac.Client.UpdateServiceAccount(ctx, serviceAccount, opts)
}
// AccountInfo implements madmin.AccountInfo()
func (ac AdminClient) AccountInfo(ctx context.Context) (madmin.AccountInfo, error) {
return ac.Client.AccountInfo(ctx, madmin.AccountOpts{})
}
func (ac AdminClient) heal(ctx context.Context, bucket, prefix string, healOpts madmin.HealOpts, clientToken string,
forceStart, forceStop bool,
) (healStart madmin.HealStartSuccess, healTaskStatus madmin.HealTaskStatus, err error) {
return ac.Client.Heal(ctx, bucket, prefix, healOpts, clientToken, forceStart, forceStop)
}
// listRemoteBuckets - return a list of remote buckets
func (ac AdminClient) listRemoteBuckets(ctx context.Context, bucket, arnType string) (targets []madmin.BucketTarget, err error) {
return ac.Client.ListRemoteTargets(ctx, bucket, arnType)
}
// getRemoteBucket - gets remote bucked based on a given bucket name
func (ac AdminClient) getRemoteBucket(ctx context.Context, bucket, arnType string) (*madmin.BucketTarget, error) {
targets, err := ac.Client.ListRemoteTargets(ctx, bucket, arnType)
if err != nil {
return nil, err
}
if len(targets) > 0 {
return &targets[0], nil
}
return nil, err
}
// removeRemoteBucket removes a remote target associated with particular ARN for this bucket
func (ac AdminClient) removeRemoteBucket(ctx context.Context, bucket, arn string) error {
return ac.Client.RemoveRemoteTarget(ctx, bucket, arn)
}
// addRemoteBucket sets up a remote target for this bucket
func (ac AdminClient) addRemoteBucket(ctx context.Context, bucket string, target *madmin.BucketTarget) (string, error) {
return ac.Client.SetRemoteTarget(ctx, bucket, target)
}
func (ac AdminClient) setBucketQuota(ctx context.Context, bucket string, quota *madmin.BucketQuota) error {
return ac.Client.SetBucketQuota(ctx, bucket, quota)
}
func (ac AdminClient) getBucketQuota(ctx context.Context, bucket string) (madmin.BucketQuota, error) {
return ac.Client.GetBucketQuota(ctx, bucket)
}
// serverHealthInfo implements mc.ServerHealthInfo - Connect to a minio server and call Health Info Management API
func (ac AdminClient) serverHealthInfo(ctx context.Context, healthDataTypes []madmin.HealthDataType, deadline time.Duration) (interface{}, string, error) {
info := madmin.HealthInfo{}
var healthInfo interface{}
var version string
var tryCount int
for info.Version == "" && tryCount < 10 {
resp, version, err := ac.Client.ServerHealthInfo(ctx, healthDataTypes, deadline, "")
if err != nil {
return nil, version, err
}
decoder := json.NewDecoder(resp.Body)
for {
if err = decoder.Decode(&info); err != nil {
break
}
}
tryCount++
time.Sleep(2 * time.Second)
}
if info.Version == "" {
return nil, "", ErrHealthReportFail
}
healthInfo = info
return healthInfo, version, nil
}
// implements madmin.listTiers()
func (ac AdminClient) listTiers(ctx context.Context) ([]*madmin.TierConfig, error) {
return ac.Client.ListTiers(ctx)
}
// implements madmin.tierStats()
func (ac AdminClient) tierStats(ctx context.Context) ([]madmin.TierInfo, error) {
return ac.Client.TierStats(ctx)
}
// implements madmin.AddTier()
func (ac AdminClient) addTier(ctx context.Context, cfg *madmin.TierConfig) error {
return ac.Client.AddTier(ctx, cfg)
}
// implements madmin.Inspect()
func (ac AdminClient) inspect(ctx context.Context, insOpts madmin.InspectOptions) ([]byte, io.ReadCloser, error) {
return ac.Client.Inspect(ctx, insOpts)
}
// implements madmin.EditTier()
func (ac AdminClient) editTierCreds(ctx context.Context, tierName string, creds madmin.TierCreds) error {
return ac.Client.EditTier(ctx, tierName, creds)
}
// implements madmin.VerifyTier()
func (ac AdminClient) verifyTierStatus(ctx context.Context, tierName string) error {
return ac.Client.VerifyTier(ctx, tierName)
func (ac AdminClient) kmsStatus(ctx context.Context) (madmin.KMSStatus, error) {
return ac.Client.KMSStatus(ctx)
}
func NewMinioAdminClient(ctx context.Context, sessionClaims *models.Principal) (*madmin.AdminClient, error) {
@@ -474,19 +92,6 @@ func newAdminFromClaims(claims *models.Principal, clientIP string) (*madmin.Admi
return adminClient, nil
}
// newAdminFromCreds Creates a minio client using custom credentials for connecting to a remote host
func newAdminFromCreds(accessKey, secretKey, endpoint string, tlsEnabled bool) (*madmin.AdminClient, error) {
minioClient, err := madmin.NewWithOptions(endpoint, &madmin.Options{
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
Secure: tlsEnabled,
})
if err != nil {
return nil, err
}
minioClient.SetAppInfo(globalAppName, pkg.Version)
return minioClient, nil
}
// isLocalAddress returns true if the url contains an IPv4/IPv6 hostname
// that points to the local machine - FQDN are not supported
func isLocalIPEndpoint(endpoint string) bool {
@@ -583,162 +188,3 @@ func getClientIP(r *http.Request) string {
}
return raddr
}
func (ac AdminClient) speedtest(ctx context.Context, opts madmin.SpeedtestOpts) (chan madmin.SpeedTestResult, error) {
return ac.Client.Speedtest(ctx, opts)
}
// Site Replication
func (ac AdminClient) getSiteReplicationInfo(ctx context.Context) (*madmin.SiteReplicationInfo, error) {
res, err := ac.Client.SiteReplicationInfo(ctx)
if err != nil {
return nil, err
}
return &madmin.SiteReplicationInfo{
Enabled: res.Enabled,
Name: res.Name,
Sites: res.Sites,
ServiceAccountAccessKey: res.ServiceAccountAccessKey,
}, nil
}
func (ac AdminClient) addSiteReplicationInfo(ctx context.Context, sites []madmin.PeerSite, opts madmin.SRAddOptions) (*madmin.ReplicateAddStatus, error) {
res, err := ac.Client.SiteReplicationAdd(ctx, sites, opts)
if err != nil {
return nil, err
}
return &madmin.ReplicateAddStatus{
Success: res.Success,
Status: res.Status,
ErrDetail: res.ErrDetail,
InitialSyncErrorMessage: res.InitialSyncErrorMessage,
}, nil
}
func (ac AdminClient) editSiteReplicationInfo(ctx context.Context, site madmin.PeerInfo, opts madmin.SREditOptions) (*madmin.ReplicateEditStatus, error) {
res, err := ac.Client.SiteReplicationEdit(ctx, site, opts)
if err != nil {
return nil, err
}
return &madmin.ReplicateEditStatus{
Success: res.Success,
Status: res.Status,
ErrDetail: res.ErrDetail,
}, nil
}
func (ac AdminClient) deleteSiteReplicationInfo(ctx context.Context, removeReq madmin.SRRemoveReq) (*madmin.ReplicateRemoveStatus, error) {
res, err := ac.Client.SiteReplicationRemove(ctx, removeReq)
if err != nil {
return nil, err
}
return &madmin.ReplicateRemoveStatus{
Status: res.Status,
ErrDetail: res.ErrDetail,
}, nil
}
func (ac AdminClient) getSiteReplicationStatus(ctx context.Context, params madmin.SRStatusOptions) (*madmin.SRStatusInfo, error) {
res, err := ac.Client.SRStatusInfo(ctx, params)
if err != nil {
return nil, err
}
return &res, nil
}
func (ac AdminClient) kmsStatus(ctx context.Context) (madmin.KMSStatus, error) {
return ac.Client.KMSStatus(ctx)
}
func (ac AdminClient) kmsMetrics(ctx context.Context) (*madmin.KMSMetrics, error) {
return ac.Client.KMSMetrics(ctx)
}
func (ac AdminClient) kmsAPIs(ctx context.Context) ([]madmin.KMSAPI, error) {
return ac.Client.KMSAPIs(ctx)
}
func (ac AdminClient) kmsVersion(ctx context.Context) (*madmin.KMSVersion, error) {
return ac.Client.KMSVersion(ctx)
}
func (ac AdminClient) createKey(ctx context.Context, key string) error {
return ac.Client.CreateKey(ctx, key)
}
func (ac AdminClient) importKey(ctx context.Context, key string, content []byte) error {
return ac.Client.ImportKey(ctx, key, content)
}
func (ac AdminClient) listKeys(ctx context.Context, pattern string) ([]madmin.KMSKeyInfo, error) {
return ac.Client.ListKeys(ctx, pattern)
}
func (ac AdminClient) keyStatus(ctx context.Context, key string) (*madmin.KMSKeyStatus, error) {
return ac.Client.GetKeyStatus(ctx, key)
}
func (ac AdminClient) deleteKey(ctx context.Context, key string) error {
return ac.Client.DeleteKey(ctx, key)
}
func (ac AdminClient) setKMSPolicy(ctx context.Context, policy string, content []byte) error {
return ac.Client.SetKMSPolicy(ctx, policy, content)
}
func (ac AdminClient) assignPolicy(ctx context.Context, policy string, content []byte) error {
return ac.Client.AssignPolicy(ctx, policy, content)
}
func (ac AdminClient) describePolicy(ctx context.Context, policy string) (*madmin.KMSDescribePolicy, error) {
return ac.Client.DescribePolicy(ctx, policy)
}
func (ac AdminClient) getKMSPolicy(ctx context.Context, policy string) (*madmin.KMSPolicy, error) {
return ac.Client.GetPolicy(ctx, policy)
}
func (ac AdminClient) listKMSPolicies(ctx context.Context, pattern string) ([]madmin.KMSPolicyInfo, error) {
return ac.Client.ListPolicies(ctx, pattern)
}
func (ac AdminClient) deletePolicy(ctx context.Context, policy string) error {
return ac.Client.DeletePolicy(ctx, policy)
}
func (ac AdminClient) describeIdentity(ctx context.Context, identity string) (*madmin.KMSDescribeIdentity, error) {
return ac.Client.DescribeIdentity(ctx, identity)
}
func (ac AdminClient) describeSelfIdentity(ctx context.Context) (*madmin.KMSDescribeSelfIdentity, error) {
return ac.Client.DescribeSelfIdentity(ctx)
}
func (ac AdminClient) deleteIdentity(ctx context.Context, identity string) error {
return ac.Client.DeleteIdentity(ctx, identity)
}
func (ac AdminClient) listIdentities(ctx context.Context, pattern string) ([]madmin.KMSIdentityInfo, error) {
return ac.Client.ListIdentities(ctx, pattern)
}
func (ac AdminClient) addOrUpdateIDPConfig(ctx context.Context, idpType, cfgName, cfgData string, update bool) (restart bool, err error) {
return ac.Client.AddOrUpdateIDPConfig(ctx, idpType, cfgName, cfgData, update)
}
func (ac AdminClient) listIDPConfig(ctx context.Context, idpType string) ([]madmin.IDPListItem, error) {
return ac.Client.ListIDPConfig(ctx, idpType)
}
func (ac AdminClient) deleteIDPConfig(ctx context.Context, idpType, cfgName string) (restart bool, err error) {
return ac.Client.DeleteIDPConfig(ctx, idpType, cfgName)
}
func (ac AdminClient) getIDPConfig(ctx context.Context, idpType, cfgName string) (c madmin.IDPConfig, err error) {
return ac.Client.GetIDPConfig(ctx, idpType, cfgName)
}
func (ac AdminClient) getLDAPPolicyEntities(ctx context.Context, query madmin.PolicyEntitiesQuery) (madmin.PolicyEntitiesResult, error) {
return ac.Client.GetLDAPPolicyEntities(ctx, query)
}

View File

@@ -21,13 +21,13 @@ import (
"errors"
"fmt"
"io"
"net/http"
"path"
"strings"
"time"
"github.com/minio/minio-go/v7/pkg/replication"
"github.com/minio/minio-go/v7/pkg/sse"
xnet "github.com/minio/pkg/v2/net"
xnet "github.com/minio/pkg/v3/net"
"github.com/minio/console/models"
"github.com/minio/console/pkg"
@@ -38,7 +38,6 @@ import (
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/lifecycle"
"github.com/minio/minio-go/v7/pkg/notification"
"github.com/minio/minio-go/v7/pkg/tags"
)
@@ -74,8 +73,6 @@ type MinioClient interface {
setObjectLockConfig(ctx context.Context, bucketName string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit) error
getBucketObjectLockConfig(ctx context.Context, bucketName string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error)
getObjectLockConfig(ctx context.Context, bucketName string) (lock string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error)
getLifecycleRules(ctx context.Context, bucketName string) (lifecycle *lifecycle.Configuration, err error)
setBucketLifecycle(ctx context.Context, bucketName string, config *lifecycle.Configuration) error
copyObject(ctx context.Context, dst minio.CopyDestOptions, src minio.CopySrcOptions) (minio.UploadInfo, error)
GetBucketTagging(ctx context.Context, bucketName string) (*tags.Tags, error)
SetBucketTagging(ctx context.Context, bucketName string, tags *tags.Tags) error
@@ -140,11 +137,6 @@ func (c minioClient) getBucketVersioning(ctx context.Context, bucketName string)
return c.client.GetBucketVersioning(ctx, bucketName)
}
// implements minio.getBucketVersioning(ctx, bucketName)
func (c minioClient) getBucketReplication(ctx context.Context, bucketName string) (replication.Config, error) {
return c.client.GetBucketReplication(ctx, bucketName)
}
// implements minio.listObjects(ctx)
func (c minioClient) listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
return c.client.ListObjects(ctx, bucket, opts)
@@ -209,14 +201,6 @@ func (c minioClient) getObjectLockConfig(ctx context.Context, bucketName string)
return c.client.GetObjectLockConfig(ctx, bucketName)
}
func (c minioClient) getLifecycleRules(ctx context.Context, bucketName string) (lifecycle *lifecycle.Configuration, err error) {
return c.client.GetBucketLifecycle(ctx, bucketName)
}
func (c minioClient) setBucketLifecycle(ctx context.Context, bucketName string, config *lifecycle.Configuration) error {
return c.client.SetBucketLifecycle(ctx, bucketName, config)
}
func (c minioClient) copyObject(ctx context.Context, dst minio.CopyDestOptions, src minio.CopySrcOptions) (minio.UploadInfo, error) {
return c.client.CopyObject(ctx, dst, src)
}
@@ -257,14 +241,6 @@ func (c mcClient) watch(ctx context.Context, options mc.WatchOptions) (*mc.Watch
return c.client.Watch(ctx, options)
}
func (c mcClient) setReplication(ctx context.Context, cfg *replication.Config, opts replication.Options) *probe.Error {
return c.client.SetReplication(ctx, cfg, opts)
}
func (c mcClient) deleteAllReplicationRules(ctx context.Context) *probe.Error {
return c.client.RemoveReplication(ctx)
}
func (c mcClient) setVersioning(ctx context.Context, status string, excludePrefix []string, excludeFolders bool) *probe.Error {
return c.client.SetVersion(ctx, status, excludePrefix, excludeFolders)
}
@@ -299,6 +275,7 @@ type ConsoleCredentialsI interface {
type ConsoleCredentials struct {
ConsoleCredentials *credentials.Credentials
AccountAccessKey string
CredContext *credentials.CredContext
}
func (c ConsoleCredentials) GetAccountAccessKey() string {
@@ -307,7 +284,7 @@ func (c ConsoleCredentials) GetAccountAccessKey() string {
// Get implements *Login.Get()
func (c ConsoleCredentials) Get() (credentials.Value, error) {
return c.ConsoleCredentials.Get()
return c.ConsoleCredentials.GetWithContext(c.CredContext)
}
// Expire implements *Login.Expire()
@@ -322,6 +299,10 @@ type consoleSTSAssumeRole struct {
stsAssumeRole *credentials.STSAssumeRole
}
func (s consoleSTSAssumeRole) RetrieveWithCredContext(cc *credentials.CredContext) (credentials.Value, error) {
return s.stsAssumeRole.RetrieveWithCredContext(cc)
}
func (s consoleSTSAssumeRole) Retrieve() (credentials.Value, error) {
return s.stsAssumeRole.Retrieve()
}
@@ -330,7 +311,7 @@ func (s consoleSTSAssumeRole) IsExpired() bool {
return s.stsAssumeRole.IsExpired()
}
func stsCredentials(minioURL, accessKey, secretKey, location, clientIP string) (*credentials.Credentials, error) {
func stsCredentials(minioURL, accessKey, secretKey, location string, client *http.Client) (*credentials.Credentials, error) {
if accessKey == "" || secretKey == "" {
return nil, errors.New("credentials endpoint, access and secret key are mandatory for AssumeRoleSTS")
}
@@ -341,7 +322,7 @@ func stsCredentials(minioURL, accessKey, secretKey, location, clientIP string) (
DurationSeconds: int(xjwt.GetConsoleSTSDuration().Seconds()),
}
stsAssumeRole := &credentials.STSAssumeRole{
Client: GetConsoleHTTPClient(clientIP),
Client: client,
STSEndpoint: minioURL,
Options: opts,
}
@@ -349,51 +330,48 @@ func stsCredentials(minioURL, accessKey, secretKey, location, clientIP string) (
return credentials.New(consoleSTSWrapper), nil
}
func NewConsoleCredentials(accessKey, secretKey, location, clientIP string) (*credentials.Credentials, error) {
func NewConsoleCredentials(accessKey, secretKey, location string, client *http.Client) (*credentials.Credentials, error) {
minioURL := getMinIOServer()
// Future authentication methods can be added under this switch statement
switch {
// LDAP authentication for Console
case ldap.GetLDAPEnabled():
{
creds, err := auth.GetCredentialsFromLDAP(GetConsoleHTTPClient(clientIP), minioURL, accessKey, secretKey)
if ldap.GetLDAPEnabled() {
creds, err := auth.GetCredentialsFromLDAP(client, minioURL, accessKey, secretKey)
if err != nil {
return nil, err
}
credContext := &credentials.CredContext{
Client: client,
}
// We verify if LDAP credentials are correct and no error is returned
_, err = creds.GetWithContext(credContext)
if err != nil && strings.Contains(strings.ToLower(err.Error()), "not found") {
// We try to use STS Credentials in case LDAP credentials are incorrect.
stsCreds, errSTS := stsCredentials(minioURL, accessKey, secretKey, location, client)
// If there is an error with STS too, then we return the original LDAP error
if errSTS != nil {
LogError("error in STS credentials for LDAP case: %v ", errSTS)
// We return LDAP result
return creds, nil
}
_, err := stsCreds.GetWithContext(credContext)
// There is an error with STS credentials, We return the result of LDAP as STS is not a priority in this case.
if err != nil {
return nil, err
return creds, nil
}
// We verify if LDAP credentials are correct and no error is returned
_, err = creds.Get()
if err != nil && strings.Contains(strings.ToLower(err.Error()), "not found") {
// We try to use STS Credentials in case LDAP credentials are incorrect.
stsCreds, errSTS := stsCredentials(minioURL, accessKey, secretKey, location, clientIP)
// If there is an error with STS too, then we return the original LDAP error
if errSTS != nil {
LogError("error in STS credentials for LDAP case: %v ", errSTS)
// We return LDAP result
return creds, nil
}
_, err := stsCreds.Get()
// There is an error with STS credentials, We return the result of LDAP as STS is not a priority in this case.
if err != nil {
return creds, nil
}
return stsCreds, nil
}
return creds, nil
}
// default authentication for Console is via STS (Security Token Service) against MinIO
default:
{
return stsCredentials(minioURL, accessKey, secretKey, location, clientIP)
return stsCreds, nil
}
return creds, nil
}
return stsCredentials(minioURL, accessKey, secretKey, location, client)
}
// getConsoleCredentialsFromSession returns the *consoleCredentials.Login associated to the

View File

@@ -26,9 +26,9 @@ import (
"time"
"github.com/minio/console/pkg/auth/idp/oauth2"
xcerts "github.com/minio/pkg/v2/certs"
"github.com/minio/pkg/v2/env"
xnet "github.com/minio/pkg/v2/net"
xcerts "github.com/minio/pkg/v3/certs"
"github.com/minio/pkg/v3/env"
xnet "github.com/minio/pkg/v3/net"
)
var (
@@ -98,10 +98,6 @@ func getMinIOServer() string {
return strings.TrimSpace(env.Get(ConsoleMinIOServer, "http://localhost:9000"))
}
func getSubnetProxy() string {
return strings.TrimSpace(env.Get(ConsoleSubnetProxy, ""))
}
func GetMinIORegion() string {
return strings.TrimSpace(env.Get(ConsoleMinIORegion, ""))
}
@@ -266,18 +262,10 @@ func getPrometheusURL() string {
return env.Get(PrometheusURL, "")
}
func getPrometheusAuthToken() string {
return env.Get(PrometheusAuthToken, "")
}
func getPrometheusJobID() string {
return env.Get(PrometheusJobID, "minio-job")
}
func getPrometheusExtraLabels() string {
return env.Get(PrometheusExtraLabels, "")
}
func getMaxConcurrentUploadsLimit() int64 {
cu, err := strconv.ParseInt(env.Get(ConsoleMaxConcurrentUploads, "10"), 10, 64)
if err != nil {
@@ -303,3 +291,7 @@ func getConsoleDevMode() bool {
func getConsoleAnimatedLogin() bool {
return strings.ToLower(env.Get(ConsoleAnimatedLogin, "on")) == "on"
}
func getConsoleBrowserRedirectURL() string {
return env.Get(ConsoleBrowserRedirectURL, "")
}

View File

@@ -31,6 +31,8 @@ import (
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"time"
@@ -44,9 +46,9 @@ import (
"github.com/klauspost/compress/gzhttp"
portal_ui "github.com/minio/console/web-app"
"github.com/minio/pkg/v2/env"
"github.com/minio/pkg/v2/mimedb"
xnet "github.com/minio/pkg/v2/net"
"github.com/minio/pkg/v3/env"
"github.com/minio/pkg/v3/mimedb"
xnet "github.com/minio/pkg/v3/net"
"github.com/go-openapi/errors"
"github.com/go-openapi/swag"
@@ -113,72 +115,19 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
registerLogoutHandlers(api)
// Register bucket handlers
registerBucketsHandlers(api)
// Register all users handlers
registerUsersHandlers(api)
// Register groups handlers
registerGroupsHandlers(api)
// Register policies handlers
registersPoliciesHandler(api)
// Register configurations handlers
registerConfigHandlers(api)
// Register bucket events handlers
registerBucketEventsHandlers(api)
// Register bucket lifecycle handlers
registerBucketsLifecycleHandlers(api)
// Register service handlers
registerServiceHandlers(api)
// Register session handlers
registerSessionHandlers(api)
// Register admin info handlers
registerAdminInfoHandlers(api)
// Register admin arns handlers
registerAdminArnsHandlers(api)
// Register admin notification endpoints handlers
registerAdminNotificationEndpointsHandlers(api)
// Register admin Service Account Handlers
registerServiceAccountsHandlers(api)
// Register admin remote buckets
registerAdminBucketRemoteHandlers(api)
// Register admin log search
registerLogSearchHandlers(api)
// Register admin subnet handlers
registerSubnetHandlers(api)
// Register admin KMS handlers
registerKMSHandlers(api)
// Register admin IDP handlers
registerIDPHandlers(api)
// Register Account handlers
registerAdminTiersHandlers(api)
// Register Inspect Handler
registerInspectHandler(api)
// Register nodes handlers
registerNodesHandler(api)
registerSiteReplicationHandler(api)
registerSiteReplicationStatusHandler(api)
// Register Support Handler
registerSupportHandlers(api)
// Operator Console
// Register Object's Handlers
registerObjectsHandlers(api)
// Register Bucket Quota's Handlers
registerBucketQuotaHandlers(api)
// Register Account handlers
registerAccountHandlers(api)
registerReleasesHandlers(api)
// Register Bucket Policy's Handlers
registerPublicObjectsHandlers(api)
api.PreServerShutdown = func() {}
api.ServerShutdown = func() {}
// do an initial subnet plan caching
fetchLicensePlan()
return setupGlobalMiddleware(api.Serve(setupMiddlewares))
}
@@ -216,6 +165,97 @@ func AuditLogMiddleware(next http.Handler) http.Handler {
})
}
func DebugLogMiddleware(next http.Handler) http.Handler {
debugLogLevel, _ := env.GetInt("CONSOLE_DEBUG_LOGLEVEL", 0)
if debugLogLevel == 0 {
return next
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rw := logger.NewResponseWriter(w)
next.ServeHTTP(rw, r)
debugLog(debugLogLevel, r, rw)
})
}
func debugLog(debugLogLevel int, r *http.Request, rw *logger.ResponseWriter) {
switch debugLogLevel {
case 1:
// Log server errors only (summary)
if rw.StatusCode >= 500 {
debugLogSummary(r, rw)
}
case 2:
// Log server and client errors (summary)
if rw.StatusCode >= 400 {
debugLogSummary(r, rw)
}
case 3:
// Log all requests (summary)
debugLogSummary(r, rw)
case 4:
// Log server errors only (including headers)
if rw.StatusCode >= 500 {
debugLogDetails(r, rw)
}
case 5:
// Log server and client errors (including headers)
if rw.StatusCode >= 400 {
debugLogDetails(r, rw)
}
case 6:
// Log all requests (including headers)
debugLogDetails(r, rw)
}
}
func debugLogSummary(r *http.Request, rw *logger.ResponseWriter) {
statusCode := strconv.Itoa(rw.StatusCode)
if rw.Hijacked {
statusCode = "hijacked"
}
logger.Info(fmt.Sprintf("%s %s %s %s %dms", r.RemoteAddr, r.Method, r.URL, statusCode, time.Since(rw.StartTime).Milliseconds()))
}
func debugLogDetails(r *http.Request, rw *logger.ResponseWriter) {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("- Method/URL: %s %s\n", r.Method, r.URL))
sb.WriteString(fmt.Sprintf(" Remote endpoint: %s\n", r.RemoteAddr))
if rw.Hijacked {
sb.WriteString(" Status code: <hijacked, probably a websocket>\n")
} else {
sb.WriteString(fmt.Sprintf(" Status code: %d\n", rw.StatusCode))
}
sb.WriteString(fmt.Sprintf(" Duration (ms): %d\n", time.Since(rw.StartTime).Milliseconds()))
sb.WriteString(" Request headers: ")
debugLogHeaders(&sb, r.Header)
sb.WriteString(" Response headers: ")
debugLogHeaders(&sb, rw.Header())
logger.Info(sb.String())
}
func debugLogHeaders(sb *strings.Builder, h http.Header) {
keys := make([]string, 0, len(h))
for key := range h {
keys = append(keys, key)
}
sort.Strings(keys)
first := true
for _, key := range keys {
values := h[key]
for _, value := range values {
if !first {
sb.WriteString(" ")
} else {
first = false
}
sb.WriteString(fmt.Sprintf("%s: %s\n", key, value))
}
}
if first {
sb.WriteRune('\n')
}
}
// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
// So this is a good place to plug in a panic handling middleware, logger and metrics
func setupGlobalMiddleware(handler http.Handler) http.Handler {
@@ -228,6 +268,8 @@ func setupGlobalMiddleware(handler http.Handler) http.Handler {
next = ContextMiddleware(next)
// handle cookie or authorization header for session
next = AuthenticationMiddleware(next)
// handle debug logging
next = DebugLogMiddleware(next)
sslHostFn := secure.SSLHostFunc(func(host string) string {
xhost, err := xnet.ParseHost(host)
@@ -313,6 +355,12 @@ func AuthenticationMiddleware(next http.Handler) http.Handler {
// FileServerMiddleware serves files from the static folder
func FileServerMiddleware(next http.Handler) http.Handler {
buildFs, err := fs.Sub(portal_ui.GetStaticAssets(), "build")
if err != nil {
panic(err)
}
spaFileHandler := wrapHandlerSinglePageApplication(requestBounce(http.FileServer(http.FS(buildFs))))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", globalAppName) // do not add version information
switch {
@@ -321,11 +369,7 @@ func FileServerMiddleware(next http.Handler) http.Handler {
case strings.HasPrefix(r.URL.Path, "/api"):
next.ServeHTTP(w, r)
default:
buildFs, err := fs.Sub(portal_ui.GetStaticAssets(), "build")
if err != nil {
panic(err)
}
wrapHandlerSinglePageApplication(requestBounce(http.FileServer(http.FS(buildFs)))).ServeHTTP(w, r)
spaFileHandler.ServeHTTP(w, r)
}
})
}
@@ -424,13 +468,10 @@ func handleSPA(w http.ResponseWriter, r *http.Request) {
}
indexPageBytes = replaceLicense(indexPageBytes)
mimeType := mimedb.TypeByExtension(filepath.Ext(r.URL.Path))
if mimeType == "application/octet-stream" {
mimeType = "text/html"
}
w.Header().Set("Content-Type", mimeType)
// it's important to force "Content-Type: text/html", because a previous
// handler may have already set the content-type to a different value.
// (i.e. the FileServer when it detected that it couldn't find the file)
w.Header().Set("Content-Type", "text/html")
http.ServeContent(w, r, "index.html", time.Now(), bytes.NewReader(indexPageBytes))
}
@@ -507,8 +548,6 @@ func replaceBaseInIndex(indexPageBytes []byte, basePath string) []byte {
func replaceLicense(indexPageBytes []byte) []byte {
indexPageStr := string(indexPageBytes)
newPlan := fmt.Sprintf("<meta name=\"minio-license\" content=\"%s\" />", InstanceLicensePlan.String())
indexPageStr = strings.Replace(indexPageStr, "<meta name=\"minio-license\" content=\"agpl\"/>", newPlan, 1)
indexPageBytes = []byte(indexPageStr)
return indexPageBytes
}

View File

@@ -56,6 +56,7 @@ const (
ConsoleMaxConcurrentDownloads = "CONSOLE_MAX_CONCURRENT_DOWNLOADS"
ConsoleDevMode = "CONSOLE_DEV_MODE"
ConsoleAnimatedLogin = "CONSOLE_ANIMATED_LOGIN"
ConsoleBrowserRedirectURL = "CONSOLE_BROWSER_REDIRECT_URL"
LogSearchQueryAuthToken = "LOGSEARCH_QUERY_AUTH_TOKEN"
SlashSeparator = "/"
LocalAddress = "127.0.0.1"

View File

@@ -30,7 +30,6 @@
// - multipart/form-data
//
// Produces:
// - application/zip
// - application/octet-stream
// - application/json
//

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,7 @@ import (
var (
ErrDefault = errors.New("an error occurred, please try again")
ErrInvalidLogin = errors.New("invalid Login")
ErrInvalidLogin = errors.New("invalid login")
ErrForbidden = errors.New("403 Forbidden")
ErrBadRequest = errors.New("400 Bad Request")
ErrFileTooLarge = errors.New("413 File too Large")
@@ -43,7 +43,6 @@ var (
ErrGroupNameNotInRequest = errors.New("error group name not in request")
ErrPolicyNameNotInRequest = errors.New("error policy name not in request")
ErrPolicyBodyNotInRequest = errors.New("error policy body not in request")
ErrPolicyNameContainsSpace = errors.New("error policy name cannot contain spaces")
ErrInvalidEncryptionAlgorithm = errors.New("error invalid encryption algorithm")
ErrSSENotConfigured = errors.New("error server side encryption configuration not found")
ErrBucketLifeCycleNotConfigured = errors.New("error bucket life cycle configuration not found")
@@ -73,6 +72,7 @@ var (
ErrPolicyNotFound = errors.New("policy does not exist")
ErrLoginNotAllowed = errors.New("login not allowed")
ErrHealthReportFail = errors.New("failure to generate Health report")
ErrNetworkError = errors.New("unable to login due to network error")
)
type CodedAPIError struct {
@@ -111,6 +111,11 @@ func ErrorWithContext(ctx context.Context, err ...interface{}) *CodedAPIError {
errorCode = 401
errorMessage = ErrInvalidLogin.Error()
}
if errors.Is(err1, ErrNetworkError) {
detailedMessage = ""
errorCode = 503
errorMessage = ErrNetworkError.Error()
}
if strings.Contains(strings.ToLower(err1.Error()), ErrAccessDenied.Error()) {
errorCode = 403
errorMessage = err1.Error()
@@ -155,10 +160,6 @@ func ErrorWithContext(ctx context.Context, err ...interface{}) *CodedAPIError {
errorCode = 400
errorMessage = ErrPolicyBodyNotInRequest.Error()
}
if errors.Is(err1, ErrPolicyNameContainsSpace) {
errorCode = 400
errorMessage = ErrPolicyNameContainsSpace.Error()
}
// console invalid session errors
if errors.Is(err1, ErrInvalidSession) {
errorCode = 401

View File

@@ -46,25 +46,24 @@ func TestError(t *testing.T) {
appErrors := map[string]expectedError{
"ErrDefault": {code: 500, err: ErrDefault},
"ErrForbidden": {code: 403, err: ErrForbidden},
"ErrFileTooLarge": {code: 413, err: ErrFileTooLarge},
"ErrInvalidSession": {code: 401, err: ErrInvalidSession},
"ErrNotFound": {code: 404, err: ErrNotFound},
"ErrGroupAlreadyExists": {code: 400, err: ErrGroupAlreadyExists},
"ErrInvalidErasureCodingValue": {code: 400, err: ErrInvalidErasureCodingValue},
"ErrBucketBodyNotInRequest": {code: 400, err: ErrBucketBodyNotInRequest},
"ErrBucketNameNotInRequest": {code: 400, err: ErrBucketNameNotInRequest},
"ErrGroupBodyNotInRequest": {code: 400, err: ErrGroupBodyNotInRequest},
"ErrGroupNameNotInRequest": {code: 400, err: ErrGroupNameNotInRequest},
"ErrPolicyNameNotInRequest": {code: 400, err: ErrPolicyNameNotInRequest},
"ErrPolicyBodyNotInRequest": {code: 400, err: ErrPolicyBodyNotInRequest},
"ErrInvalidEncryptionAlgorithm": {code: 500, err: ErrInvalidEncryptionAlgorithm},
"ErrSSENotConfigured": {code: 404, err: ErrSSENotConfigured},
"ErrBucketLifeCycleNotConfigured": {code: 404, err: ErrBucketLifeCycleNotConfigured},
"ErrChangePassword": {code: 403, err: ErrChangePassword},
"ErrInvalidLicense": {code: 404, err: ErrInvalidLicense},
"ErrLicenseNotFound": {code: 404, err: ErrLicenseNotFound},
"ErrAvoidSelfAccountDelete": {code: 403, err: ErrAvoidSelfAccountDelete},
"ErrForbidden": {code: 403, err: ErrForbidden},
"ErrFileTooLarge": {code: 413, err: ErrFileTooLarge},
"ErrInvalidSession": {code: 401, err: ErrInvalidSession},
"ErrNotFound": {code: 404, err: ErrNotFound},
"ErrGroupAlreadyExists": {code: 400, err: ErrGroupAlreadyExists},
"ErrInvalidErasureCodingValue": {code: 400, err: ErrInvalidErasureCodingValue},
"ErrBucketBodyNotInRequest": {code: 400, err: ErrBucketBodyNotInRequest},
"ErrBucketNameNotInRequest": {code: 400, err: ErrBucketNameNotInRequest},
"ErrGroupBodyNotInRequest": {code: 400, err: ErrGroupBodyNotInRequest},
"ErrGroupNameNotInRequest": {code: 400, err: ErrGroupNameNotInRequest},
"ErrPolicyNameNotInRequest": {code: 400, err: ErrPolicyNameNotInRequest},
"ErrPolicyBodyNotInRequest": {code: 400, err: ErrPolicyBodyNotInRequest},
"ErrInvalidEncryptionAlgorithm": {code: 500, err: ErrInvalidEncryptionAlgorithm},
"ErrSSENotConfigured": {code: 404, err: ErrSSENotConfigured},
"ErrChangePassword": {code: 403, err: ErrChangePassword},
"ErrInvalidLicense": {code: 404, err: ErrInvalidLicense},
"ErrLicenseNotFound": {code: 404, err: ErrLicenseNotFound},
"ErrAvoidSelfAccountDelete": {code: 403, err: ErrAvoidSelfAccountDelete},
"ErrNonUniqueAccessKey": {code: 500, err: ErrNonUniqueAccessKey},
"ErrRemoteTierExists": {code: 400, err: ErrRemoteTierExists},

View File

@@ -1,71 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"net/http"
"os"
"github.com/minio/pkg/v2/licverifier"
"github.com/minio/pkg/v2/subnet"
)
type SubnetPlan int
const (
PlanAGPL SubnetPlan = iota
PlanStandard
PlanEnterprise
)
func (sp SubnetPlan) String() string {
switch sp {
case PlanStandard:
return "standard"
case PlanEnterprise:
return "enterprise"
default:
return "agpl"
}
}
var InstanceLicensePlan = PlanAGPL
func getLicenseInfo(client http.Client, license string) (*licverifier.LicenseInfo, error) {
lv := subnet.LicenseValidator{
Client: client,
ExpiryGracePeriod: 0,
}
lv.Init(getConsoleDevMode())
return lv.ParseLicense(license)
}
func fetchLicensePlan() {
client := GetConsoleHTTPClient("127.0.0.1")
licenseInfo, err := getLicenseInfo(*client, os.Getenv(EnvSubnetLicense))
if err != nil {
return
}
switch licenseInfo.Plan {
case "STANDARD":
InstanceLicensePlan = PlanStandard
case "ENTERPRISE":
InstanceLicensePlan = PlanEnterprise
default:
InstanceLicensePlan = PlanAGPL
}
}

View File

@@ -1,88 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package account
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// AccountChangePasswordHandlerFunc turns a function with the right signature into a account change password handler
type AccountChangePasswordHandlerFunc func(AccountChangePasswordParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn AccountChangePasswordHandlerFunc) Handle(params AccountChangePasswordParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// AccountChangePasswordHandler interface for that can handle valid account change password params
type AccountChangePasswordHandler interface {
Handle(AccountChangePasswordParams, *models.Principal) middleware.Responder
}
// NewAccountChangePassword creates a new http.Handler for the account change password operation
func NewAccountChangePassword(ctx *middleware.Context, handler AccountChangePasswordHandler) *AccountChangePassword {
return &AccountChangePassword{Context: ctx, Handler: handler}
}
/*
AccountChangePassword swagger:route POST /account/change-password Account accountChangePassword
Change password of currently logged in user.
*/
type AccountChangePassword struct {
Context *middleware.Context
Handler AccountChangePasswordHandler
}
func (o *AccountChangePassword) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewAccountChangePasswordParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -1,101 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package account
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewAccountChangePasswordParams creates a new AccountChangePasswordParams object
//
// There are no default values defined in the spec.
func NewAccountChangePasswordParams() AccountChangePasswordParams {
return AccountChangePasswordParams{}
}
// AccountChangePasswordParams contains all the bound params for the account change password operation
// typically these are obtained from a http.Request
//
// swagger:parameters AccountChangePassword
type AccountChangePasswordParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.AccountChangePasswordRequest
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewAccountChangePasswordParams() beforehand.
func (o *AccountChangePasswordParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.AccountChangePasswordRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(r.Context())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -1,115 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package account
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// AccountChangePasswordNoContentCode is the HTTP code returned for type AccountChangePasswordNoContent
const AccountChangePasswordNoContentCode int = 204
/*
AccountChangePasswordNoContent A successful login.
swagger:response accountChangePasswordNoContent
*/
type AccountChangePasswordNoContent struct {
}
// NewAccountChangePasswordNoContent creates AccountChangePasswordNoContent with default headers values
func NewAccountChangePasswordNoContent() *AccountChangePasswordNoContent {
return &AccountChangePasswordNoContent{}
}
// WriteResponse to the client
func (o *AccountChangePasswordNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(204)
}
/*
AccountChangePasswordDefault Generic error response.
swagger:response accountChangePasswordDefault
*/
type AccountChangePasswordDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewAccountChangePasswordDefault creates AccountChangePasswordDefault with default headers values
func NewAccountChangePasswordDefault(code int) *AccountChangePasswordDefault {
if code <= 0 {
code = 500
}
return &AccountChangePasswordDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the account change password default response
func (o *AccountChangePasswordDefault) WithStatusCode(code int) *AccountChangePasswordDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the account change password default response
func (o *AccountChangePasswordDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the account change password default response
func (o *AccountChangePasswordDefault) WithPayload(payload *models.APIError) *AccountChangePasswordDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the account change password default response
func (o *AccountChangePasswordDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *AccountChangePasswordDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,104 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package account
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// AccountChangePasswordURL generates an URL for the account change password operation
type AccountChangePasswordURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *AccountChangePasswordURL) WithBasePath(bp string) *AccountChangePasswordURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *AccountChangePasswordURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *AccountChangePasswordURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/account/change-password"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *AccountChangePasswordURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *AccountChangePasswordURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *AccountChangePasswordURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on AccountChangePasswordURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on AccountChangePasswordURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *AccountChangePasswordURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,88 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package account
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// ChangeUserPasswordHandlerFunc turns a function with the right signature into a change user password handler
type ChangeUserPasswordHandlerFunc func(ChangeUserPasswordParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn ChangeUserPasswordHandlerFunc) Handle(params ChangeUserPasswordParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// ChangeUserPasswordHandler interface for that can handle valid change user password params
type ChangeUserPasswordHandler interface {
Handle(ChangeUserPasswordParams, *models.Principal) middleware.Responder
}
// NewChangeUserPassword creates a new http.Handler for the change user password operation
func NewChangeUserPassword(ctx *middleware.Context, handler ChangeUserPasswordHandler) *ChangeUserPassword {
return &ChangeUserPassword{Context: ctx, Handler: handler}
}
/*
ChangeUserPassword swagger:route POST /account/change-user-password Account changeUserPassword
Change password of currently logged in user.
*/
type ChangeUserPassword struct {
Context *middleware.Context
Handler ChangeUserPasswordHandler
}
func (o *ChangeUserPassword) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewChangeUserPasswordParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -1,101 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package account
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewChangeUserPasswordParams creates a new ChangeUserPasswordParams object
//
// There are no default values defined in the spec.
func NewChangeUserPasswordParams() ChangeUserPasswordParams {
return ChangeUserPasswordParams{}
}
// ChangeUserPasswordParams contains all the bound params for the change user password operation
// typically these are obtained from a http.Request
//
// swagger:parameters ChangeUserPassword
type ChangeUserPasswordParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.ChangeUserPasswordRequest
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewChangeUserPasswordParams() beforehand.
func (o *ChangeUserPasswordParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.ChangeUserPasswordRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(r.Context())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -1,115 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package account
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// ChangeUserPasswordCreatedCode is the HTTP code returned for type ChangeUserPasswordCreated
const ChangeUserPasswordCreatedCode int = 201
/*
ChangeUserPasswordCreated Password successfully changed.
swagger:response changeUserPasswordCreated
*/
type ChangeUserPasswordCreated struct {
}
// NewChangeUserPasswordCreated creates ChangeUserPasswordCreated with default headers values
func NewChangeUserPasswordCreated() *ChangeUserPasswordCreated {
return &ChangeUserPasswordCreated{}
}
// WriteResponse to the client
func (o *ChangeUserPasswordCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(201)
}
/*
ChangeUserPasswordDefault Generic error response.
swagger:response changeUserPasswordDefault
*/
type ChangeUserPasswordDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewChangeUserPasswordDefault creates ChangeUserPasswordDefault with default headers values
func NewChangeUserPasswordDefault(code int) *ChangeUserPasswordDefault {
if code <= 0 {
code = 500
}
return &ChangeUserPasswordDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the change user password default response
func (o *ChangeUserPasswordDefault) WithStatusCode(code int) *ChangeUserPasswordDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the change user password default response
func (o *ChangeUserPasswordDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the change user password default response
func (o *ChangeUserPasswordDefault) WithPayload(payload *models.APIError) *ChangeUserPasswordDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the change user password default response
func (o *ChangeUserPasswordDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *ChangeUserPasswordDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,104 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package account
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// ChangeUserPasswordURL generates an URL for the change user password operation
type ChangeUserPasswordURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *ChangeUserPasswordURL) WithBasePath(bp string) *ChangeUserPasswordURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *ChangeUserPasswordURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *ChangeUserPasswordURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/account/change-user-password"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *ChangeUserPasswordURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *ChangeUserPasswordURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *ChangeUserPasswordURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on ChangeUserPasswordURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on ChangeUserPasswordURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *ChangeUserPasswordURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,88 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// AddBucketLifecycleHandlerFunc turns a function with the right signature into a add bucket lifecycle handler
type AddBucketLifecycleHandlerFunc func(AddBucketLifecycleParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn AddBucketLifecycleHandlerFunc) Handle(params AddBucketLifecycleParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// AddBucketLifecycleHandler interface for that can handle valid add bucket lifecycle params
type AddBucketLifecycleHandler interface {
Handle(AddBucketLifecycleParams, *models.Principal) middleware.Responder
}
// NewAddBucketLifecycle creates a new http.Handler for the add bucket lifecycle operation
func NewAddBucketLifecycle(ctx *middleware.Context, handler AddBucketLifecycleHandler) *AddBucketLifecycle {
return &AddBucketLifecycle{Context: ctx, Handler: handler}
}
/*
AddBucketLifecycle swagger:route POST /buckets/{bucket_name}/lifecycle Bucket addBucketLifecycle
Add Bucket Lifecycle
*/
type AddBucketLifecycle struct {
Context *middleware.Context
Handler AddBucketLifecycleHandler
}
func (o *AddBucketLifecycle) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewAddBucketLifecycleParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -1,126 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewAddBucketLifecycleParams creates a new AddBucketLifecycleParams object
//
// There are no default values defined in the spec.
func NewAddBucketLifecycleParams() AddBucketLifecycleParams {
return AddBucketLifecycleParams{}
}
// AddBucketLifecycleParams contains all the bound params for the add bucket lifecycle operation
// typically these are obtained from a http.Request
//
// swagger:parameters AddBucketLifecycle
type AddBucketLifecycleParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.AddBucketLifecycle
/*
Required: true
In: path
*/
BucketName string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewAddBucketLifecycleParams() beforehand.
func (o *AddBucketLifecycleParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.AddBucketLifecycle
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(r.Context())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindBucketName binds and validates parameter BucketName from path.
func (o *AddBucketLifecycleParams) bindBucketName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.BucketName = raw
return nil
}

View File

@@ -1,115 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// AddBucketLifecycleCreatedCode is the HTTP code returned for type AddBucketLifecycleCreated
const AddBucketLifecycleCreatedCode int = 201
/*
AddBucketLifecycleCreated A successful response.
swagger:response addBucketLifecycleCreated
*/
type AddBucketLifecycleCreated struct {
}
// NewAddBucketLifecycleCreated creates AddBucketLifecycleCreated with default headers values
func NewAddBucketLifecycleCreated() *AddBucketLifecycleCreated {
return &AddBucketLifecycleCreated{}
}
// WriteResponse to the client
func (o *AddBucketLifecycleCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(201)
}
/*
AddBucketLifecycleDefault Generic error response.
swagger:response addBucketLifecycleDefault
*/
type AddBucketLifecycleDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewAddBucketLifecycleDefault creates AddBucketLifecycleDefault with default headers values
func NewAddBucketLifecycleDefault(code int) *AddBucketLifecycleDefault {
if code <= 0 {
code = 500
}
return &AddBucketLifecycleDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the add bucket lifecycle default response
func (o *AddBucketLifecycleDefault) WithStatusCode(code int) *AddBucketLifecycleDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the add bucket lifecycle default response
func (o *AddBucketLifecycleDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the add bucket lifecycle default response
func (o *AddBucketLifecycleDefault) WithPayload(payload *models.APIError) *AddBucketLifecycleDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the add bucket lifecycle default response
func (o *AddBucketLifecycleDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *AddBucketLifecycleDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,116 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// AddBucketLifecycleURL generates an URL for the add bucket lifecycle operation
type AddBucketLifecycleURL struct {
BucketName string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *AddBucketLifecycleURL) WithBasePath(bp string) *AddBucketLifecycleURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *AddBucketLifecycleURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *AddBucketLifecycleURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/{bucket_name}/lifecycle"
bucketName := o.BucketName
if bucketName != "" {
_path = strings.Replace(_path, "{bucket_name}", bucketName, -1)
} else {
return nil, errors.New("bucketName is required on AddBucketLifecycleURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *AddBucketLifecycleURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *AddBucketLifecycleURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *AddBucketLifecycleURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on AddBucketLifecycleURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on AddBucketLifecycleURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *AddBucketLifecycleURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,88 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// AddMultiBucketLifecycleHandlerFunc turns a function with the right signature into a add multi bucket lifecycle handler
type AddMultiBucketLifecycleHandlerFunc func(AddMultiBucketLifecycleParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn AddMultiBucketLifecycleHandlerFunc) Handle(params AddMultiBucketLifecycleParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// AddMultiBucketLifecycleHandler interface for that can handle valid add multi bucket lifecycle params
type AddMultiBucketLifecycleHandler interface {
Handle(AddMultiBucketLifecycleParams, *models.Principal) middleware.Responder
}
// NewAddMultiBucketLifecycle creates a new http.Handler for the add multi bucket lifecycle operation
func NewAddMultiBucketLifecycle(ctx *middleware.Context, handler AddMultiBucketLifecycleHandler) *AddMultiBucketLifecycle {
return &AddMultiBucketLifecycle{Context: ctx, Handler: handler}
}
/*
AddMultiBucketLifecycle swagger:route POST /buckets/multi-lifecycle Bucket addMultiBucketLifecycle
Add Multi Bucket Lifecycle
*/
type AddMultiBucketLifecycle struct {
Context *middleware.Context
Handler AddMultiBucketLifecycleHandler
}
func (o *AddMultiBucketLifecycle) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewAddMultiBucketLifecycleParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -1,101 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewAddMultiBucketLifecycleParams creates a new AddMultiBucketLifecycleParams object
//
// There are no default values defined in the spec.
func NewAddMultiBucketLifecycleParams() AddMultiBucketLifecycleParams {
return AddMultiBucketLifecycleParams{}
}
// AddMultiBucketLifecycleParams contains all the bound params for the add multi bucket lifecycle operation
// typically these are obtained from a http.Request
//
// swagger:parameters AddMultiBucketLifecycle
type AddMultiBucketLifecycleParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.AddMultiBucketLifecycle
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewAddMultiBucketLifecycleParams() beforehand.
func (o *AddMultiBucketLifecycleParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.AddMultiBucketLifecycle
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(r.Context())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -1,135 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// AddMultiBucketLifecycleOKCode is the HTTP code returned for type AddMultiBucketLifecycleOK
const AddMultiBucketLifecycleOKCode int = 200
/*
AddMultiBucketLifecycleOK A successful response.
swagger:response addMultiBucketLifecycleOK
*/
type AddMultiBucketLifecycleOK struct {
/*
In: Body
*/
Payload *models.MultiLifecycleResult `json:"body,omitempty"`
}
// NewAddMultiBucketLifecycleOK creates AddMultiBucketLifecycleOK with default headers values
func NewAddMultiBucketLifecycleOK() *AddMultiBucketLifecycleOK {
return &AddMultiBucketLifecycleOK{}
}
// WithPayload adds the payload to the add multi bucket lifecycle o k response
func (o *AddMultiBucketLifecycleOK) WithPayload(payload *models.MultiLifecycleResult) *AddMultiBucketLifecycleOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the add multi bucket lifecycle o k response
func (o *AddMultiBucketLifecycleOK) SetPayload(payload *models.MultiLifecycleResult) {
o.Payload = payload
}
// WriteResponse to the client
func (o *AddMultiBucketLifecycleOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*
AddMultiBucketLifecycleDefault Generic error response.
swagger:response addMultiBucketLifecycleDefault
*/
type AddMultiBucketLifecycleDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewAddMultiBucketLifecycleDefault creates AddMultiBucketLifecycleDefault with default headers values
func NewAddMultiBucketLifecycleDefault(code int) *AddMultiBucketLifecycleDefault {
if code <= 0 {
code = 500
}
return &AddMultiBucketLifecycleDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the add multi bucket lifecycle default response
func (o *AddMultiBucketLifecycleDefault) WithStatusCode(code int) *AddMultiBucketLifecycleDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the add multi bucket lifecycle default response
func (o *AddMultiBucketLifecycleDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the add multi bucket lifecycle default response
func (o *AddMultiBucketLifecycleDefault) WithPayload(payload *models.APIError) *AddMultiBucketLifecycleDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the add multi bucket lifecycle default response
func (o *AddMultiBucketLifecycleDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *AddMultiBucketLifecycleDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,104 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// AddMultiBucketLifecycleURL generates an URL for the add multi bucket lifecycle operation
type AddMultiBucketLifecycleURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *AddMultiBucketLifecycleURL) WithBasePath(bp string) *AddMultiBucketLifecycleURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *AddMultiBucketLifecycleURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *AddMultiBucketLifecycleURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/multi-lifecycle"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *AddMultiBucketLifecycleURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *AddMultiBucketLifecycleURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *AddMultiBucketLifecycleURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on AddMultiBucketLifecycleURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on AddMultiBucketLifecycleURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *AddMultiBucketLifecycleURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,88 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// AddRemoteBucketHandlerFunc turns a function with the right signature into a add remote bucket handler
type AddRemoteBucketHandlerFunc func(AddRemoteBucketParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn AddRemoteBucketHandlerFunc) Handle(params AddRemoteBucketParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// AddRemoteBucketHandler interface for that can handle valid add remote bucket params
type AddRemoteBucketHandler interface {
Handle(AddRemoteBucketParams, *models.Principal) middleware.Responder
}
// NewAddRemoteBucket creates a new http.Handler for the add remote bucket operation
func NewAddRemoteBucket(ctx *middleware.Context, handler AddRemoteBucketHandler) *AddRemoteBucket {
return &AddRemoteBucket{Context: ctx, Handler: handler}
}
/*
AddRemoteBucket swagger:route POST /remote-buckets Bucket addRemoteBucket
Add Remote Bucket
*/
type AddRemoteBucket struct {
Context *middleware.Context
Handler AddRemoteBucketHandler
}
func (o *AddRemoteBucket) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewAddRemoteBucketParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -1,101 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewAddRemoteBucketParams creates a new AddRemoteBucketParams object
//
// There are no default values defined in the spec.
func NewAddRemoteBucketParams() AddRemoteBucketParams {
return AddRemoteBucketParams{}
}
// AddRemoteBucketParams contains all the bound params for the add remote bucket operation
// typically these are obtained from a http.Request
//
// swagger:parameters AddRemoteBucket
type AddRemoteBucketParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.CreateRemoteBucket
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewAddRemoteBucketParams() beforehand.
func (o *AddRemoteBucketParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.CreateRemoteBucket
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(r.Context())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -1,115 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// AddRemoteBucketCreatedCode is the HTTP code returned for type AddRemoteBucketCreated
const AddRemoteBucketCreatedCode int = 201
/*
AddRemoteBucketCreated A successful response.
swagger:response addRemoteBucketCreated
*/
type AddRemoteBucketCreated struct {
}
// NewAddRemoteBucketCreated creates AddRemoteBucketCreated with default headers values
func NewAddRemoteBucketCreated() *AddRemoteBucketCreated {
return &AddRemoteBucketCreated{}
}
// WriteResponse to the client
func (o *AddRemoteBucketCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(201)
}
/*
AddRemoteBucketDefault Generic error response.
swagger:response addRemoteBucketDefault
*/
type AddRemoteBucketDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewAddRemoteBucketDefault creates AddRemoteBucketDefault with default headers values
func NewAddRemoteBucketDefault(code int) *AddRemoteBucketDefault {
if code <= 0 {
code = 500
}
return &AddRemoteBucketDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the add remote bucket default response
func (o *AddRemoteBucketDefault) WithStatusCode(code int) *AddRemoteBucketDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the add remote bucket default response
func (o *AddRemoteBucketDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the add remote bucket default response
func (o *AddRemoteBucketDefault) WithPayload(payload *models.APIError) *AddRemoteBucketDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the add remote bucket default response
func (o *AddRemoteBucketDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *AddRemoteBucketDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,104 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// AddRemoteBucketURL generates an URL for the add remote bucket operation
type AddRemoteBucketURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *AddRemoteBucketURL) WithBasePath(bp string) *AddRemoteBucketURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *AddRemoteBucketURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *AddRemoteBucketURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/remote-buckets"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *AddRemoteBucketURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *AddRemoteBucketURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *AddRemoteBucketURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on AddRemoteBucketURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on AddRemoteBucketURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *AddRemoteBucketURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,88 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// BucketSetPolicyHandlerFunc turns a function with the right signature into a bucket set policy handler
type BucketSetPolicyHandlerFunc func(BucketSetPolicyParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn BucketSetPolicyHandlerFunc) Handle(params BucketSetPolicyParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// BucketSetPolicyHandler interface for that can handle valid bucket set policy params
type BucketSetPolicyHandler interface {
Handle(BucketSetPolicyParams, *models.Principal) middleware.Responder
}
// NewBucketSetPolicy creates a new http.Handler for the bucket set policy operation
func NewBucketSetPolicy(ctx *middleware.Context, handler BucketSetPolicyHandler) *BucketSetPolicy {
return &BucketSetPolicy{Context: ctx, Handler: handler}
}
/*
BucketSetPolicy swagger:route PUT /buckets/{name}/set-policy Bucket bucketSetPolicy
Bucket Set Policy
*/
type BucketSetPolicy struct {
Context *middleware.Context
Handler BucketSetPolicyHandler
}
func (o *BucketSetPolicy) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewBucketSetPolicyParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -1,126 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewBucketSetPolicyParams creates a new BucketSetPolicyParams object
//
// There are no default values defined in the spec.
func NewBucketSetPolicyParams() BucketSetPolicyParams {
return BucketSetPolicyParams{}
}
// BucketSetPolicyParams contains all the bound params for the bucket set policy operation
// typically these are obtained from a http.Request
//
// swagger:parameters BucketSetPolicy
type BucketSetPolicyParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.SetBucketPolicyRequest
/*
Required: true
In: path
*/
Name string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewBucketSetPolicyParams() beforehand.
func (o *BucketSetPolicyParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.SetBucketPolicyRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(r.Context())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
rName, rhkName, _ := route.Params.GetOK("name")
if err := o.bindName(rName, rhkName, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindName binds and validates parameter Name from path.
func (o *BucketSetPolicyParams) bindName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Name = raw
return nil
}

View File

@@ -1,135 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// BucketSetPolicyOKCode is the HTTP code returned for type BucketSetPolicyOK
const BucketSetPolicyOKCode int = 200
/*
BucketSetPolicyOK A successful response.
swagger:response bucketSetPolicyOK
*/
type BucketSetPolicyOK struct {
/*
In: Body
*/
Payload *models.Bucket `json:"body,omitempty"`
}
// NewBucketSetPolicyOK creates BucketSetPolicyOK with default headers values
func NewBucketSetPolicyOK() *BucketSetPolicyOK {
return &BucketSetPolicyOK{}
}
// WithPayload adds the payload to the bucket set policy o k response
func (o *BucketSetPolicyOK) WithPayload(payload *models.Bucket) *BucketSetPolicyOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the bucket set policy o k response
func (o *BucketSetPolicyOK) SetPayload(payload *models.Bucket) {
o.Payload = payload
}
// WriteResponse to the client
func (o *BucketSetPolicyOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*
BucketSetPolicyDefault Generic error response.
swagger:response bucketSetPolicyDefault
*/
type BucketSetPolicyDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewBucketSetPolicyDefault creates BucketSetPolicyDefault with default headers values
func NewBucketSetPolicyDefault(code int) *BucketSetPolicyDefault {
if code <= 0 {
code = 500
}
return &BucketSetPolicyDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the bucket set policy default response
func (o *BucketSetPolicyDefault) WithStatusCode(code int) *BucketSetPolicyDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the bucket set policy default response
func (o *BucketSetPolicyDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the bucket set policy default response
func (o *BucketSetPolicyDefault) WithPayload(payload *models.APIError) *BucketSetPolicyDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the bucket set policy default response
func (o *BucketSetPolicyDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *BucketSetPolicyDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,116 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// BucketSetPolicyURL generates an URL for the bucket set policy operation
type BucketSetPolicyURL struct {
Name string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *BucketSetPolicyURL) WithBasePath(bp string) *BucketSetPolicyURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *BucketSetPolicyURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *BucketSetPolicyURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/{name}/set-policy"
name := o.Name
if name != "" {
_path = strings.Replace(_path, "{name}", name, -1)
} else {
return nil, errors.New("name is required on BucketSetPolicyURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *BucketSetPolicyURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *BucketSetPolicyURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *BucketSetPolicyURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on BucketSetPolicyURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on BucketSetPolicyURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *BucketSetPolicyURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,88 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// CreateBucketEventHandlerFunc turns a function with the right signature into a create bucket event handler
type CreateBucketEventHandlerFunc func(CreateBucketEventParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn CreateBucketEventHandlerFunc) Handle(params CreateBucketEventParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// CreateBucketEventHandler interface for that can handle valid create bucket event params
type CreateBucketEventHandler interface {
Handle(CreateBucketEventParams, *models.Principal) middleware.Responder
}
// NewCreateBucketEvent creates a new http.Handler for the create bucket event operation
func NewCreateBucketEvent(ctx *middleware.Context, handler CreateBucketEventHandler) *CreateBucketEvent {
return &CreateBucketEvent{Context: ctx, Handler: handler}
}
/*
CreateBucketEvent swagger:route POST /buckets/{bucket_name}/events Bucket createBucketEvent
Create Bucket Event
*/
type CreateBucketEvent struct {
Context *middleware.Context
Handler CreateBucketEventHandler
}
func (o *CreateBucketEvent) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewCreateBucketEventParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -1,126 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewCreateBucketEventParams creates a new CreateBucketEventParams object
//
// There are no default values defined in the spec.
func NewCreateBucketEventParams() CreateBucketEventParams {
return CreateBucketEventParams{}
}
// CreateBucketEventParams contains all the bound params for the create bucket event operation
// typically these are obtained from a http.Request
//
// swagger:parameters CreateBucketEvent
type CreateBucketEventParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.BucketEventRequest
/*
Required: true
In: path
*/
BucketName string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewCreateBucketEventParams() beforehand.
func (o *CreateBucketEventParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.BucketEventRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body", ""))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(r.Context())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body", ""))
}
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindBucketName binds and validates parameter BucketName from path.
func (o *CreateBucketEventParams) bindBucketName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.BucketName = raw
return nil
}

View File

@@ -1,115 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// CreateBucketEventCreatedCode is the HTTP code returned for type CreateBucketEventCreated
const CreateBucketEventCreatedCode int = 201
/*
CreateBucketEventCreated A successful response.
swagger:response createBucketEventCreated
*/
type CreateBucketEventCreated struct {
}
// NewCreateBucketEventCreated creates CreateBucketEventCreated with default headers values
func NewCreateBucketEventCreated() *CreateBucketEventCreated {
return &CreateBucketEventCreated{}
}
// WriteResponse to the client
func (o *CreateBucketEventCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(201)
}
/*
CreateBucketEventDefault Generic error response.
swagger:response createBucketEventDefault
*/
type CreateBucketEventDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewCreateBucketEventDefault creates CreateBucketEventDefault with default headers values
func NewCreateBucketEventDefault(code int) *CreateBucketEventDefault {
if code <= 0 {
code = 500
}
return &CreateBucketEventDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the create bucket event default response
func (o *CreateBucketEventDefault) WithStatusCode(code int) *CreateBucketEventDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the create bucket event default response
func (o *CreateBucketEventDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the create bucket event default response
func (o *CreateBucketEventDefault) WithPayload(payload *models.APIError) *CreateBucketEventDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the create bucket event default response
func (o *CreateBucketEventDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *CreateBucketEventDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -1,116 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// CreateBucketEventURL generates an URL for the create bucket event operation
type CreateBucketEventURL struct {
BucketName string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *CreateBucketEventURL) WithBasePath(bp string) *CreateBucketEventURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *CreateBucketEventURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *CreateBucketEventURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/buckets/{bucket_name}/events"
bucketName := o.BucketName
if bucketName != "" {
_path = strings.Replace(_path, "{bucket_name}", bucketName, -1)
} else {
return nil, errors.New("bucketName is required on CreateBucketEventURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *CreateBucketEventURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *CreateBucketEventURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *CreateBucketEventURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on CreateBucketEventURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on CreateBucketEventURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *CreateBucketEventURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,88 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// DeleteAccessRuleWithBucketHandlerFunc turns a function with the right signature into a delete access rule with bucket handler
type DeleteAccessRuleWithBucketHandlerFunc func(DeleteAccessRuleWithBucketParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn DeleteAccessRuleWithBucketHandlerFunc) Handle(params DeleteAccessRuleWithBucketParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// DeleteAccessRuleWithBucketHandler interface for that can handle valid delete access rule with bucket params
type DeleteAccessRuleWithBucketHandler interface {
Handle(DeleteAccessRuleWithBucketParams, *models.Principal) middleware.Responder
}
// NewDeleteAccessRuleWithBucket creates a new http.Handler for the delete access rule with bucket operation
func NewDeleteAccessRuleWithBucket(ctx *middleware.Context, handler DeleteAccessRuleWithBucketHandler) *DeleteAccessRuleWithBucket {
return &DeleteAccessRuleWithBucket{Context: ctx, Handler: handler}
}
/*
DeleteAccessRuleWithBucket swagger:route DELETE /bucket/{bucket}/access-rules Bucket deleteAccessRuleWithBucket
Delete Access Rule From Given Bucket
*/
type DeleteAccessRuleWithBucket struct {
Context *middleware.Context
Handler DeleteAccessRuleWithBucketHandler
}
func (o *DeleteAccessRuleWithBucket) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewDeleteAccessRuleWithBucketParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -1,126 +0,0 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package bucket
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"github.com/minio/console/models"
)
// NewDeleteAccessRuleWithBucketParams creates a new DeleteAccessRuleWithBucketParams object
//
// There are no default values defined in the spec.
func NewDeleteAccessRuleWithBucketParams() DeleteAccessRuleWithBucketParams {
return DeleteAccessRuleWithBucketParams{}
}
// DeleteAccessRuleWithBucketParams contains all the bound params for the delete access rule with bucket operation
// typically these are obtained from a http.Request
//
// swagger:parameters DeleteAccessRuleWithBucket
type DeleteAccessRuleWithBucketParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: path
*/
Bucket string
/*
Required: true
In: body
*/
Prefix *models.PrefixWrapper
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewDeleteAccessRuleWithBucketParams() beforehand.
func (o *DeleteAccessRuleWithBucketParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
rBucket, rhkBucket, _ := route.Params.GetOK("bucket")
if err := o.bindBucket(rBucket, rhkBucket, route.Formats); err != nil {
res = append(res, err)
}
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.PrefixWrapper
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("prefix", "body", ""))
} else {
res = append(res, errors.NewParseError("prefix", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(r.Context())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Prefix = &body
}
}
} else {
res = append(res, errors.Required("prefix", "body", ""))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindBucket binds and validates parameter Bucket from path.
func (o *DeleteAccessRuleWithBucketParams) bindBucket(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Bucket = raw
return nil
}

Some files were not shown because too many files have changed in this diff Show More