Compare commits

...

545 Commits

Author SHA1 Message Date
Alex
151c8117a3 Release v0.46.0 (#3238) 2024-02-16 13:58:01 -08:00
Alex
9b5c17c2db Updated failing UI tests in console (#3236)
Updated failing tests in console

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-02-14 17:48:41 -06:00
Alex
23e01b257e Updated mds to v0.18.1 (#3233)
- Updated other project dependencies
- Fixed an issue while pressing enter key in a page that contains a select box
- Fixed an issue while selecting an autocomplete option from an input box

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-02-13 17:53:11 -06:00
jinapurapu
ecc8c7a86e Added latest blog content to help menu (#3223) 2024-02-13 00:28:05 -08:00
Harshavardhana
ee4d7b9b69 upgrade all dependencies for console (#3235)
Signed-off-by: Harshavardhana <harsha@minio.io>
2024-02-12 22:17:21 -08:00
Alex
80c03839a4 Fixed lint issues with files (#3234)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-02-12 21:55:17 -08:00
jinapurapu
f394cb69ce Fixed disabled Create User button if policy has no resource (#3231)
Co-authored-by: Jillian Inapurapu <jillii@Jillians-MBP.attlocal.net>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2024-02-12 09:54:32 -08:00
Prakash Senthil Vel
52137ba9e5 Logout state clean up (#3219) 2024-02-09 15:36:56 -06:00
Prakash Senthil Vel
942b8101cc Fix lifecycle rule and edit modal to be consistent to display tier (#3227) 2024-02-07 11:35:25 -06:00
jinapurapu
a0a6b33ecd Add HelpTip to clarify Usage calculation (#3143) 2024-01-30 23:15:11 -06:00
Cesar N
d4c5e1b51c Fail request when error occurs during download (#3214) 2024-01-26 15:06:25 -06:00
jinapurapu
96923aed75 Changed DeleteAccessRule to use swagger api (#3213) 2024-01-26 14:59:34 -06:00
Shubhendu
a04f833e3f Query params replicate-ilm-expiry and disable-ilm-expiry-replication (#3065)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-01-26 14:59:09 -06:00
dependabot[bot]
3b52cc9bd4 Bump github.com/lestrrat-go/jwx from 1.2.27 to 1.2.28 (#3215)
Bumps [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) from 1.2.27 to 1.2.28.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/v1.2.28/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v1.2.27...v1.2.28)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-26 12:01:41 -06:00
Cesar N
df2e92e451 Use swagger api for Site Replication Status (#3208) 2024-01-24 23:05:27 -06:00
Cesar N
8836fe043b Return proper progress on download error (#3211) 2024-01-23 10:55:41 -06:00
Cesar N
7ce6a58099 Use swagger api for Get API from Subnet (#3207) 2024-01-22 10:42:33 -06:00
Haruaki
57d6aca716 Fix BucketNamingRules button click (#3209) 2024-01-22 10:41:35 -06:00
Cesar N
8d99637455 Use swagger api for Add KMS key in Bucket Encryption (#3205) 2024-01-21 22:16:10 -08:00
Cesar N
abd66780f4 Use swagger api for AirGap Subnet Registration (#3206) 2024-01-19 14:17:23 -06:00
Cesar N
a5175a35ec Use swagger api for import KSM Key (#3204) 2024-01-19 11:07:05 -08:00
Cesar N
d9f945b5df Use swagger api for Add KMS Key (#3202) 2024-01-18 22:41:40 -08:00
Cesar N
0c55e39e8c Use swagger api for Add IDP Configuration (#3201) 2024-01-18 16:03:01 -08:00
Cesar N
b5443952da Use swagger api for IDP configuration details (#3200) 2024-01-18 15:02:11 -08:00
Cesar N
b9f0ccfaba Use swagger api for delete single and multiple Service Accounts (#3199) 2024-01-18 11:55:56 -05:00
Daniel Valdivia
24742325b7 Remove useAPI from DeleteUser and DeleteIDPConfigurationModal (#3191) 2024-01-17 15:37:28 -08:00
Cesar N
c87ebe447f Use swagger api for delete KMS key (#3197) 2024-01-17 15:31:19 -06:00
Cesar N
462cf16db9 Use swagger api for delete Policy (#3198) 2024-01-17 12:06:03 -08:00
Cesar N
edaa4e8754 Use swagger api for delete group (#3196) 2024-01-17 11:49:03 -08:00
Cesar N
76c596c574 Disable KMS Endpoints and Metrics Tabs if using KMS Secret Key (#3193) 2024-01-17 09:05:10 -08:00
Alex
b5554f6dcf Added Dark / Light Mode Status icons (#3194)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-01-16 19:06:58 -06:00
Daniel Valdivia
fc65f1afd1 Add Base URI to Inspect API calls (#3190)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2024-01-16 13:45:27 -08:00
Cesar N
b066b6a920 Kms status endpoints null validation (#3189) 2024-01-12 16:32:37 -06:00
Alex
bc0e63aac8 Release v0.45.0 (#3187)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-01-10 21:48:08 -08:00
Alex
343ff575e6 Removed heal backend (#3188)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-01-10 21:16:08 -08:00
Alex
08c922dca6 Deprecated Heal (Drives) functionality (#3186)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-01-10 22:33:18 -06:00
Alex
4dd6519cc6 Updated Section titles & labels (#3185)
Renamed Subscription to Subnet in menus
Renamed Settings to Configuration
Renamed Diagnostics to Heath

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-01-10 22:16:24 -06:00
Prakash Senthil Vel
cdffdae289 Fix life cycle rule edit for transition (#3183) 2024-01-08 08:43:07 -06:00
Shireesh Anjal
27e3b82223 Use subnet package in pkg for license validation (#3156) 2023-12-29 12:12:27 -06:00
Daniel Valdivia
239b31748a Rename portal-ui to web-app (#3178)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-12-29 11:44:01 -06:00
Daniel Valdivia
b465b74326 Release v0.44.0 (#3177)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-12-27 00:11:15 -08:00
Daniel Valdivia
616f262d09 Rename restapi to api (#3176)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-12-26 15:07:30 -06:00
Harshavardhana
8aa0ec17c5 simplify the provider config init, loading and allow reachable IDPs (#3168)
fixes https://github.com/minio/console/issues/3018
2023-12-26 14:38:42 -06:00
Daniel Valdivia
a8c5b53a2c Pkg Subnet Utils Tests (#3173)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-12-26 14:29:11 -06:00
Cesar Celis Hernandez
e96dbd444e Add all possible tests under restapi (#3174) 2023-12-26 14:17:40 -06:00
Prakash Senthil Vel
939e2acb0b fix event dest icons not loading when subpath is configured (#3169)
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-12-26 14:03:25 -06:00
Daniel Valdivia
a04955dc70 Save all coverage files to play (#3170)
* Save all coverage files to play

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

* Save them to latest as well

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

---------

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-12-26 13:52:18 -06:00
Cesar Celis Hernandez
ba04a22492 Adjusting the threshold of the coverage (#3172) 2023-12-26 13:52:03 -06:00
Cesar Celis Hernandez
a281fe129f To include pkg tests in the coverage (#3171)
Execute and include pkg tests in the coverage
2023-12-26 13:34:25 -06:00
Daniel Valdivia
a655cc8d3b Upgrade Go Dependencies (#3167) 2023-12-23 08:26:57 -06:00
Daniel Valdivia
63b584c83d Change Loading Logic for Metrics Page (#3166)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-12-22 10:25:54 -06:00
Alex
7dffd5f079 Release v0.43.1 (#3161) 2023-12-18 17:00:58 -06:00
Cesar N
044e5702df Update Share Object UI to reflect max expiration time (#3098) 2023-12-14 15:28:11 -06:00
Alex
3c3b9546d9 Release v0.43.0 (#3159)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-12-12 21:53:25 -06:00
jinapurapu
5bc0e74b53 Omit detailedMessage from login errors (#3136) 2023-12-12 18:09:56 -06:00
Alex
f0d4dddacd Changed PDF file preview behavior (#3144)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-12-12 14:20:06 -06:00
Prakash Senthil Vel
f4a3f46bcf Fix to prevent extra metadata call when listing at a bucket level (#3158) 2023-12-12 10:56:48 -06:00
Alex
38472e4cd2 Updated Console dependencies (#3157)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-12-11 18:29:07 -06:00
Daniel Valdivia
9db5d1e4f4 Tests for pkg/utils (#3155)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-12-11 09:57:02 -08:00
jinapurapu
4cadaf7d49 Cleaned up Tooltips and HelpTips on AddBucket screen (#3154) 2023-12-11 11:33:28 -06:00
Adrian Najera
748486160f FIX: Use STS env variable to increase the IDP token expiration (#3132)
Share link duration is based on the token expiration,
this increases the IDP token expiration so the share link
is able to last longer, by using an env variable called
MINIO_STS_DURATION
2023-12-08 09:58:46 -08:00
Alex
2c0a0b2bc4 Release v0.42.2 (#3153)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-12-07 15:37:44 -06:00
Daniel Valdivia
7e51d4bebb Don't attempt to show prometheus metrics if URL is empty (#3152)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-12-07 15:16:03 -06:00
Alex
c011e67122 Release v0.42.1 (#3150)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-12-06 19:10:28 -06:00
Alex
394b4c403d Rollback to go 1.19 (#3149)
Rollback go version to go 1.19, This is a requirement of MinIO server

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-12-06 16:55:03 -08:00
Cesar N
74030aa067 Release 0.42.0 (#3148) 2023-12-06 16:04:01 -08:00
Cesar N
07b8c745e6 Remove prints from delete handlers (#3147) 2023-12-06 16:59:10 -06:00
Prakash Senthil Vel
6767bfa2d2 add encoded filename as part of delete api url (#3141) 2023-12-06 14:28:46 -08:00
Prakash Senthil Vel
607d94fef4 add encoded filename as part of upload url (#3140) 2023-12-06 14:14:25 -08:00
Alex
83b060ef94 Updated govuln check to use latest version (#3146)
Updated go vuln check to use latest version

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-12-06 15:13:22 -06:00
dependabot[bot]
cb14cb94ce Bump @adobe/css-tools from 4.3.1 to 4.3.2 in /portal-ui (#3139) 2023-12-06 11:31:55 -06:00
Daniel Valdivia
2b9de49fbe Upgrade to Go 1.21 (#3142)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-12-01 18:23:24 -06:00
Alex
290f273bdf Updated mds version to v0.13.0 (#3138) 2023-11-28 17:11:54 -08:00
Prakash Senthil Vel
7b43779fb0 Fix share option should not be enabled for a prefix (#3135) 2023-11-28 18:45:24 -06:00
Prakash Senthil Vel
924c38faa6 Fix actions on a del marker in object list view (#3134) 2023-11-23 22:32:08 -06:00
Prakash Senthil Vel
e4d5f9610e access keys ui details improvement and edit (#3116) 2023-11-21 22:08:23 -06:00
jinapurapu
04e9cb0ac8 Enable Create Path button on BrowserBreadcrumbs if User can create subPath (#3131) 2023-11-17 13:21:07 -06:00
jinapurapu
da53daff37 Add UI to select number of non-current versions subject to ILM expiry rule (#3088) 2023-11-16 13:34:04 -06:00
Alex
8c26eff2c1 Added matchMedia mock function for Testing purposes (#3130)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-11-15 15:51:55 -06:00
Alex
044c265423 Enabled Dark Mode in Console (#3129)
- Dark mode will be tied to system settings if not set
- Dark mode will be stored in Application storage once set

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-11-15 13:33:05 -06:00
jinapurapu
0053658d5d Added OIDC docs and blog to helpMenu (#3127) 2023-11-15 09:30:39 -06:00
Alex
99cf3b378f Removed MUI dependencies from console (#3126)
- Updated DateTime Selector
- Updated last dependency functions
- Removed mui theming tools

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-11-14 10:22:47 -06:00
jinapurapu
78293eab62 Added new blog and video content to HelpMenu (#3124) 2023-11-13 12:11:55 -06:00
Cesar N
1a84be5782 Ignore server file from swagger-gen make command (#3123) 2023-11-06 14:01:02 -08:00
Alex
178f82b675 Release v0.41.0 (#3121)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-11-02 16:30:50 -07:00
Alex
ec5fbbcd1e Updated Login Error Callback page to mds (#3120)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-11-01 15:21:24 -06:00
Alex
78164054d4 Updated HelpMenu components to use only mds subcomponents (#3119)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-11-01 09:54:14 -06:00
Alex
6d5d11d5b4 Updated common bars (#3118)
- Updated common Loading Bars
- Updated Restart MinIO notification bar

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-11-01 09:53:42 -06:00
nreisingercres
9e3b93d385 Fix days selector to ignore daylight savings (#3117) 2023-10-31 14:30:10 -06:00
jiuker
d77cf93193 update the madmin-go dep (#3115) 2023-10-31 11:03:26 -06:00
Alex
2cca3f3722 Updated Logs Page to use mds components (#3114)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-31 10:12:23 -06:00
Anis Eleuch
36d96a1791 Support resolving of listen addrs to multiple IPv4/IPv6 (#3100)
If you specify --console-address "myaddress:9001" while myaddress can be
resolved to one IPv4 address and another IPv6 address, Console ignores
the IPv6 address. This commit fixes that.

Authored-by: Anis Elleuch <anis@min.io>
2023-10-30 17:47:16 -07:00
Alex
82e34a5df2 Replaced MenuDropdown components with mds variables (#3113)
- Replaced menu dropdown in Files Button
- Input Unit Menu dropdown replacement
- Migrated Download Widget dropdown

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-30 16:46:22 -06:00
Alex
7b83f4b1dc Restored ENV var display support in Console Configuration pages. (#3112)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-30 14:38:33 -06:00
Andreas Auernhammer
7d9910d1ca update docker file to ubi9 base image (#3109)
This commit improves and updates the docker file
to the UBI 9 base image.

Signed-off-by: Andreas Auernhammer <github@aead.dev>
2023-10-30 13:20:43 -06:00
Alex
7a63f6da56 Migrated Object Manager components to mds (#3108) 2023-10-27 06:36:01 -07:00
Alex
0043833f36 Migrated Date Selectors & Chart Tooltips styles (#3107)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-26 22:41:40 -06:00
Alex
3ad3bccadb Autocomplete component replacement (#3104)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-26 11:54:34 -06:00
Alex
39e94c890e Replaced Notification components with mds variants (#3103)
Replaced Notification components for mds variants

- Updated Warning Message components
- Replaced Snackbar component

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-25 11:38:35 -06:00
Alex
701039454a MDS Console improvements (#3102)
- Removed mui badge component
- Updated icons screen
- Migrated Wizard Component
- Improved modals styles
- Updated KMS Status page

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-24 14:09:22 -06:00
Alex
ec77a03d7c Replace missing icons from mui (#3101)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-24 09:28:33 -07:00
Alex
8dbad84a58 Improved error handling in Object Browser (#3097)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-23 10:17:44 -06:00
Cesar N
1767a37162 Add share link exp api (#3095) 2023-10-19 16:03:14 -05:00
Javier Adriel
622c3a067a Add global params for limit and offset (#3096) 2023-10-19 15:38:31 -05:00
Cesar N
4389548b64 Update swagger generated files with latest swagger version (#3094) 2023-10-19 12:49:38 -07:00
Alex
8cb0f1e558 Replaced mui accordion with mds component (#3089)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-17 10:32:23 -05:00
Alex
faafb77c73 Updated mds version to v0.9.6 (#3090)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-16 18:17:05 -05:00
Alex
0ecd1c73c1 Updated styling methods to remove mui and replace it with mds (#3085)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-13 12:02:29 -05:00
Shubhendu
88bf40f9a6 Use the new golang version 1.21.3 (#3087)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2023-10-12 11:50:15 -05:00
dependabot[bot]
a025163b34 Bump golang.org/x/net from 0.15.0 to 0.17.0 (#3086) 2023-10-12 11:50:00 -05:00
Alex
07c80462b7 mui Grid component replacement (#3084)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-11 13:59:04 -07:00
Alex
79ac2277d4 Migrated mui Box to use mds component (#3082)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-11 13:58:33 -07:00
Alex
1c27bee9d0 Removed FormSwitchWrapper component (#3081)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-10 12:29:24 -05:00
Alex
56dc58b0b8 Components cleanup (#3079)
Removed old component wrappers and replaced them with their mds equivalent components:

- InputBoxWrapper
- CommentBoxWrapper
- CheckboxWrapper
- PredefinedList

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-09 14:41:23 -05:00
Adrian Najera
4018addd79 Fix: cache clientIP in GetConsoleHTTPClient (#3056) 2023-10-09 14:40:54 -05:00
jinapurapu
429dfb4314 Add HelpTips to Console (#3054) 2023-10-06 15:38:55 -05:00
Alex
1d7bb0bb2b Removed mui support from PasswordSelector component (#3078)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-06 14:09:44 -05:00
Alex
476eb673bb Updated FileSelector and removed old component (#3076)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-06 09:12:13 -07:00
Alex
71681b710c Removed deprecated components and replaced them with mds ones (#3077)
- SectionTitle
- AButton
- ScreenTitle

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-06 09:11:48 -07:00
Alex
fb02a7da06 Updated README file (#3074)
Updated README file

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-05 12:31:06 -05:00
MinIO Bot
5fdc341138 mds-released-v0.9.3 (#3073) 2023-10-04 19:17:16 -05:00
Alex
15de6caf75 Fixed Failing integration test (#3071)
Added a wait time after bucket creation to list the new buckets list
2023-10-04 09:09:55 -07:00
Alex
77bc2d5006 Updated Playwright tests (#3070)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-03 20:09:33 -05:00
Alex
972f5fca9d Release v0.40.0 (#3069) 2023-10-03 12:17:05 -07:00
Alex
083314ee2d Object Browser Refactor (#3066)
- Refactored navigation to be handled only with URL
- Refactored & simplified websocket
- Updated components to use mds
- Fixed an issue with Anonymous access and file selection
- Fixed an issue with anonymous access and download selection click
- Fixed an issue with object details selection on root path from a bucket
- Simplified reducer

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-02 18:02:03 -05:00
Alex
078ce0e546 Updated console dependencies (#3068)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-10-02 15:52:34 -05:00
MinIO Bot
9beca2c226 mds-released-v0.9.2 (#3060) 2023-09-25 11:49:51 -06:00
Prakash Senthil Vel
451f23ae24 Improve change password user experience (#3058) 2023-09-22 13:51:03 -06:00
Alex
300ebfa19f Updated OpenID UX (#3050)
- Display ENV variables set in configuration
- Removed Password empty placeholders
- Added notification to re-enter password when modifying OpenID configuration

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-22 13:50:41 -06:00
Alex
1ce2846c95 AuditLogs page fixes (#3059)
- Added support to endDate
- Converted dates to UTC as required by AuditLogs API

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-22 09:49:36 -06:00
Alex
7fb8c11a9d Added support for Scanner event type (#3055)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-21 13:14:54 -06:00
Prakash Senthil Vel
e7993c2d1b Fix: handle crash in error handling of access keys page (#3051) 2023-09-20 20:45:51 -06:00
Alex
3e93f7ff88 Release v0.39.0 (#3049)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-14 23:43:58 -07:00
Alex
f0580956db Improvements to LDAP Configuration page (#3047)
- Fixed LDAP Enabled / Disabled calculation
- Removed Password Label from main page to avoid confusion
- Display LDAP ENV variables in case of configured

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-14 17:54:59 -06:00
Pedro Juarez
65b0bab26d Load the available CA in logout API (#3044) 2023-09-13 15:26:48 -06:00
Harshavardhana
a559421293 migrate and upgrade all dependencies (#3046) 2023-09-13 13:55:47 -06:00
Cesar N
6ef35c26a4 Remove unused check-version api (#3045) 2023-09-13 13:55:06 -06:00
Alex
8a918324aa Added CONSOLE_PROMETHEUS_AUTH_TOKEN flag support (#3043)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-13 12:02:13 -06:00
Alex
d30c0c8cbb Migrated Metrics pages and components to mds (#3042)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-13 12:01:43 -06:00
Alex
1697c826c0 Fixed an issue while deleting objects with similar prefixes (#3035)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-12 10:05:41 -06:00
Cesar N
b378b8c8ef Fix regression on login path when using redirect url (#3038) 2023-09-11 14:25:34 -07:00
MinIO Bot
2c42d7ff81 mds-released-v0.9.1 (#3041) 2023-09-11 09:58:23 -06:00
Cesar N
e1f36ee54a Upgrade go version in git workflows to 1.21.1 (#3039) 2023-09-07 17:29:22 -06:00
Alex
698f72f828 Migrated Register page components to mds (#3034)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-07 11:48:29 -06:00
jinapurapu
8dd94f5336 Moved EditBucketReplication to screen (#3037) 2023-09-06 17:24:07 -06:00
Alex
1dc21b9a21 Removed TableWrapper (#3032)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-05 11:06:33 -06:00
Alex
248215cc77 Updated Changelog for v0.38.0 (#3031)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-04 11:44:50 -07:00
Alex
a6eee73c11 Release v0.38.0 (#3029)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-02 09:10:10 -07:00
Anis Eleuch
50d6a39312 idp: Use 900 seconds as minimum expiry without returning an error (#3022)
Do not bother the user with an error if the IDP expiry is less than
900 seconds, since the S3 spec sets a minimum of 900 seconds for STS
expiration, use that minimum duration instead of returning an error

Co-authored-by: Anis Elleuch <anis@min.io>
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2023-09-02 07:50:45 -07:00
Aditya Manthramurthy
467b6b9bcb Use github.com/minio/pkg/v2 (#3027) 2023-09-01 17:29:07 -06:00
Alex
6e6246797c Renamed Subnet strings to SUBNET (#3026)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-01 15:10:12 -07:00
Alex
f8e4f747f5 Migrated Components loader to mds (#3025)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-09-01 12:06:33 -06:00
Alex
9acd49fcc6 Updated console dependencies (#3024)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-31 09:27:48 -06:00
Alex
636d12d43f Replaced LinearProgress as loader in favor of ProgressBar from mds (#3020)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-31 09:27:29 -06:00
MinIO Bot
5e9383de95 mds-released-v0.9.0 (#3021) 2023-08-30 12:38:11 -06:00
Kaan Kabalak
4863af863e Update minimum Standard plan capacity on License page (#3023) 2023-08-30 11:28:42 -06:00
Martin
b6d4c62edd Fix oauth2Config.Exchange error reporting (#3003) 2023-08-28 15:35:53 -07:00
Alex
bf733f3822 Migrated Heal and Watch pages to mds (#3016)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-25 09:53:02 -06:00
Alex
bbf4027418 Migrated Speedtest components to mds (#3015)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-24 17:08:16 -06:00
Alex
dbffc5fc22 Migrated Site Replication Pages (#3011)
- Improved & Simplified UI elements

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-24 17:07:58 -06:00
Cesar Celis Hernandez
f4a9420002 To properly show error in AddAccessRule.tsx (#3008)
Fixing the shown errors
2023-08-24 06:53:32 -06:00
Alex
657854bd29 Updated generated stylesheet (#3013) 2023-08-23 15:31:22 -06:00
MinIO Bot
cdc9e7d921 mds-released-v0.8.6 (#3012) 2023-08-23 15:21:36 -06:00
Alex
b125121ac8 Migrated available mds components in Tiers Page (#3007)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-21 22:30:04 -06:00
Daniel Valdivia
0c480dd5ec Handle errors for Set Policy for Access keys (#3005)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-08-21 12:00:45 -06:00
MinIO Bot
c07b8dcf73 mds-released-v0.8.4 (#3002)
Co-authored-by: MinIO Bot <minio.bot@minio.io>
2023-08-21 09:25:24 -06:00
Daniel Valdivia
912a4b216f Rename Error to ApiError to avoid ambiguity (#2954)
Rename Error to ApiError to avoide ambiguity and remove redundant status code

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-08-16 14:18:08 -07:00
Prakash Senthil Vel
e7fb205c31 show service account status and expiry in ui (#2992) 2023-08-16 11:55:07 -06:00
Alex
a2ba20e12f Release v0.37.0 (#3000) 2023-08-16 10:03:02 -07:00
Alex
f515dd82fe Migrated Trace page to mds (#2996)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-16 10:23:01 -06:00
Alex
61d3193c41 Migrated available mds components in Logs Page (#2998)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-16 09:42:15 -06:00
Harshavardhana
430ae66955 update disk -> drive change in prometheus metrics in minio master (#2999)
refer to https://github.com/minio/minio/pull/17854
2023-08-15 19:49:53 -07:00
MinIO Bot
3abbbc82b2 mds-released-v0.8.3 (#2995) 2023-08-15 10:09:50 -06:00
Alex
854181f63e Disabled input fields and changed Register label in Subscription features (#2994)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-14 17:07:31 -06:00
Kaan Kabalak
df996794ed Remove duplicate ref=con in License page buttons (#2993) 2023-08-14 10:12:10 -06:00
Cesar N
2d94018e3c Release v0.36.0 (#2988) 2023-08-10 11:59:15 -07:00
Alex
72bb9d0ca1 Update Settings Page components (#2986)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-09 18:30:42 -06:00
Javier Adriel
93bd0d65e2 Show LDAP configuration (#2985) 2023-08-09 12:06:17 -06:00
Prakash Senthil Vel
495b0f0068 truncate prefixes above the selected level to make the download layout flat (#2987) 2023-08-09 10:29:41 -07:00
Cesar Celis Hernandez
3275b6a6d8 Removing unneeded password (#2983) 2023-08-04 13:45:41 -06:00
Harshavardhana
fed5aa1599 fix: dateTime usage only available in go1.20 (#2982)
instead use a simpler normalizer for timestamp
to be user friendly without spaces.

For example

```
selected_files_20091110T230000Z.zip
```
2023-08-03 20:21:44 -07:00
Alex
6bc4efbac1 Release v0.35.0 (#2981) 2023-08-03 14:54:29 -07:00
Alex
cc0164a67b Initial Tools Pages Migrations (#2978)
Tools Pages Migrations

- Call Home
- Inspect
- Profile
- Health

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-03 15:26:17 -06:00
Prakash Senthil Vel
b968cc25ad feat: download multiple object selection as zip ignoring any deleted objects selected (#2965) 2023-08-02 18:28:25 -06:00
jinapurapu
d116a35a6d Fix subpath button (#2977)
Added bucketName to permissions check for subpath
2023-08-02 15:26:39 -07:00
Alex
49f856bdd5 Added Exclude Folders & Exclude Prefixes support (#2973)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-02 13:35:00 -06:00
Cesar Celis Hernandez
0d628f589a Removing white spaces from provided values (#2922) 2023-07-31 22:29:11 -06:00
Cesar N
4387b2149f Update Github jobs to use hardcoded testcafe version (#2972) 2023-07-31 16:11:28 -07:00
Alex
8cc602434e Release v0.34.0 (#2969)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-07-28 16:31:50 -07:00
Anis Eleuch
6411dc9504 auth: Avoid forcing one hour expiration for IDP sts creds (#2966) 2023-07-27 22:25:47 -06:00
Alex
32c34b0a11 Migrated Buckets Pages to mds (#2960) 2023-07-27 19:27:43 -07:00
jinapurapu
6e8f5e0fc2 Fixed subnet Health report upload (#2927)
Fixed subnet file upload to allow upload of different report types
2023-07-27 15:29:30 -07:00
Alex
3ce377dbd1 Changed Download Handler Behavior (#2964)
- Updated Handler for files with size of 49GB or less
- Used Browser Handler tor size of more than 50GB

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-07-26 21:08:35 -07:00
Alex
ad502b9f18 Fixed issue with folders named as the bucket in rewind mode (#2963)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-07-26 13:53:04 -07:00
Alex
fde186a5a3 Release v0.33.0 (#2956)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-07-20 22:26:32 -07:00
Prakash Senthil Vel
0823f623c8 fix configuration page to include configs based on supported config subsystems (#2946) 2023-07-20 14:43:17 -06:00
Prakash Senthil Vel
6cec113304 fix: add new dashboard metrics (#2952) 2023-07-20 07:57:41 -07:00
dependabot[bot]
cd42d77a46 Bump word-wrap from 1.2.3 to 1.2.4 in /portal-ui (#2953) 2023-07-19 11:40:03 -06:00
Alex
35907beaca Updated Prettier to v3.0.0 (#2949)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-07-18 09:58:21 -06:00
MinIO Bot
a062a59288 mds-released-v0.7.0 (#2948) 2023-07-18 09:53:53 +05:30
Prakash Senthil Vel
842c2decd0 fix console dashboard node io prometheus metric WChar and RChar (#2947) 2023-07-17 10:51:59 -07:00
Prakash Senthil Vel
d1069ed359 fix loading of objects at a path when url is shared opened elsewhere (#2944)
- fix loading of objects at a path when url is shared and opened elsewhere
- fix bug when a path is created and objects are uploaded it is not refreshed
2023-07-17 09:03:31 -07:00
Prakash Senthil Vel
6d81a1b1f8 add support for preview based on content type (#2930) 2023-07-13 18:29:56 -06:00
Alex
b2fe478dae Updated vulnerabilities checks & workflows (#2941) 2023-07-12 22:05:54 -06:00
Prakash Senthil Vel
02ed6a6e8b Fix delete share download options permission check in sidebar (#2935) 2023-07-12 11:07:18 -06:00
jinapurapu
1b271ab467 Removed extra 'Event Destination' text (#2938) 2023-07-12 10:35:39 -06:00
Prakash Senthil Vel
bacb5f8901 Preserve white space in breadcrumb disply and navigation (#2932) 2023-07-11 22:32:03 -06:00
MinIO Bot
ae7371da95 mds-released-v0.6.9 (#2934) 2023-07-10 22:56:44 -06:00
Prakash Senthil Vel
60f5eb603b remove warning message in delete bucket replication rule (#2931) 2023-07-10 14:29:23 -06:00
Prakash Senthil Vel
420ed00f55 Fix object metadata section crash due to un escaped characters (#2928) 2023-07-07 12:21:06 -06:00
Alex
b975871e9d Migrated remaining LDAP subcomponents to mds (#2926)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-07-06 22:21:19 -06:00
Alex
803ffe2960 Migrated OpenID module components to mds (#2925)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-07-06 14:14:10 -06:00
Cesar N
c96c95924c Release v0.32.0 (#2924) 2023-07-06 10:32:39 -07:00
Alex
f02786001c Migrated Groups Module components to mds (#2923)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-07-05 22:29:17 -06:00
Alex
542b7192c3 Migrated Users Module components to mds (#2920)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-07-05 17:21:07 -07:00
Javier Adriel
b31aa10b52 Refactor to swagger TS API (#2903) 2023-07-05 17:27:46 -06:00
jiuker
1eba59954d fix: websockets goroutine leak for object list UI (#2805)
listen on the done channel to ensure that we can clean up 
the WebSocket go-routine, once the caller is done with
writing the response.
2023-07-05 12:17:54 -07:00
jinapurapu
b71d9f05c5 Updated placeholderimage for offline HelpMenu items (#2916) 2023-06-30 18:35:04 -06:00
Daniel Valdivia
fc5e94d55f Remove Cross compile jobs (#2915) 2023-06-29 18:57:14 -06:00
Alex
3875fb3eaa Release v0.31.0 (#2912)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2023-06-29 12:21:34 -07:00
Aditya Manthramurthy
b3d4132fe6 Remove goreleaser (#2914)
Not needed any more as standalone console deploys are not supported any more.
2023-06-29 11:15:58 -06:00
Alex
21f20bb9ea Migrated Policies module to mds (#2911)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-06-28 18:23:58 -06:00
Alex
df937467a0 Fixed Bucket Events Notifications Page (#2906)
- Added missing ilm & replica supported types
- Fixed add screen crash when no arn is selected
- Migrated components to mds

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-06-28 15:39:02 -06:00
Alex
d1ae271111 Replaced CodeMirrorWrapper internal components (#2910)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-06-28 12:05:18 -07:00
Alex
e7fb3e0e45 Updated console dependencies (#2909)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-06-26 14:12:09 -06:00
dependabot[bot]
b7b0271ec7 Bump fast-xml-parser from 4.2.4 to 4.2.5 in /portal-ui (#2904)
Signed-off-by: dependabot[bot] <support@github.com>
2023-06-22 21:41:22 -06:00
jinapurapu
9935b839b7 Help menu cleanup (#2905) 2023-06-22 20:25:06 -06:00
Cesar N
0e11098c31 Upgrade semver version to address vulnerability (#2902) 2023-06-22 17:54:14 -04:00
Daniel Valdivia
c55e0a069b Format number of objects (#2901) 2023-06-22 12:59:16 -07:00
jinapurapu
c3da876b04 Added placeholder image to HelpMenu if links unavailable (#2899) 2023-06-22 12:12:13 -07:00
Alex
643a9c6c7c Updated Audit Logs page to use mds (#2897)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-06-21 14:32:19 -06:00
Aditya Manthramurthy
0c3a94172d Bump up madmin-go to v3.0.2 (#2896) 2023-06-21 10:51:12 -07:00
Javier Adriel
527eb16700 Refactor to swagger TS API (#2888)
Co-authored-by: Prakash Senthil Vel <23444145+prakashsvmx@users.noreply.github.com>
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2023-06-20 12:46:20 -07:00
Alex
e3aec3f094 Updated mds to v0.6.4 (#2895) 2023-06-20 11:49:58 -07:00
Alex
eac2734df4 Update mds to v0.6.2 (#2894) 2023-06-20 11:37:09 -06:00
Allan Roger Reid
a411e7c977 Create bug_report.md (#2893) 2023-06-19 19:58:26 -07:00
Prakash Senthil Vel
28c4abe2d0 audit only the dependencies and exclude dev dependencies in ci for ui (#2892) 2023-06-19 10:55:20 -06:00
Javier Adriel
a9ef6ebf5f Fix download of folders (#2891) 2023-06-19 10:54:18 -06:00
Ali Garajian
c12415c12d Virtualize download/upload object list (#2889) 2023-06-16 13:31:39 -06:00
Cesar N
1f481e690b Add CHANGELOG file for Releases (#2887) 2023-06-15 14:21:27 -07:00
Alex
58aad859e1 Release v0.30.0 (#2886) 2023-06-15 13:13:53 -07:00
jinapurapu
b9ebfe09ee Removed console.log (#2885) 2023-06-15 12:52:24 -06:00
Prakash Senthil Vel
864cf7af99 Apply policy resource restrictions for file extensions (#2842) 2023-06-15 11:33:52 -07:00
Javier Adriel
b76f460979 Remove dead code and add job in CI to find it (#2882) 2023-06-15 10:24:24 -06:00
Cesar N
cbd2c4682d Raise error on upload artifact if no files (#2884)
Raise error on artifact upload if no files to upload

Co-authored-by: cesnietor <>
2023-06-15 09:00:51 -07:00
Javier Adriel
d49bdf7d49 Add staticcheck to console API (#2883) 2023-06-14 21:35:00 -06:00
dependabot[bot]
559a7278a0 Bump github.com/lestrrat-go/jwx from 1.2.25 to 1.2.26 (#2879) 2023-06-14 17:16:17 -06:00
Daniel Valdivia
4a172fae97 Pass Client IP address to MinIO on x-forwarded-for header (#2864) 2023-06-14 12:36:48 -07:00
Cesar N
fc4263e2f9 Define action version to Git action (#2875) 2023-06-13 18:16:46 -06:00
Cesar N
c1b9b4c81b Add Git job to add new issues to project (#2873) 2023-06-13 17:36:21 -06:00
Javier Adriel
aa9b73522e Refactor to swagger ts API (#2860) 2023-06-13 15:00:56 -07:00
Alex
0904f83627 Fix playwright (#2867)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-06-13 14:43:28 -07:00
Cesar N
253053cc23 Refactor session to avoid duplicate calls to apis (#2868)
Co-authored-by: cesnietor <>
2023-06-13 14:27:48 -07:00
Alex
08a3ff65c7 Updated menu component to use mds (#2866)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-06-13 15:16:46 -06:00
jinapurapu
ee8fac8be8 Removed debugging line (#2870)
Co-authored-by: Jillian Inapurapu <jillii@Jillians-MacBook-Pro.local>
2023-06-13 11:48:25 -07:00
jinapurapu
9fa49b40b3 Console help menu (#2804)
Authored-by: Jillian Inapurapu <jillii@Jillians-MBP.attlocal.net>
2023-06-12 14:50:25 -07:00
Javier Adriel
de13119e02 Use swagger autogenerated ts API for session endpoint (#2859) 2023-06-12 15:00:40 -06:00
Alex
bf9acd7691 Share File access key / secret key rollback (#2863)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-06-12 14:02:38 -06:00
Alex
a5066fecc8 Update mds to v0.6.0 (#2862)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-06-12 11:55:23 -07:00
jinapurapu
6432681440 Minor tooltip fixes (#2861) 2023-06-12 09:54:07 -06:00
jinapurapu
b3b6df9d82 Minor fixes to Config screen tooltips (#2857) 2023-06-08 23:28:28 -06:00
Alex
fe7be4ef62 Fixed an issue with subpaths in resources policies (#2856)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-06-08 17:37:45 -06:00
Javier Adriel
b4603547f6 Add tests for remote buckets handlers (#2843) 2023-06-08 13:06:49 -06:00
Javier Adriel
b33b9315ea Fix fast-xml-parser vulnerability (#2855) 2023-06-06 11:55:23 -07:00
BlockListed
6ae03fa028 Fix spelling in file sharing menu (#2853) 2023-06-04 12:23:25 -06:00
MinIO Bot
e1bb1e0472 mds-released-v0.5.1 (#2851) 2023-06-02 20:45:08 -06:00
Alex
411670e4f5 Release v0.29.0 (#2850)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2023-06-02 13:51:48 -07:00
Alex
1c55932f84 Updated console dependencies (#2849) 2023-06-02 12:58:07 -06:00
Kaan Kabalak
5a77054d6b Update Service Acct fns based on new ListServiceAccounts type (#2824)
* Update Service Acct fns based on new ListServiceAccounts type
2023-05-30 10:30:02 -07:00
Alex
84c5fd58f9 Display error snack bar on raw policy edit error (#2841)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-05-29 18:05:42 -06:00
Daniel Valdivia
98979911ee Move Login to Typescript Swagger API (#2839)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-05-29 13:52:29 -07:00
Alex
c0cf7358c7 Removed hardcoded background in access keys table (#2837)
## What does this do?

Removed non required classes from access keys table

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-05-28 18:47:10 -07:00
Alex
9053e64dff Fixed multiselection of items after deleting one service account (#2836) 2023-05-26 16:58:01 -06:00
Alex
57bfe97d08 Release v0.28.0 (#2831) 2023-05-26 13:42:42 -07:00
Alex
028570279c Migrated Access Keys page components to mds (#2834)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-05-26 11:24:34 -06:00
MinIO Bot
bda1cd1f25 mds-released-V0.4.3 (#2830)
Co-authored-by: MinIO Bot <minio.bot@minio.io>
2023-05-24 11:37:48 -07:00
Alex
7a9b775b09 Changed Share Object logic to use Access Keys (#2827)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-05-24 10:52:40 -07:00
Daniel Valdivia
17e791afb9 Replace RIGHT-TO-LEFT OVERRIDE unicode (#2828)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-05-23 09:47:12 -06:00
Daniel Valdivia
920fc7d937 Fix Subpath behavior (#2818)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-05-22 12:30:59 -07:00
GeekTR
6e314a2fa5 Fix crash when backend has no rrSCParity property (#2826) 2023-05-22 13:14:41 -06:00
Alex
dc90db6591 Changed SSO Login screen to hide login form by default (#2807)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-05-22 11:20:45 -06:00
Prakash Senthil Vel
beed4895c1 Apply permission check for create accesskey button (#2822) 2023-05-22 09:26:31 -07:00
Prakash Senthil Vel
629dd669c4 Fix anonymous access rule not displayed due to style (#2820) 2023-05-18 10:54:21 -06:00
Alex
fc9319e55b Added identifier field to Event destinations page & migrated to mds (#2816) 2023-05-16 20:21:31 -06:00
Alex
58b64a5739 Fixed an issue with allowResources & KeyBar (#2817) 2023-05-16 16:53:32 -06:00
Javier Adriel
d93537261e Fix download of large files in Console (#2773) 2023-05-15 11:31:43 -07:00
Prakash Senthil Vel
22ec87d00e improve playwright tests with refactoring and clean up (#2809) 2023-05-15 10:02:40 -06:00
MinIO Bot
b87b4156e7 mds-released-v0.4.2 (#2815)
Co-authored-by: MinIO Bot <minio.bot@minio.io>
2023-05-12 18:55:23 -06:00
Alex
93f010b880 Release v0.27.0 (#2813)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2023-05-10 21:33:38 -07:00
Prakash Senthil Vel
c5d4cdf1bc fix to show or hide show deleted objects option based on versioning status (#2780) 2023-05-10 11:36:49 -06:00
Anis Eleuch
c117601e53 Update madmin-go to 2.1.1 (#2810)
Update madmin-go library + github.com/minio/mc

Also update the code to work with the library update

Co-authored-by: Anis Elleuch <anis@min.io>
2023-05-09 10:49:51 -07:00
MinIO Bot
f78f838ed9 mds-released-v0.4.1 (#2808)
Co-authored-by: MinIO Bot <minio.bot@minio.io>
2023-05-09 10:03:49 -06:00
Alex
1583b69fb7 Made Service Account creation consistent with mc (#2801) 2023-05-03 12:33:21 -06:00
Alex
cde6d1b0e0 Added Disable login animation with env variable (#2799)
Set CONSOLE_ANIMATED_LOGIN=off env variable before running Console binary

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-05-03 11:36:41 -06:00
Alex
be60569a14 Changed Object Browser components to use new mds components (#2796) 2023-05-02 17:29:21 -07:00
Alex
51226a74d0 Release v0.26.4 (#2798)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-05-02 18:01:09 -06:00
Daniel Valdivia
e983473a54 Upgrade Go Dependencies (#2786)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-05-02 14:35:22 -06:00
Kaan Kabalak
211ab3fd9d Fix incorrect logo appearing for Standard License holders (#2797) 2023-05-01 14:50:01 -06:00
Javier Adriel
90c8ea7f09 Use PageLayout from mds (#2789) 2023-04-29 21:16:09 -06:00
Alex
fb5193d896 Updated mds to v0.4.0 (#2794) 2023-04-27 19:50:21 -06:00
Alex
f7a7f01d7d Fixed issue while getting locking status of a bucket (#2790)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-26 09:59:58 -06:00
Javier Adriel
2c84a52937 Use FormLayout from mds (#2788) 2023-04-25 14:22:49 -06:00
Cesar Celis Hernandez
6020590b2f Make playwright run faster (#2737) 2023-04-25 09:19:00 -07:00
Alex
1477def4fe Updated Console UI dependencies (#2787)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-24 23:19:22 -07:00
Javier Adriel
8882f1da0e Fix yaml vulnerability (#2785)
Bump yaml package version to 2.2.2 to address unhandled exception [CVE-2023-2251](https://nvd.nist.gov/vuln/detail/CVE-2023-2251)

More details on https://github.com/advisories/GHSA-f9xv-q969-pqx4
2023-04-24 16:40:17 -07:00
Cesar N
056d487f1c Show progress bar when loading Usage Info (#2784) 2023-04-19 22:06:16 -06:00
Cesar N
b8083215b3 Update Dev Docs with MinIO naming conventions (#2783) 2023-04-19 11:12:21 -07:00
Cesar N
61c864e748 Update Dev Documentation (#2781) 2023-04-19 10:47:15 -06:00
Javier Adriel
0e0f5030da Remove health diagnostic warning (#2779) 2023-04-17 17:00:03 -06:00
Alex
0dacc4d49e Changed breadcrumbs back button behavior (#2776)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-12 17:23:04 -06:00
Alex
4d783c5e42 Added Object Version read-only field in Edit Lifecycle Modal (#2772) 2023-04-11 17:33:16 -06:00
Alex
75b3a6bea4 Fixed Object Version selector visibility in Add Lifecycle Rule modal (#2769)
- Fixed Object Version selector visibility in Add Lifecycle Rule modal
- Changed definition in swagger file to match camelCase standard
- Added a playwright test case to avoid this issue in the future

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-11 11:01:03 +02:00
Alex
29507cda7e Updated xml2js library (#2770)
## What does this do?

Updated xml2js library for tests in console. Will fix this in minio-js
as well in an upcoming PR

## How does it look?
<img width="391" alt="Screenshot 2023-04-10 at 21 58 04"
src="https://user-images.githubusercontent.com/33497058/231052457-dac4b376-d1b8-4fd2-80e2-10892f8a453d.png">

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-11 10:36:03 +02:00
Alex
81e0c82fee Changed GitHub labels (#2768)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-10 14:05:08 -06:00
Alex
7519fad6dd Release v0.26.3 (#2764)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-06 09:45:15 -07:00
Prakash Senthil Vel
248a59ee8c compliance license faq updated (#2762) 2023-04-06 10:11:34 -06:00
Alex
cefb6d3c95 Release v0.26.2 (#2761)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-05 14:12:30 -07:00
Alex
543076eaac Release v0.26.1 (#2759)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-05 13:11:39 -07:00
Alex
cbf1ddeb4c Added support for root credentials login with LDAP enabled (#2758)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-04 15:38:32 -06:00
Daniel Valdivia
3746adcc13 Update Login Message (#2757) 2023-04-04 12:24:10 +02:00
Alex
854b984850 Display explicit errors if websocket connection cannot be stablished (#2756)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-03 18:10:59 -06:00
Alex
62fa0e2043 Updated Entities results panel styles (#2753)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-31 22:09:37 -06:00
Javier Adriel
4f5b1b0aa7 Show modal to restart minio after subnet register (#2752) 2023-03-31 17:31:54 -06:00
MinIO Bot
0e362c2106 mds-released-v0.3.3 (#2750)
Co-authored-by: MinIO Bot <minio.bot@minio.io>
2023-03-30 14:11:56 +09:00
Alex
6ac5be32b2 Release v0.26.0 (#2746)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-27 17:12:21 -07:00
Harshavardhana
6966183bc8 Update deps and fix build issues (#2745) 2023-03-27 15:30:52 -06:00
Alex
60d70e3668 Added anonymous access modal in folder selection (#2736)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-23 12:05:16 -06:00
jinapurapu
5e65f2aced Fixed Drives offline card UI (#2732) 2023-03-23 09:53:21 -06:00
jinapurapu
1e345364cf Fixed refresh button on ListObjects screen (#2728) 2023-03-22 15:39:44 -06:00
jinapurapu
502a6c462f Fixed snackMessage to display if error returned creating policy (#2726) 2023-03-22 15:39:08 -06:00
Javier Adriel
ceafdb9cb4 Fix revive lint issues (#2730) 2023-03-22 15:06:04 -06:00
Alex
a6d8f6beaa Added Audit Kafka configuration panel (#2724) 2023-03-21 10:07:56 -06:00
Alex
fa1f84bd0a Release v0.25.1 (#2723)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-17 17:34:30 -07:00
Alex
72a1e5eefb Changed response type for Add bucket (#2721)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-17 17:36:42 -06:00
Cesar Celis Hernandez
f20fa0b1c8 Condensing Successful responses (#2722) 2023-03-17 17:12:59 -06:00
Alex
07b7af59b6 Minor Adjustments (#2719)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-17 11:28:12 -06:00
jinapurapu
c9ac525358 Tenant health info upload (#2606)
For registered clusters, after generating the Health Info report, Health Info is uploaded to Subnet, and latest metrics are visible in Subnet.
Authored-by: Jillian Inapurapu <jillii@Jillians-MBP.attlocal.net>
2023-03-17 09:42:01 -07:00
Daniel Valdivia
8b1b2b1e2d Introducer swagger typescript api (#2704)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-03-17 09:57:45 -06:00
Alex
ad0591ec17 Updated yarn packages (#2718)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-17 08:30:58 -07:00
Alex
59c47d98dd Reactivated Enable / Disable LDAP functionality (#2714)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-16 15:18:47 -07:00
Alex
cb38a545ce Fixed navigation after bucket creation (#2713)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-16 14:11:03 -06:00
Alex
dd356b6ea9 LDAP Page improvements (#2708)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-16 11:48:59 -06:00
Alex
c1d39a910f Display Menu Headers (#2712)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-16 09:42:58 -06:00
Alex
eb3913ba48 Fixed issues in Embeddable Object Browser (#2711)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-16 09:42:35 -06:00
dependabot[bot]
f7efbc66f7 Bump webpack from 5.75.0 to 5.76.1 in /portal-ui (#2707) 2023-03-14 12:58:33 -07:00
Alex
f368d9936a Adjusted dropdown box styles in login page (#2706)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-14 11:24:46 -06:00
Cesar Celis Hernandez
5cb4e6f651 Adopting Playwright (#2702) 2023-03-14 09:26:06 -06:00
Daniel Valdivia
f7f7b087c4 Hide Header and Padding on iframed Metrics (#2703)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-03-13 22:42:19 -06:00
Alex
4cceee8936 Added LDAP Entities API (#2700) 2023-03-10 09:16:29 -06:00
Prakash Senthil Vel
5262c02a28 improve airgap license update user experience (#2697) 2023-03-08 23:25:49 -06:00
Daniel Valdivia
fd81529ddc Upgrade Go Dependencies (#2699)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-03-08 23:24:46 -06:00
Daniel Valdivia
383341ad61 Update UI Dependencies (#2698)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-03-08 18:21:05 -06:00
Daniel Valdivia
66d0182825 Release v0.25.0 (#2696)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-03-08 09:17:03 -08:00
Alex
1fc9a40273 Changed IDP login support in console (#2695)
- Allowed to use External IDP + Built-in IDP at the same time
- Added IDP type to IDP listing
- Added IDP name when no display name is configured
- Changed STS login link into new menu
- Cleanup of Operator login strategies

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-07 14:10:21 -08:00
Daniel Valdivia
1953a98968 Ref Con for Login Page Links (#2694) 2023-03-06 16:12:25 -08:00
Cesar Celis Hernandez
3b5979d783 Compiling when cache is missing (#2688) 2023-03-03 09:11:00 -08:00
Harshavardhana
9aa1f43df7 move to UBI micro image (#2684)
Co-authored-by: Prakash Senthil Vel <23444145+prakashsvmx@users.noreply.github.com>
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2023-03-02 20:42:28 -08:00
Daniel Valdivia
bf89f09238 Removing Operator UI (#2692)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-03-02 21:35:08 -06:00
Prakash Senthil Vel
b8e14ee269 improve versioning status display and delete with versions when versioning is suspended (#2689) 2023-03-02 12:41:44 -06:00
Prakash Senthil Vel
c700ee491e fix luxon time parsing format to fix the invalid time in the logs screen (#2691) 2023-03-02 09:42:54 -08:00
Alex
65575751ff Release v0.24.0 (#2679)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-28 12:53:47 -08:00
Alex
cc28f88325 Update Operator version and fixed issue in operator console (#2686) 2023-02-27 23:53:41 -08:00
Harshavardhana
dd913decc6 Use global HTTP client whenever applicable (#2682) 2023-02-27 19:19:56 -06:00
jinapurapu
372852ee86 Fixed tooltip on Add Access Rule modal (#2683) 2023-02-27 09:42:04 -06:00
Alex
154974c24e Disabled Preview button for text files (#2680)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-23 15:25:04 -06:00
Daniel Valdivia
844041fc32 Upgrade Go Dependencies (#2676)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-02-23 09:55:40 -06:00
Alex
c23aff45c8 Hidden red notification in License menu when cluster is registered (#2678)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-21 19:12:03 -06:00
Daniel Valdivia
1b4eacb587 Remove Chartsjs and react-chartjs-2 (#2677)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-02-21 17:33:04 -06:00
Prakash Senthil Vel
f21434a971 Export import server config from settings page (#2664) 2023-02-21 11:50:04 -08:00
Alex
6c0e7baa87 Updated mds to v0.2.5 (#2675)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-21 09:41:09 -08:00
Alex
09fe3af6b7 Removed confirmation box in enable call home screen (#2674)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-21 10:28:09 -06:00
dependabot[bot]
50a6c61d0d Bump golang.org/x/net from 0.5.0 to 0.7.0 (#2672) 2023-02-20 19:38:35 -08:00
Daniel Valdivia
53b5fba532 Wordng updates on Call-Home (#2673)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-02-18 23:43:16 -08:00
Javier Adriel
a73b710313 Change updates service url (#2658) 2023-02-14 12:56:45 -06:00
Alex
404b1ef92e Restored ObjectManagerButton (#2665)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-14 11:36:49 -06:00
Alex
c3b7af9c89 Close Delete Object modal on Access Denied error. (#2661)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-14 09:56:09 -06:00
Alex
13d04b15de Fixed DateTime interaction with DateTimePickerWrapper & DateRangeSelector (#2657)
fixes #2655 
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-13 22:03:26 -08:00
Daniel Valdivia
88fdd3a456 Remove Unused Health Component (#2659)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-02-13 18:29:42 -06:00
Alex
de4ff1c1f0 Call Home Enabled in console (#2248)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-13 15:04:35 -08:00
Javier Adriel
a6637f18c8 Add fields to read json metadata (#2652) 2023-02-10 13:48:47 -06:00
Alex
d4031ee7b5 Display override settings with env variables (#2636)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-10 11:28:54 -08:00
Alex
7bf2b9601f Fixed RegisterCluster alignment (#2653)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-10 12:01:10 -06:00
Javier Adriel
6fbb4b568b Add handlers that will communicate with release service (#2651) 2023-02-09 12:49:07 -08:00
Alex
7a14f0c012 Release v0.23.1 (#2648)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-08 18:20:55 -08:00
Daniel Valdivia
e8f27228a2 Use latest Version of Operator for Testing (#2650) 2023-02-08 19:08:20 -06:00
jinapurapu
8346fe4bd6 Fixed permissions-2 inspect test (#2649)
Authored-by: Jillian Inapurapu <jillii@Jillians-MBP.attlocal.net>
2023-02-08 16:13:01 -08:00
Alex
d1b8d7240e Update mds version to v0.2.4 (#2647)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-08 14:11:52 -06:00
jinapurapu
274b16a1a6 Fixed text in AddEventDestination backlink for consistency (#2646) 2023-02-08 13:21:35 -06:00
Daniel Valdivia
b0d8c332e5 Rename Notification Endpoints (#2645)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-02-08 12:16:07 -06:00
Daniel Valdivia
ba1888b6c4 Remove unused components (#2641)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-02-08 11:17:16 -06:00
Daniel Valdivia
c0e47bafa4 Menu Tweaks and Rename Notifications to Events (#2639)
- Rename notifications to events.
- Add categories to event destinations
- Menu changes

Co-authored-by: jinapurapu <65002498+jinapurapu@users.noreply.github.com>
2023-02-07 21:19:17 -08:00
Alex
894f6ce131 Fixed Tenant License crash in tenant details (#2644)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-07 22:30:25 -06:00
Alex
f0b39a6b6b Updated copyright tags to 2023 (#2631)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-06 20:32:49 -08:00
Daniel Valdivia
24fdf3487b Move Register Component to Redux (#2630)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-02-06 13:51:39 -08:00
Shubhendu
900b8d9f06 Disable Audit Log and Monitoring by default (#2632)
While tenant creation, by default keep the Audit Log and Monitoring option disabled
2023-02-06 12:03:51 -08:00
Alex
58b45b8ebc Redesigned Logger Webhooks & Audit Webhooks pages (#2620)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-02 21:22:43 -08:00
Daniel Valdivia
606a69b92c Remove Unused Style code (#2629)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-02-02 22:42:15 -06:00
Allan Roger Reid
2c8b96c511 Expose error on Tenant if certificate is near expiration or expired (#2628) 2023-02-02 17:44:37 -08:00
Javier Adriel
38e6af1d09 Add tests for tenantEncryptionInfo (#2625) 2023-02-02 13:11:47 -06:00
Alex
32a289d563 Added runtime class name selector (#2626)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-02-01 20:58:19 -08:00
Daniel Valdivia
8d4d6eace0 Remove unused CSS Classes (#2623) 2023-02-01 18:37:18 -08:00
Alex
f337a8dce7 Updated Yarn dependencies (#2627) 2023-02-01 18:10:16 -08:00
jinapurapu
fba06cf651 Fix UI to access Support tools if registered (#2624) 2023-02-01 19:42:40 -06:00
Daniel Valdivia
fecf484eda Reformat imports (#2622)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-02-01 14:33:03 -08:00
Daniel Valdivia
34907856c5 Use MDS's BackLink (#2615)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-02-01 14:14:47 -06:00
Javier Adriel
4a2915a62b Use MDS's PageHeader (#2618) 2023-01-31 17:37:56 -06:00
Alex
8d51fe60f4 Update mds to v0.2.3 (#2617)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-31 13:53:58 -06:00
Daniel Valdivia
e96632a6b2 License Page Adjustments. Fix Object Browser double scroll. (#2616)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-31 13:15:28 -06:00
Daniel Valdivia
5ba7abaa79 Use MDS HelpBox (#2614)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-31 11:12:23 -06:00
Daniel Valdivia
451238d48b Disable Audit Logs by default on Tenant Creation (#2613)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-31 08:23:29 -08:00
Javier Adriel
8df91922ad Add test for tenantUpdateEncryption (#2611) 2023-01-30 16:34:33 -08:00
Anis Elleuch
34d62837fd Trust STS IDP connection when the url is localhost (#2603)
During SSO login, Console contacts MinIO server to generate new temporary credentials. When TLS is enabled, setting up a correct TLS certificate is something that needs to be done correctly by the user. However, recently, we started to skip the TLS verification when Console talks to MinIO server using a loopback address, but we missed the case of Console generating temporary credentials in case of IDP. This commit will get the configured MinIO server url to decide if the STS call needs to skip the TLS verification or not.

Co-authored-by: Anis Elleuch <anis@min.io>
2023-01-27 14:26:30 -08:00
Alex
ed281bbe4a Update mds to v0.2.1 (#2609)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-27 14:25:54 -08:00
Daniel Valdivia
b218cbf503 Anonymous Access (#2600)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-27 12:23:30 -08:00
Javier Adriel
c141b6d65e Add tests for setTenantMonitoring (#2608) 2023-01-26 18:35:49 -06:00
Alex
aacec617a8 Added bucket icons to object browser buckets listing (#2607)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-26 17:40:22 -06:00
Alex
ed19f3e1ad UI Fixes for License Plans page (#2605)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-26 14:45:39 -08:00
Javier Adriel
1374cf047e Add tests for updateTenantPools (#2602) 2023-01-26 10:30:52 -08:00
Daniel Valdivia
3a0e4c4f8c License Meta Tag (#2601)
Introduces a meta tag `minio-license` so that we can render the proper logo on login and menu
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-25 13:18:41 -08:00
Javier Adriel
b34b05f059 Tests for getTenantMonitoringResponse, getTenantUsage and getTenantPods (#2599) 2023-01-24 21:28:46 -06:00
Daniel Valdivia
579e8e05b7 Fix Login Warning (#2597)
Signed-off-by: Daniel Valdivia
<18384552+dvaldivia@users.noreply.github.com>
2023-01-24 11:17:00 -06:00
Daniel Valdivia
a0463f405d Github Action .nvmrc (#2596)
Signed-off-by: Daniel Valdivia
<18384552+dvaldivia@users.noreply.github.com>
2023-01-24 10:30:19 -06:00
Daniel Valdivia
ec344654db Release v0.23.0 (#2595)
Signed-off-by: Daniel Valdivia
<18384552+dvaldivia@users.noreply.github.com>
2023-01-23 20:32:40 -08:00
Daniel Valdivia
4aab5bc68f Fix Logout Warning (#2594)
Signed-off-by: Daniel Valdivia
<18384552+dvaldivia@users.noreply.github.com>
2023-01-23 21:56:37 -06:00
Alex
4c37afb446 Removed Force Delete from delete multiple objects request (#2592)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-23 19:53:20 -08:00
Daniel Valdivia
86115afc6b Upgrade Dependencies (#2589)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-23 19:51:29 -08:00
Alex
87399d1064 Fixed WS reconnection logic onError and onClose (#2591)
Removes duplicated reconnection request when error is reached on Object
Browser websocket, also removed the use of the connect call back as it
will contain references to the initial request and it is not necessary
as we already have information available.

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-24 08:13:52 +05:30
Alex
a01b855d2f Fixed issues with ThemedLogo component implementation (#2588)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-23 14:17:50 -06:00
Harshavardhana
be39cb5af3 update gorilla/websocket to minio/websocket (#2587) 2023-01-23 13:48:21 -06:00
Javier Adriel
6930fd198c Add tests for setImageRegistry tenantAdministrators and tenantConfiguration (#2584) 2023-01-23 10:56:54 -08:00
Daniel Valdivia
62e822df4e License FAQ (#2581)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-23 10:33:15 -08:00
Daniel Valdivia
6466687a27 Update superagent dependency (#2586)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-23 10:15:23 -08:00
Javier Adriel
ccaaa21f16 Move adminClientMock methods to single file (#2583) 2023-01-23 10:10:51 -06:00
Javier Adriel
0595ec166d Add tests to update tenant security and identity provider (#2580) 2023-01-20 14:26:59 -08:00
Daniel Valdivia
72d1698242 Upgrade Go Dependencies and mds (#2582)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-20 14:26:22 -08:00
Alex
9321e00d0a Made Improvements to Console System (#2579)
- Removed Lato font references in code making Inter font as default
- Rejected direct requests to static assets folders

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-19 11:15:28 -08:00
Javier Adriel
b03a49e4e1 Add tests for getTenantSecurity and getTenantIdentityProvider (#2578) 2023-01-19 11:44:55 -06:00
Javier Adriel
aa9f6f02ca Refactor and tests for adding tenant (#2577) 2023-01-13 11:11:40 -08:00
Allan Roger Reid
6a88d26054 Add tenant runtimeclass (#2565) 2023-01-12 14:27:53 -08:00
Daniel Valdivia
37db97dba9 Tests for User Objects (#2574)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-12 11:56:26 -08:00
Daniel Valdivia
0ccce2b2bc Update README.md screenshots (#2575)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-12 10:36:27 -08:00
Daniel Valdivia
58813eb4e0 Re-use MinIO latest binary (#2572)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-12 10:04:07 -08:00
Javier Adriel
5992edb3ba Refactor of tenant creation to make it more testable and readable (Phase 1) (#2571) 2023-01-12 09:17:57 -08:00
Daniel Valdivia
808e0ce3d5 Make remote buckets tests local (#2570)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-11 15:48:13 -08:00
Alex
534f3ec627 Release v0.22.5 (#2568) 2023-01-11 17:20:05 -06:00
Javier Adriel
9d37c9f743 Move mock k8s functions to one files (#2569) 2023-01-11 13:58:34 -08:00
Javier Adriel
1bad4c3da6 Add KMS UI (#2377)
Adds components to interact with KMS server connected to minio
2023-01-11 10:57:53 -08:00
Alex
9edb579156 Updated vulnerable dependencies (#2567)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-11 12:08:31 -06:00
Javier Adriel
d1aee5d431 Add tenants tests phase 1 (#2566) 2023-01-10 16:11:34 -08:00
Prakash Senthil Vel
dc8d4b4ca1 fix prometheus dashboard values for luxon migration (#2564) 2023-01-10 12:22:45 -06:00
Alex
35ab508109 Updated mds to v0.1.0 (#2563)
- Removed icons from console
- Replaced all icons & loaders in console with mds ones
- Renamed caret  icons
- Removed unused icons
- Replaced colored icons in object browser
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-09 14:35:46 -08:00
Javier Adriel
e01030820c Add tests for tenant logs (#2561) 2023-01-09 11:55:33 -08:00
dependabot[bot]
fab041b364 Bump luxon from 3.2.0 to 3.2.1 in /portal-ui (#2560) 2023-01-09 10:27:05 -08:00
Kaan Kabalak
e5da67d1bc Allow s3:Get* actions in Console (#2559) 2023-01-09 10:42:19 -06:00
Harshavardhana
bbc61930f9 always tidy-up go.mod (#2557) 2023-01-06 09:19:09 -08:00
Daniel Valdivia
40c789e7cb Github Actions v3 (#2556)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-05 21:54:55 -06:00
Alex
6084212bf6 Release v0.22.4 (#2542)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-05 16:23:34 -08:00
Daniel Valdivia
944b56751d Remove GKE Integration (#2552)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-05 17:54:12 -06:00
Pedro Juarez
c9e53542e0 Show the DNS helpbox on tenant edit, as is shown in tenant create (#2545) 2023-01-05 17:37:15 -06:00
Javier Adriel
7001067baa Increase threshold (#2555) 2023-01-05 17:31:32 -06:00
Alex
aece2251cf Migrated moment.js to Luxon (#2540)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-05 15:16:21 -08:00
Javier Adriel
756f0048f0 Add tests for admin info (#2553) 2023-01-05 14:54:00 -08:00
Daniel Valdivia
1cb2fca7a5 Increase coverage (#2551)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-05 14:36:21 -08:00
Kaan Kabalak
287af260b7 Fix Copy URL snackbar styling in Share File modal (#2549) 2023-01-05 14:27:36 -06:00
Daniel Valdivia
9fd76362dc Upgrade Go Dependencies (#2546)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-04 16:44:08 -08:00
Javier Adriel
733517fa76 Fix replication test (#2547)
Before the test was expecting an error while making the request but now the request is successful but it returns a 500 instead, this change will fix it
2023-01-04 15:07:03 -08:00
Daniel Valdivia
d8b3d0715f Change Go version to 1.19 (#2543)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2023-01-04 13:43:13 -08:00
Javier Adriel
1d951b28aa Allow Put* actions in console (#2544) 2023-01-04 12:51:46 -06:00
Alex
65ab6870e6 Updated mds to v0.0.9 (#2541)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-03 12:46:16 -06:00
Alex
255e3d14d0 Added delete governance bypass option when deleting file versions (#2536)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-01-03 10:35:37 -08:00
Javier Adriel
25e486ef18 Fix Operator login when openID provider is configured (#2533) 2023-01-03 10:35:15 -08:00
Alex
92123bd243 Changed selected styles for deleted rows (#2537)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-12-29 19:57:40 -06:00
Alex
6d5026d17e Update json5 & testcafe (#2538)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-12-29 17:11:17 -06:00
Prakash Senthil Vel
1e6ca131d9 update route path for idp screens as per menu placement (#2529) 2022-12-27 15:42:44 -06:00
Javier Adriel
21c6a441aa Upgrade jsonwebtoken - vulnerabilities found in current version (#2530) 2022-12-27 12:59:09 -06:00
Pedro Juarez
e23c110057 Run test with ubi 8.7 (#2532) 2022-12-22 14:26:18 -06:00
Kaan Kabalak
de16b73ce8 Capitalize Kubernetes in Login page text (#2526) 2022-12-22 09:49:02 -06:00
Alex
f269d67ec6 Release v0.22.3 (#2525)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-12-21 15:41:25 -08:00
Alex
7f3490974b First Phase of Luxon migration (#2521)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-12-21 11:56:05 -06:00
Javier Adriel
a42eef376d Support wildcard list actions (#2520) 2022-12-21 11:55:43 -06:00
Alex
bbf115dc71 Updated Login Front page for Console (#2522)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-12-21 10:33:05 -06:00
Prakash Senthil Vel
77bcdcfb08 fix crash on add user to group in user details (#2523) 2022-12-20 11:24:54 -06:00
jinapurapu
d713dc4933 Add Helpbox to Tier Type selector screen and adjust formatting (#2519) 2022-12-17 00:03:19 -06:00
Daniel Valdivia
0c9a6bf76b Tidy go.mod file (#2518) 2022-12-16 14:38:15 -06:00
Cesar Celis Hernandez
4c4094420b Ignoring message from MinIO (#2515)
As discussed with @harshavardhana , we should ignore the message on delete in the API. After @poornas changed in PR https://github.com/minio/minio/pull/16221 this message is expected and cannot be considered as an error.
2022-12-16 12:16:19 -08:00
Javier Adriel
96e55ca79b UI changes to IDP screen and remove old sections from settings screen (#2513) 2022-12-16 11:46:35 -08:00
jinapurapu
c61c5024d0 Add hover effect to Tier Type buttons (#2509) 2022-12-16 13:41:39 -06:00
Alex
f61d0a68d5 Implements CONSOLE_DEV_MODE env var (#2517)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-12-16 13:32:52 -06:00
Javier Adriel
f917f7d167 Remove commented jobs (#2514) 2022-12-16 09:33:12 -08:00
Alex
35541ef323 Fixed Delete all object versions selector functionality (#2516)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-12-16 11:07:40 -06:00
Daniel Valdivia
1f60d4a808 Tweaks to Menu and Bucket Details (#2512) 2022-12-15 18:45:31 -06:00
Alex
adc199b315 Made improvements to title bar & break points (#2508)
Removed extra margin & changed breakpoints for console / operator pages title bar
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-12-14 18:13:18 -08:00
Daniel Valdivia
f557c4c550 Split Object Browser and Buckets Admin (#2500)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-12-14 16:20:13 -08:00
Javier Adriel
0c4a73ff10 Do not regenerate assets for permissions-* tests (#2501) 2022-12-14 13:26:04 -08:00
jinapurapu
203e019e82 Add hover effect to Notification type selector buttons (#2502) 2022-12-14 14:38:34 -06:00
jinapurapu
4d2a39bad2 Added HelpBox to Notification Type Selector screen (#2503) 2022-12-12 22:08:20 -06:00
dependabot[bot]
db4fae3603 Bump d3-color from 2.0.0 to 3.1.0 in /portal-ui (#2504) 2022-12-12 17:51:10 -06:00
Daniel Valdivia
871789a236 Bump UI Dependencies (#2497)
Signed-off-by: Daniel Valdivia
<18384552+dvaldivia@users.noreply.github.com>
2022-12-12 14:44:51 -08:00
Javier Adriel
ba6fb53594 Increase test threshold (#2499) 2022-12-12 12:38:33 -08:00
Javier Adriel
5d74b21bc4 Reduce test execution times (#2498) 2022-12-12 12:37:53 -08:00
Javier Adriel
006490c753 Add admin subnet tests (#2490) 2022-12-11 23:16:30 -08:00
Daniel Valdivia
1614af0128 Upgrade Go Dependencies (#2496)
Upgraded all dependencies except for Kubernetes packages until we
validate 1.26

Signed-off-by: Daniel Valdivia
<18384552+dvaldivia@users.noreply.github.com>
2022-12-10 10:25:51 -08:00
Daniel Valdivia
27fd91ed37 Release v0.22.2 (#2495)
Signed-off-by: Daniel Valdivia
<18384552+dvaldivia@users.noreply.github.com>
2022-12-09 16:42:57 -08:00
Alex
7df0560104 Updated mds & fixed blank pages on login (#2494)
- Updates mds to v0.0.7
- Fixed an issue with blank pages during login
- Fixes an issue during logout where path was redirected to
'logout/login'

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-12-09 14:24:58 -08:00
Javier Adriel
2368199e03 IDP management UI (#2487)
Adds UI to interact with IDP Configurations (CRUD)
2022-12-09 14:13:10 -08:00
Daniel Valdivia
bee98e1ba0 fix: race Condition on Object Browser via Websocket (#2492)
Signed-off-by: Daniel Valdivia
<18384552+dvaldivia@users.noreply.github.com>
2022-12-09 11:18:19 -08:00
Harshavardhana
8abbbb4625 avoid printing random logs (#2489) 2022-12-08 07:54:21 -08:00
Javier Adriel
ef182fe75e Increase threshold to 54.4 (Current coverage 54.5) (#2488) 2022-12-07 20:01:09 -06:00
Daniel Valdivia
613e93fdc6 Release v0.22.1 (#2486)
Signed-off-by: Daniel Valdivia <hola@danielvaldivia.com>
2022-12-06 14:41:25 -08:00
Javier Adriel
a5e3c89a54 Implement handlers for managing IDP (OpenID and LDAP) Configurations (#2485) 2022-12-06 14:33:17 -06:00
jinapurapu
7f55b71495 Add backend tier status checking to all tier types (#2420)
Moves Tier status check to backend for all Tier types.

![Screen Shot 2022-12-02 at 10 37 36
AM](https://user-images.githubusercontent.com/65002498/205370942-e20c0229-77b1-4a7c-9579-eebbd101c4ae.png)
![Screen Shot 2022-12-02 at 10 37 18
AM](https://user-images.githubusercontent.com/65002498/205370943-0d038206-b0e3-4597-9522-b21f94c36c0b.png)

Co-authored-by: Jillian Inapurapu <jillii@Jillians-MBP.attlocal.net>
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2022-12-06 12:13:14 -08:00
Alex
d95d59e454 Changed Object browser logic to work with websockets (#2419)
fixes https://github.com/minio/console/issues/943

## What does this do?

This allows us to start streaming results to the list instead of waiting
to retrieve all the objects list before sending it to the client

Included a couple of fixes:

- Removed metadata for deleted items
- Fixed multiple metadata requests
- Fixed height grow for parent wrapper
- Fixed object reload after restore version


Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-12-06 11:23:07 -08:00
Harshavardhana
08ea069ed4 change doc URL if UI is running in k8s (#2484)
Co-authored-by: Prakash Senthil Vel <23444145+prakashsvmx@users.noreply.github.com>
2022-12-06 09:53:01 -08:00
Javier Adriel
e7a41b4cd9 Call end_session_endpoint in IDP provider when login out from Console (#2476) 2022-12-05 18:14:41 -06:00
Alex
262a601d21 Release v0.22.0 (#2482)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-12-02 15:12:59 -08:00
dependabot[bot]
00af6b5179 Bump decode-uri-component from 0.2.0 to 0.2.2 in /portal-ui (#2481)
Bumps
[decode-uri-component](https://github.com/SamVerschueren/decode-uri-component)
from 0.2.0 to 0.2.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/SamVerschueren/decode-uri-component/releases">decode-uri-component's
releases</a>.</em></p>
<blockquote>
<h2>v0.2.2</h2>
<ul>
<li>Prevent overwriting previously decoded tokens  980e0bf</li>
</ul>
<p><a
href="https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.1...v0.2.2">https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.1...v0.2.2</a></p>
<h2>v0.2.1</h2>
<ul>
<li>Switch to GitHub workflows  76abc93</li>
<li>Fix issue where decode throws - fixes <a
href="https://github-redirect.dependabot.com/SamVerschueren/decode-uri-component/issues/6">#6</a>
746ca5d</li>
<li>Update license (<a
href="https://github-redirect.dependabot.com/SamVerschueren/decode-uri-component/issues/1">#1</a>)
486d7e2</li>
<li>Tidelift tasks  a650457</li>
<li>Meta tweaks  66e1c28</li>
</ul>
<p><a
href="https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.1">https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a0eea469d2"><code>a0eea46</code></a>
0.2.2</li>
<li><a
href="980e0bf09b"><code>980e0bf</code></a>
Prevent overwriting previously decoded tokens</li>
<li><a
href="3c8a373dd4"><code>3c8a373</code></a>
0.2.1</li>
<li><a
href="76abc93978"><code>76abc93</code></a>
Switch to GitHub workflows</li>
<li><a
href="746ca5dcb6"><code>746ca5d</code></a>
Fix issue where decode throws - fixes <a
href="https://github-redirect.dependabot.com/SamVerschueren/decode-uri-component/issues/6">#6</a></li>
<li><a
href="486d7e26d3"><code>486d7e2</code></a>
Update license (<a
href="https://github-redirect.dependabot.com/SamVerschueren/decode-uri-component/issues/1">#1</a>)</li>
<li><a
href="a65045724e"><code>a650457</code></a>
Tidelift tasks</li>
<li><a
href="66e1c2834c"><code>66e1c28</code></a>
Meta tweaks</li>
<li>See full diff in <a
href="https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=decode-uri-component&package-manager=npm_and_yarn&previous-version=0.2.0&new-version=0.2.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the
default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as
the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as
the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the
default for future PRs for this repo and language

You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/minio/console/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-02 12:33:19 -06:00
Aditya Manthramurthy
5800d01406 Bump up madmin-go to v2 (#2479) 2022-12-01 20:55:21 -08:00
Aditya Manthramurthy
c803451920 Separate out dependency vulnerability checks in CI (#2480) 2022-12-01 14:17:46 -06:00
Lenin Alevski
95bdc70d1d Display warning for public access policy in bucket settings (#2473)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-11-23 09:10:14 -08:00
Prakash Senthil Vel
5d10197334 page header ux (#2475)
Closes [#1031](https://github.com/miniohq/engineering/issues/1031)
2022-11-23 09:09:47 -08:00
Prakash Senthil Vel
e7da6cd651 relax editing of endpoint in add site replication UI (#2474) 2022-11-22 09:07:16 -08:00
adfost
0f35369292 fix: incorrect IP and bucket regexes (#2469)
Fixes https://github.com/minio/console/issues/2453.
2022-11-21 13:30:32 -08:00
Daniel Valdivia
579845dfb1 Upgrade Go Dependencies (#2472)
Signed-off-by: Daniel Valdivia
<18384552+dvaldivia@users.noreply.github.com>
2022-11-21 11:58:14 -08:00
Alex
6ad5c16d3a Fixed logo visibility in Safari (#2470)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-11-18 14:09:00 -06:00
Alex
24176b5c7d Initial Changes to Login page styles (#2467)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-11-18 13:00:08 -06:00
jinapurapu
0b3b5979ba Local bucket creation time (#2464)
<img width="1327" alt="Screen Shot 2022-11-16 at 9 57 57 AM"
src="https://user-images.githubusercontent.com/65002498/202257455-ceb041b0-4db9-44f3-9fd3-cc73c89f4339.png">


Fixes https://github.com/minio/console/issues/2462

Authored-by: Jillian Inapurapu <jillii@Jillians-MBP.attlocal.net>
2022-11-16 15:40:22 -08:00
jinapurapu
dbec9fbb4a Fix UpdateTierCredentialsModal For MinIO tier (#2459) 2022-11-16 12:00:17 -06:00
Prakash Senthil Vel
7cd0bc7cac update airgap registration steps and instructions (#2463) 2022-11-16 11:36:21 -06:00
Javier Adriel
c12a931dbf Update madmin-go (#2460) 2022-11-15 20:29:36 -06:00
jinapurapu
f8af10dd26 Add permission guidance if User doesn't have permission to view Objects in bucket (#2448) 2022-11-15 19:18:37 -06:00
Alex
4c47b9c3e6 Updated loader utils to avoid ReDoS issue (#2461)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-11-15 18:22:37 -06:00
Alex
44f2fc67a3 Release v0.21.3 (#2458)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-11-15 12:16:09 -08:00
Anis Elleuch
ec1ba16ef7 prom: Fix leak when a call fails with the prometheus endpoint (#2456) 2022-11-15 12:09:58 -06:00
Prakash Senthil Vel
7a5724591a fix crash in ui for empty log entries (#2457) 2022-11-15 12:09:42 -06:00
Alex
9f433bc359 Release v0.21.3 (#2455)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-11-15 07:52:33 -08:00
Javier Adriel
1d45a174ac Add deprecated message in Audit logs section (#2437) 2022-11-14 13:06:55 -06:00
Alex
80cee32031 Fixed Table Action buttons roundness (#2451)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-11-14 12:58:01 -06:00
Prakash Senthil Vel
437cfd0b8e fix broken chart axis data for date time formatting (#2452) 2022-11-14 11:56:17 -06:00
jinapurapu
dc4dae6ddb Added formatting to required permssions in permissionTooltipHelper (#2447) 2022-11-14 09:48:06 -06:00
Alex
2f578010a0 New Menu Adjustments (#2450) 2022-11-13 10:23:55 -08:00
Prakash Senthil Vel
e7ec3fe61f fix sub path resource matching in list objects (#2444)
fixes #https://github.com/minio/console/issues/2400 

Used the policy to validate the fix.

```json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::sam-card"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::sam-card/*"
            ]
        },
        {
            "Effect": "Deny",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::sam-card/aa/*"
            ]
        }
    ]
}

```
2022-11-10 11:27:40 -08:00
dilverse
7b389fc323 Add erasure info support (#2446)
Make it easier for user to see backend properties like backend-type,
Standard storage class Parity and Reduced Redundancy storage class
Parity
Co-authored-by: Prakash Senthil Vel <23444145+prakashsvmx@users.noreply.github.com>
2022-11-10 09:24:39 -08:00
Daniel Valdivia
86361b630e Tweak some icons to inherit color (#2445)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-11-09 11:05:39 -06:00
Lenin Alevski
c57df87bc3 Add yarn audit check on github actions (#2441)
Checks for known security issues with the installed packages

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-11-08 19:20:47 -08:00
Lenin Alevski
cbbf3c5a53 selective version resolution for load-utils in react-scripts (#2442)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-11-08 13:03:50 -08:00
3499 changed files with 268989 additions and 481155 deletions

View File

@@ -3,5 +3,5 @@ dist/
target/
console
!console/
portal-ui/node_modules/
web-app/node_modules/
.git/

48
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,48 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: community, triage
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.
<!--- Provide a general summary of the issue in the title above -->
## Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
## Current Behavior
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
## Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->
## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
2.
3.
4.
## Context
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
## Regression
<!-- Is this issue a regression? (Yes / No) -->
<!-- If Yes, optionally please include the MinIO version or commit id or PR# that caused this regression, if you have these details. -->
## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
* MinIO version used (`minio --version`):
* Server setup and configuration:
* Operating System and version (`uname -a`):

166
.github/workflows/cross-compile.yaml vendored Normal file
View File

@@ -0,0 +1,166 @@
# @format
name: Cross Compile
on:
pull_request:
branches:
- master
paths:
- go.sum
# This ensures that previous jobs for the PR are canceled when the PR is
# updated.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
jobs:
cross-compile-1:
name: Cross compile
needs:
- lint-job
- ui-assets
- reuse-golang-dependencies
- semgrep-static-code-analysis
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [ 1.21.x ]
os: [ ubuntu-latest ]
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
cache: true
id: go
- name: Build on ${{ matrix.os }}
env:
GO111MODULE: on
GOOS: linux
run: |
make crosscompile arg1="'linux/ppc64le linux/mips64'"
cross-compile-2:
name: Cross compile 2
needs:
- lint-job
- ui-assets
- reuse-golang-dependencies
- semgrep-static-code-analysis
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [ 1.21.x ]
os: [ ubuntu-latest ]
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
cache: true
id: go
- name: Build on ${{ matrix.os }}
env:
GO111MODULE: on
GOOS: linux
run: |
make crosscompile arg1="'linux/arm64 linux/s390x'"
cross-compile-3:
name: Cross compile 3
needs:
- lint-job
- ui-assets
- reuse-golang-dependencies
- semgrep-static-code-analysis
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [ 1.21.x ]
os: [ ubuntu-latest ]
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
cache: true
id: go
- name: Build on ${{ matrix.os }}
env:
GO111MODULE: on
GOOS: linux
run: |
make crosscompile arg1="'darwin/amd64 freebsd/amd64'"
cross-compile-4:
name: Cross compile 4
needs:
- lint-job
- ui-assets
- reuse-golang-dependencies
- semgrep-static-code-analysis
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [ 1.21.x ]
os: [ ubuntu-latest ]
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
cache: true
id: go
- name: Build on ${{ matrix.os }}
env:
GO111MODULE: on
GOOS: linux
run: |
make crosscompile arg1="'windows/amd64 linux/arm'"
cross-compile-5:
name: Cross compile 5
needs:
- lint-job
- ui-assets
- reuse-golang-dependencies
- semgrep-static-code-analysis
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [ 1.21.x ]
os: [ ubuntu-latest ]
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
cache: true
id: go
- name: Build on ${{ matrix.os }}
env:
GO111MODULE: on
GOOS: linux
run: |
make crosscompile arg1="'linux/386 netbsd/amd64'"

18
.github/workflows/issues.yaml vendored Normal file
View File

@@ -0,0 +1,18 @@
# @format
name: Issue Workflow
on:
issues:
types:
- opened
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.5.0
with:
project-url: https://github.com/orgs/miniohq/projects/2
github-token: ${{ secrets.BOT_PAT }}

File diff suppressed because it is too large Load Diff

53
.github/workflows/vulncheck.yaml vendored Normal file
View File

@@ -0,0 +1,53 @@
# @format
name: Vulnerability Check
on:
pull_request:
branches:
- master
push:
branches:
- master
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
vulncheck:
name: Analysis
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.21.7
check-latest: true
- name: Get official govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
shell: bash
- name: Run govulncheck
run: govulncheck ./...
shell: bash
react-code-known-vulnerabilities:
name: "React Code Has No Known Vulnerable Deps"
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ 1.21.7 ]
os: [ ubuntu-latest ]
steps:
- name: Check out code
uses: actions/checkout@v3
- uses: actions/setup-node@v3
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

12
.gitignore vendored
View File

@@ -1,3 +1,13 @@
# Playwright Data
web-app/storage/
web-app/playwright/.auth/admin.json
# Report from Playwright
web-app/playwright-report/
# Coverage from Playwright
web-app/.nyc_output/
# Binaries for programs and plugins
*.exe
*.exe~
@@ -27,7 +37,7 @@ dist/
# Ignore node_modules
portal-ui/node_modules/
web-app/node_modules/
# Ignore tls cert and key
private.key

View File

@@ -23,8 +23,8 @@ linters:
- gomodguard
- gofmt
- unused
- staticcheck
- unconvert
- varcheck
- gocritic
- gofumpt
- durationcheck
@@ -46,4 +46,4 @@ run:
skip-dirs:
- pkg/clientgen
- pkg/apis/networking.gke.io
- restapi/operations
- api/operations

View File

@@ -1,195 +0,0 @@
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
project_name: console
release:
name_template: "Release version {{.Tag}}"
github:
owner: minio
name: console
extra_files:
- glob: "*.minisig"
before:
hooks:
# you may remove this if you don't use vgo
- go mod tidy -compat=1.18
builds:
-
goos:
- linux
- darwin
- windows
goarch:
- amd64
- ppc64le
- s390x
- arm64
ignore:
- goos: darwin
goarch: arm
- goos: windows
goarch: arm64
- goos: windows
goarch: arm
env:
- CGO_ENABLED=0
main: ./cmd/console/
flags:
- -trimpath
- --tags=kqueue,operator
ldflags:
- -s -w -X github.com/minio/console/pkg.ReleaseTag={{.Tag}} -X github.com/minio/console/pkg.CommitID={{.FullCommit}} -X github.com/minio/console/pkg.Version={{.Version}} -X github.com/minio/console/pkg.ShortCommitID={{.ShortCommit}} -X github.com/minio/console/pkg.ReleaseTime={{.Date}}
archives:
-
name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}"
format: binary
replacements:
arm: arm
signs:
-
signature: "${artifact}.minisig"
cmd: "sh"
args:
- '-c'
- 'minisign -s /media/${USER}/minio/minisign.key -Sm ${artifact} < /media/${USER}/minio/minisign-passphrase'
artifacts: all
snapshot:
name_template: v0.0.0@{{.ShortCommit}}
changelog:
sort: asc
nfpms:
-
vendor: MinIO, Inc.
homepage: https://github.com/minio/console
maintainer: MinIO Development <dev@min.io>
description: MinIO Console Server
license: GNU Affero General Public License v3.0
formats:
- deb
- rpm
contents:
# Basic file that applies to all packagers
- src: systemd/console.service
dst: /etc/systemd/system/minio-console.service
dockers:
- image_templates:
- "minio/console:{{ .Tag }}-amd64"
use: buildx
goarch: amd64
dockerfile: Dockerfile.release
extra_files:
- LICENSE
- CREDITS
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=TAG={{ .Tag }}"
- image_templates:
- "minio/console:{{ .Tag }}-ppc64le"
use: buildx
goarch: ppc64le
dockerfile: Dockerfile.release
extra_files:
- LICENSE
- CREDITS
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=TAG={{ .Tag }}"
- image_templates:
- "minio/console:{{ .Tag }}-s390x"
use: buildx
goarch: s390x
dockerfile: Dockerfile.release
extra_files:
- LICENSE
- CREDITS
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=TAG={{ .Tag }}"
- image_templates:
- "minio/console:{{ .Tag }}-arm64"
use: buildx
goarch: arm64
goos: linux
dockerfile: Dockerfile.release
extra_files:
- LICENSE
- CREDITS
build_flag_templates:
- "--platform=linux/arm64"
- "--build-arg=TAG={{ .Tag }}"
- image_templates:
- "quay.io/minio/console:{{ .Tag }}-amd64"
use: buildx
goarch: amd64
dockerfile: Dockerfile.release
extra_files:
- LICENSE
- CREDITS
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=TAG={{ .Tag }}"
- image_templates:
- "quay.io/minio/console:{{ .Tag }}-ppc64le"
use: buildx
goarch: ppc64le
dockerfile: Dockerfile.release
extra_files:
- LICENSE
- CREDITS
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=TAG={{ .Tag }}"
- image_templates:
- "quay.io/minio/console:{{ .Tag }}-s390x"
use: buildx
goarch: s390x
dockerfile: Dockerfile.release
extra_files:
- LICENSE
- CREDITS
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=TAG={{ .Tag }}"
- image_templates:
- "quay.io/minio/console:{{ .Tag }}-arm64"
use: buildx
goarch: arm64
goos: linux
dockerfile: Dockerfile.release
extra_files:
- LICENSE
- CREDITS
build_flag_templates:
- "--platform=linux/arm64"
- "--build-arg=TAG={{ .Tag }}"
docker_manifests:
- name_template: minio/console:{{ .Tag }}
image_templates:
- minio/console:{{ .Tag }}-amd64
- minio/console:{{ .Tag }}-arm64
- minio/console:{{ .Tag }}-ppc64le
- minio/console:{{ .Tag }}-s390x
- name_template: quay.io/minio/console:{{ .Tag }}
image_templates:
- quay.io/minio/console:{{ .Tag }}-amd64
- quay.io/minio/console:{{ .Tag }}-arm64
- quay.io/minio/console:{{ .Tag }}-ppc64le
- quay.io/minio/console:{{ .Tag }}-s390x
- name_template: minio/console:latest
image_templates:
- minio/console:{{ .Tag }}-amd64
- minio/console:{{ .Tag }}-arm64
- minio/console:{{ .Tag }}-ppc64le
- minio/console:{{ .Tag }}-s390x

2
.nvmrc
View File

@@ -1 +1 @@
17
18

View File

@@ -5,7 +5,7 @@
# Common large paths
node_modules/
portal-ui/node_modules/
web-app/node_modules/
build/
dist/
.idea/
@@ -30,4 +30,6 @@ tests/
.vscode/
*.code-workspace
*~
.eslintcache
.eslintcache
consoleApi.ts

248
CHANGELOG.md Normal file
View File

@@ -0,0 +1,248 @@
<!-- @format -->
# Changelog
## Release v0.46.0
Features:
- Added latest help content to forms
Bug Fix:
- Disabled Create User button in certain policy cases
- Fixed an issue with Logout request
- Upgraded project dependencies
## Release v0.45.0
Deprecated:
- Deprecated Heal / Drives page
Features:
- Updated tines on menus & pages
Bug Fix:
- Upgraded project dependencies
## Release v0.44.0
Bug Fix:
- Upgraded project dependencies
- Fixed events icons not loading in subpaths
## Release v0.43.1
Bug Fix:
- Update Share Object UI to reflect maximum expiration time in UI
## Release v0.43.0
Features:
- Updated PDF preview method
Bug Fix:
- Fixed vulnerabilities
- Prevented non-necessary metadata calls in object browser
## Release v0.42.2
Bug Fix:
- Hidden Prometheus metrics if URL is empty
## Release v0.42.1
Bug Fix:
- Reset go version to 1.19
## Release v0.42.0
Features:
- Introducing Dark Mode
Bug Fix:
- Fixed vulnerabilities
- Changes on Upload and Delete object urls
- Fixed blocking subpath creation if not enough permissions
- Removed share object option at prefix level
- Updated allowed actions for a deleted object
## Release v0.41.0
Features:
- Updated pages to use mds components
- support for resolving IPv4/IPv6
Bug Fix:
- Remove cache for ClientIP
- Fixed override environment variables display in settings page
- Fixed daylight savings time support in share modal
## Release v0.40.0
Features:
- Updated OpenID page
- Added New bucket event types support
Bug Fix:
- Fixed crash in access keys page
- Fixed AuditLog filters issue
- Fixed multiple issues with Object Browser
## Release v0.39.0
Features:
- Migrated metrics page to mds
- Migrated Register page to mds
Bug Fix:
- Fixed LDAP configuration page issues
- Load available certificates in logout
- Updated dependencies & go version
- Fixed delete objects functionality
## Release v0.38.0
Features:
- Added extra information to Service Accounts page
- Updated Tiers, Site Replication, Speedtest, Heal & Watch pages components
Bug Fix:
- Fixed IDP expiry time errors
- Updated project Dependencies
## Release v0.37.0
Features:
- Updated Trace and Logs page components
- Updated Prometheus metrics
Bug Fix:
- Disabled input fields for Subscription features if MinIO is not registered
## Release v0.36.0
Features:
- Updated Settings page components
Bug Fix:
- Show LDAP Enabled value LDAP configuration
- Download multiple objects in same path as they were selected
## Release v0.35.1
Bug Fix:
- Change timestamp format for zip creation
## Release v0.35.0
Features:
- Add Exclude Folders and Exclude Prefixes during bucket creation
- Download multiple selected objects as zip and ignore deleted objects
- Updated Call Home, Inspet, Profile and Health components
Bug Fix:
- Remove extra white spaces for configuration strings
- Allow Create New Path in bucket view when having right permissions
## Release v0.34.0
Features:
- Updated Buckets components
Bug Fix:
- Fixed SUBNET Health report upload
- Updated Download Handler
- Fixes issue with rewind
- Avoid 1 hour expiration for IDP credentials
---
## Release v0.33.0
Features:
- Updated OpenID, LDAP components
Bug Fix:
- Fixed security issues
- Fixed navigation issues in Object Browser
- Fixed Dashboard metrics
---
## Release v0.32.0
Features:
- Updated Users and Groups components
- Added placeholder image for Help Menu
Bug Fix:
- Fixed memory leak in WebSocket API for Object Browser
---
## Release v0.31.0
**Breaking Changes:**
- **Removed support for Standalone Deployments**
Features:
- Updated way files are displayed in uploading component
- Updated Audit Logs and Policies components
Bug Fix:
- Fixed Download folders issue in Object Browser
- Added missing Notification Events (ILM & REPLICA) in Events Notification Page
- Fixed Security Vulnerability for `semver` dependency
---
## Release v0.30.0
Features:
- Added MinIO Console Help Menu
- Updated UI Menu components
Bug Fix:
- Disable the Upload button on Object Browser if the user is not allowed
- Fixed security vulnerability for `lestrrat-go/jwx` and `fast-xml-parser`
- Fixed bug on sub-paths for Object Browser
- Reduce the number of calls to `/session` API endpoint to improve performance
- Rolled back the previous change for the Share File feature to no longer ask for Service Account access keys

View File

@@ -4,56 +4,80 @@ This is a REST portal server created using [go-swagger](https://github.com/go-sw
The API handlers are created using a YAML definition located in `swagger.YAML`.
To add new api, the YAML file needs to be updated with all the desired apis using the [Swagger Basic Structure](https://swagger.io/docs/specification/2-0/basic-structure/), this includes paths, parameters, definitions, tags, etc.
To add new api, the YAML file needs to be updated with all the desired apis using
the [Swagger Basic Structure](https://swagger.io/docs/specification/2-0/basic-structure/), this includes paths,
parameters, definitions, tags, etc.
## Generate server from YAML
Once the YAML file is ready we can autogenerate the code needed for the new api by just running:
Validate it:
```
swagger validate ./swagger.yml
```
Update server code:
```
make swagger-gen
```
This will update all the necessary code.
`./restapi/configure_console.go` is a file that contains the handlers to be used by the application, here is the only place where we need to update our code to support the new apis. This file is not affected when running the swagger generator and it is safe to edit.
`./api/configure_console.go` is a file that contains the handlers to be used by the application, here is the only place
where we need to update our code to support the new apis. This file is not affected when running the swagger generator
and it is safe to edit.
## Unit Tests
`./restapi/handlers_test.go` needs to be updated with the proper tests for the new api.
`./api/handlers_test.go` needs to be updated with the proper tests for the new api.
To run tests:
```
go test ./restapi
go test ./api
```
## Commit changes
After verification, commit your changes. This is a [great post](https://chris.beams.io/posts/git-commit/) on how to write useful commit messages
After verification, commit your changes. This is a [great post](https://chris.beams.io/posts/git-commit/) on how to
write useful commit messages
```
$ git commit -am 'Add some feature'
```
### Push to the branch
Push your locally committed changes to the remote origin (your fork)
```
$ git push origin my-new-feature
```
### Create a Pull Request
Pull requests can be created via GitHub. Refer to [this document](https://help.github.com/articles/creating-a-pull-request/) for detailed steps on how to create a pull request. After a Pull Request gets peer reviewed and approved, it will be merged.
Pull requests can be created via GitHub. Refer
to [this document](https://help.github.com/articles/creating-a-pull-request/) for detailed steps on how to create a pull
request. After a Pull Request gets peer reviewed and approved, it will be merged.
## FAQs
### How does ``console`` manages dependencies?
``MinIO`` uses `go mod` to manage its dependencies.
- Run `go get foo/bar` in the source folder to add the dependency to `go.mod` file.
To remove a dependency
- Edit your code and remove the import reference.
- Run `go mod tidy` in the source folder to remove dependency from `go.mod` file.
### What are the coding guidelines for console?
``console`` is fully conformant with Golang style. Refer: [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project. If you observe offending code, please feel free to send a pull request or ping us on [Slack](https://slack.min.io).
``console`` is fully conformant with Golang style.
Refer: [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project. If you observe
offending code, please feel free to send a pull request or ping us on [Slack](https://slack.min.io).

6742
CREDITS

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,101 @@
# Developing MinIO Console
The MinIO Console requires the [MinIO Server](https://github.com/minio/minio). For development purposes, you also need
to run both the MinIO Console web app and the MinIO Console server.
## Running MinIO Console server
Build the server in the main folder by running:
```
make
```
> Note: If it's the first time running the server, you might need to run `go mod tidy` to ensure you have all modules
> required.
> To start the server run:
```
CONSOLE_ACCESS_KEY=<your-access-key>
CONSOLE_SECRET_KEY=<your-secret-key>
CONSOLE_MINIO_SERVER=<minio-server-endpoint>
CONSOLE_DEV_MODE=on
./console server
```
## Running MinIO Console web app
Refer to `/web-app` [instructions](/web-app/README.md) to run the web app locally.
# Building with MinIO
To test console in its shipping format, you need to build it from the MinIO repository, the following step will guide
you to do that.
### 0. Building with UI Changes
If you are performing changes in the UI components of console and want to test inside the MinIO binary, you need to
build assets first.
In the console folder run
```shell
make assets
```
This will regenerate all the static assets that will be served by MinIO.
### 1. Clone the `MinIO` repository
In the parent folder of where you cloned this `console` repository, clone the MinIO Repository
```shell
git clone https://github.com/minio/minio.git
```
### 2. Update `go.mod` to use your local version
In the MinIO repository open `go.mod` and after the first `require()` directive add a `replace()` directive
```
...
)
replace (
github.com/minio/console => "../console"
)
require (
...
```
### 3. Build `MinIO`
Still in the MinIO folder, run
```shell
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
@@ -15,7 +113,8 @@ $ docker cp console/docs/ldap/billy.ldif my-openldap-container:/container/servic
$ docker exec my-openldap-container ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin -f /container/service/slapd/assets/test/billy.ldif -H ldap://localhost
```
Query the ldap server to check the user billy was created correctly and got assigned to the consoleAdmin group, you should get a list
Query the ldap server to check the user billy was created correctly and got assigned to the consoleAdmin group, you
should get a list
containing ldap users and groups.
```
@@ -30,7 +129,7 @@ $ docker exec my-openldap-container ldapsearch -x -H ldap://localhost -b uid=bil
### Change the password for user billy
Set the new password for `billy` to `minio123` and enter `admin` as the default `LDAP Password`
Set the new password for `billy` to `minio123` and enter `admin` as the default `LDAP Password`
```
$ docker exec -it my-openldap-container /bin/bash
@@ -41,6 +140,7 @@ Enter LDAP Password:
```
### Add the consoleAdmin policy to user billy on MinIO
```
$ cat > consoleAdmin.json << EOF
{
@@ -66,8 +166,8 @@ $ cat > consoleAdmin.json << EOF
]
}
EOF
$ mc admin policy add myminio consoleAdmin consoleAdmin.json
$ mc admin policy set myminio consoleAdmin user="uid=billy,dc=example,dc=org"
$ mc admin policy create myminio consoleAdmin consoleAdmin.json
$ mc admin policy attach myminio consoleAdmin --user="uid=billy,dc=example,dc=org"
```
## Run MinIO

View File

@@ -3,17 +3,17 @@ FROM node:$NODE_VERSION as uilayer
WORKDIR /app
COPY ./portal-ui/package.json ./
COPY ./portal-ui/yarn.lock ./
COPY ./web-app/package.json ./
COPY ./web-app/yarn.lock ./
RUN yarn install
COPY ./portal-ui .
COPY ./web-app .
RUN make build-static
USER node
FROM golang:1.18 as golayer
FROM golang:1.19 as golayer
RUN apt-get update -y && apt-get install -y ca-certificates
@@ -29,10 +29,10 @@ WORKDIR /go/src/github.com/minio/console/
ENV CGO_ENABLED=0
COPY --from=uilayer /app/build /go/src/github.com/minio/console/portal-ui/build
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.6
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
MAINTAINER MinIO Development "dev@min.io"
EXPOSE 9090

View File

@@ -3,11 +3,11 @@ FROM node:$NODE_VERSION as uilayer
WORKDIR /app
COPY ./portal-ui/package.json ./
COPY ./portal-ui/yarn.lock ./
COPY ./web-app/package.json ./
COPY ./web-app/yarn.lock ./
RUN yarn install
COPY ./portal-ui .
COPY ./web-app .
RUN yarn install && make build-static

View File

@@ -1,10 +1,11 @@
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
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
COPY CREDITS /licenses/CREDITS
COPY LICENSE /licenses/LICENSE
LABEL name="MinIO" \
vendor="MinIO Inc <dev@min.io>" \
maintainer="MinIO Inc <dev@min.io>" \
@@ -13,11 +14,14 @@ LABEL name="MinIO" \
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."
RUN \
microdnf update --nodocs && \
microdnf install ca-certificates --nodocs
EXPOSE 9090
# 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"]

113
Makefile
View File

@@ -13,12 +13,7 @@ default: console
.PHONY: console
console:
@echo "Building Console binary to './console'"
@(GO111MODULE=on CGO_ENABLED=0 go build -trimpath --tags=kqueue,operator --ldflags "-s -w" -o console ./cmd/console)
k8sdev:
@docker build -t $(TAG) --build-arg build_version=$(BUILD_VERSION) --build-arg build_time='$(BUILD_TIME)' --build-arg NODE_VERSION='$(NODE_VERSION)' .
@kind load docker-image $(TAG)
@echo "Done, now restart your console deployment"
@(GO111MODULE=on CGO_ENABLED=0 go build -trimpath --tags=kqueue --ldflags "-s -w" -o console ./cmd/console)
getdeps:
@mkdir -p ${GOPATH}/bin
@@ -43,7 +38,7 @@ install: console
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/console $(GOPATH)/bin/console
@echo "Installation successful. To learn more, try \"console --help\"."
swagger-gen: clean-swagger swagger-console swagger-operator apply-gofmt
swagger-gen: clean-swagger swagger-console apply-gofmt
@echo "Done Generating swagger server code from yaml"
apply-gofmt:
@@ -53,35 +48,40 @@ apply-gofmt:
clean-swagger:
@echo "cleaning"
@rm -rf models
@rm -rf restapi/operations
@rm -rf operatorapi/operations
@rm -rf api/operations
swagger-console:
@echo "Generating swagger server code from yaml"
@swagger generate server -A console --main-package=management --server-package=restapi --exclude-main -P models.Principal -f ./swagger-console.yml -r NOTICE
@swagger generate server -A console --main-package=management --server-package=api --exclude-main -P models.Principal -f ./swagger.yml -r NOTICE
@echo "Generating typescript api"
@npx swagger-typescript-api -p ./swagger.yml -o ./web-app/src/api -n consoleApi.ts
@git restore api/server.go
swagger-operator:
@echo "Generating swagger server code from yaml"
@swagger generate server -A operator --main-package=operator --server-package=operatorapi --exclude-main -P models.Principal -f ./swagger-operator.yml -r NOTICE
assets:
@(if [ -f "${NVM_DIR}/nvm.sh" ]; then \. "${NVM_DIR}/nvm.sh" && nvm install && nvm use && npm install -g yarn ; fi &&\
cd portal-ui; yarn install --prefer-offline; make build-static; yarn prettier --write . --loglevel warn; cd ..)
cd web-app; yarn install --prefer-offline; make build-static; yarn prettier --write . --loglevel warn; cd ..)
test-integration:
@(docker stop pgsqlcontainer || true)
@(docker stop minio || true)
@(docker stop minio2 || true)
@(docker network rm mynet123 || true)
@echo "create docker network to communicate containers MinIO & PostgreSQL"
@(docker network create --subnet=173.18.0.0/29 mynet123)
@echo "docker run with MinIO Version below:"
@echo $(MINIO_VERSION)
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 --net=mynet123 -d --name minio --rm -p 9000:9000 -p 9001:9001 -e MINIO_KMS_SECRET_KEY=my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw= $(MINIO_VERSION) server /data{1...4} --console-address ':9001' && sleep 5)
@(docker run --net=mynet123 --ip=173.18.0.3 --name pgsqlcontainer --rm -p 5432:5432 -e POSTGRES_PASSWORD=password -d postgres && sleep 5)
@echo "MinIO 1"
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 --net=mynet123 -d --name minio --rm -p 9000:9000 -p 9091:9091 -e MINIO_KMS_SECRET_KEY=my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw= $(MINIO_VERSION) server /data{1...4} --console-address ':9091' && sleep 5)
@echo "MinIO 2"
@(docker run -v /data1 -v /data2 -v /data3 -v /data4 --net=mynet123 -d --name minio2 --rm -p 9001:9001 -p 9092:9092 -e MINIO_KMS_SECRET_KEY=my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw= $(MINIO_VERSION) server /data{1...4} --address ':9001' --console-address ':9092' && sleep 5)
@echo "Postgres"
@(docker run --net=mynet123 --ip=173.18.0.4 --name pgsqlcontainer --rm -p 5432:5432 -e POSTGRES_PASSWORD=password -d postgres && sleep 5)
@echo "execute test and get coverage for test-integration:"
@(cd integration && go test -coverpkg=../restapi -c -tags testrunmain . && mkdir -p coverage && export THETARGET=$(TARGET_BUCKET) && echo "THETARGET: ${THETARGET}" && ./integration.test -test.v -test.run "^Test*" -test.coverprofile=coverage/system.out)
@(cd integration && go test -coverpkg=../api -c -tags testrunmain . && mkdir -p coverage && ./integration.test -test.v -test.run "^Test*" -test.coverprofile=coverage/system.out)
@(docker stop pgsqlcontainer)
@(docker stop minio)
@(docker stop minio2)
@(docker network rm mynet123)
test-replication:
@@ -126,7 +126,7 @@ test-replication:
$(MINIO_VERSION) server /data{1...4} \
--address :9002 \
--console-address :6002)
@(cd replication && go test -coverpkg=../restapi -c -tags testrunmain . && mkdir -p coverage && ./replication.test -test.v -test.run "^Test*" -test.coverprofile=coverage/replication.out)
@(cd replication && go test -coverpkg=../api -c -tags testrunmain . && mkdir -p coverage && ./replication.test -test.v -test.run "^Test*" -test.coverprofile=coverage/replication.out)
@(docker stop minio || true)
@(docker stop minio1 || true)
@(docker stop minio2 || true)
@@ -174,88 +174,103 @@ test-sso-integration:
@(docker run --name minio-client --network my-net -dit --entrypoint=/bin/sh minio/mc)
@(docker exec minio-client mc alias set myminio/ http://minio:9000 minio minio123)
@echo "adding policy to Dillon Harper to be able to login:"
@(cd sso-integration && docker cp allaccess.json minio-client:/ && docker exec minio-client mc admin policy add myminio "Dillon Harper" allaccess.json)
@(cd sso-integration && docker cp allaccess.json minio-client:/ && docker exec minio-client mc admin policy create myminio "Dillon Harper" allaccess.json)
@echo "starting bash script"
@(env bash $(PWD)/sso-integration/set-sso.sh)
@echo "add python module"
@(pip3 install bs4)
@echo "Executing the test:"
@(cd sso-integration && go test -coverpkg=../restapi -c -tags testrunmain . && mkdir -p coverage && ./sso-integration.test -test.v -test.run "^Test*" -test.coverprofile=coverage/sso-system.out)
test-operator-integration:
@(echo "Start cd operator-integration && go test:")
@(pwd)
@(cd operator-integration && go test -coverpkg=../operatorapi -c -tags testrunmain . && mkdir -p coverage && ./operator-integration.test -test.v -test.run "^Test*" -test.coverprofile=coverage/operator-api.out)
test-operator:
@(env bash $(PWD)/portal-ui/tests/scripts/operator.sh)
@(docker stop minio)
@(cd sso-integration && go test -coverpkg=../api -c -tags testrunmain . && mkdir -p coverage && ./sso-integration.test -test.v -test.run "^Test*" -test.coverprofile=coverage/sso-system.out)
test-permissions-1:
@(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)/portal-ui/tests/scripts/permissions.sh "portal-ui/tests/permissions-1/")
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-1/")
@(docker stop minio)
test-permissions-2:
@(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)/portal-ui/tests/scripts/permissions.sh "portal-ui/tests/permissions-2/")
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-2/")
@(docker stop minio)
test-permissions-3:
@(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)/portal-ui/tests/scripts/permissions.sh "portal-ui/tests/permissions-3/")
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-3/")
@(docker stop minio)
test-permissions-4:
@(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)/portal-ui/tests/scripts/permissions.sh "portal-ui/tests/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)/portal-ui/tests/scripts/permissions.sh "portal-ui/tests/permissions-5/")
@(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)/portal-ui/tests/scripts/permissions.sh "portal-ui/tests/permissions-6/")
@(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)/portal-ui/tests/scripts/permissions.sh "portal-ui/tests/permissions-7/")
@(env bash $(PWD)/web-app/tests/scripts/permissions.sh "web-app/tests/permissions-7/")
@(docker stop minio)
test-apply-permissions:
@(env bash $(PWD)/portal-ui/tests/scripts/initialize-env.sh)
@(env bash $(PWD)/web-app/tests/scripts/initialize-env.sh)
test-start-docker-minio:
@(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})
initialize-operator:
@echo "Done initializing operator test"
initialize-permissions: test-start-docker-minio test-apply-permissions
@echo "Done initializing permissions test"
cleanup-permissions:
@(env bash $(PWD)/portal-ui/tests/scripts/cleanup-env.sh)
@(env bash $(PWD)/web-app/tests/scripts/cleanup-env.sh)
@(docker stop minio)
initialize-docker-network:
@(docker network create test-network)
test-start-docker-minio-w-redirect-url: initialize-docker-network
@(docker run \
-e MINIO_BROWSER_REDIRECT_URL='http://localhost:8000/console/subpath/' \
-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})
test-start-docker-nginx-w-subpath:
@(docker run \
--network host \
-d --rm \
--add-host=host.docker.internal:host-gateway \
-v ./web-app/tests/subpath-nginx/nginx.conf:/etc/nginx/nginx.conf \
--name test-nginx nginx)
test-initialize-minio-nginx: test-start-docker-minio-w-redirect-url test-start-docker-nginx-w-subpath
cleanup-minio-nginx:
@(docker stop minio test-nginx & docker network rm test-network)
# https://stackoverflow.com/questions/19200235/golang-tests-in-sub-directory
# Note: go test ./... will run tests on the current folder and all subfolders.
# This is needed because tests can be in the folder or sub-folder(s), let's include them all please!.
test:
@echo "execute test and get coverage"
@(cd restapi && mkdir coverage && GO111MODULE=on go test -test.v -coverprofile=coverage/coverage.out)
@(cd api && mkdir -p coverage && GO111MODULE=on go test ./... -test.v -coverprofile=coverage/coverage.out)
test-unit-test-operator:
@echo "execute unit test and get coverage for operatorapi"
@(cd operatorapi && mkdir coverage && GO111MODULE=on go test -test.v -coverprofile=coverage/coverage-unit-test-operatorapi.out)
# https://stackoverflow.com/questions/19200235/golang-tests-in-sub-directory
# Note: go test ./... will run tests on the current folder and all subfolders.
# This is since tests in pkg folder are in subfolders and were not executed.
test-pkg:
@echo "execute test and get coverage"
@(cd pkg && mkdir coverage && GO111MODULE=on go test -test.v -coverprofile=coverage/coverage-pkg.out)
@(cd pkg && mkdir -p coverage && GO111MODULE=on go test ./... -test.v -coverprofile=coverage/coverage-pkg.out)
coverage:
@(GO111MODULE=on go test -v -coverprofile=coverage.out github.com/minio/console/restapi/... && go tool cover -html=coverage.out && open coverage.html)
@(GO111MODULE=on go test -v -coverprofile=coverage.out github.com/minio/console/api/... && go tool cover -html=coverage.out && open coverage.html)
clean:
@echo "Cleaning up all the generated files"
@@ -269,7 +284,5 @@ docker:
release: swagger-gen
@echo "Generating Release: $(RELEASE)"
@make assets
@yq -i e '.spec.template.spec.containers[0].image |= "minio/console:$(RELEASE)"' k8s/operator-console/base/console-deployment.yaml
@yq -i e 'select(.kind == "Deployment").spec.template.spec.containers[0].image |= "minio/console:$(RELEASE)"' k8s/operator-console/standalone/console-deployment.yaml
@git add -u .
@git add portal-ui/build/
@git add web-app/build/

2
NOTICE
View File

@@ -1,5 +1,5 @@
This file is part of MinIO Console Server
Copyright (c) 2022 MinIO, Inc.
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

136
README.md
View File

@@ -4,57 +4,38 @@
A graphical user interface for [MinIO](https://github.com/minio/minio)
| Dashboard | Creating a bucket |
| ------------- | ------------- |
| ![Dashboard](images/pic1.png) | ![Dashboard](images/pic2.png) |
| Object Browser | Dashboard | Creating a bucket |
|------------------------------------|-------------------------------|-------------------------------|
| ![Object Browser](images/pic3.png) | ![Dashboard](images/pic1.png) | ![Dashboard](images/pic2.png) |
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
**Table of Contents**
- [MinIO Console](#minio-console)
- [Install](#install)
- [Binary Releases](#binary-releases)
- [Docker](#docker)
- [Build from source](#build-from-source)
- [Setup](#setup)
- [1. Create a user `console` using `mc`](#1-create-a-user-console-using-mc)
- [2. Create a policy for `console` with admin access to all resources (for testing)](#2-create-a-policy-for-console-with-admin-access-to-all-resources-for-testing)
- [3. Set the policy for the new `console` user](#3-set-the-policy-for-the-new-console-user)
- [Start Console service:](#start-console-service)
- [Start Console service with TLS:](#start-console-service-with-tls)
- [Connect Console to a Minio using TLS and a self-signed certificate](#connect-console-to-a-minio-using-tls-and-a-self-signed-certificate)
- [Install](#install)
- [Build from source](#build-from-source)
- [Setup](#setup)
- [1. Create a user `console` using `mc`](#1-create-a-user-console-using-mc)
- [2. Create a policy for `console` with admin access to all resources (for testing)](#2-create-a-policy-for-console-with-admin-access-to-all-resources-for-testing)
- [3. Set the policy for the new `console` user](#3-set-the-policy-for-the-new-console-user)
- [Start Console service:](#start-console-service)
- [Start Console service with TLS:](#start-console-service-with-tls)
- [Connect Console to a Minio using TLS and a self-signed certificate](#connect-console-to-a-minio-using-tls-and-a-self-signed-certificate)
- [Contribute to console Project](#contribute-to-console-project)
<!-- markdown-toc end -->
## Install
### Binary Releases
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.
| OS | ARCH | Binary |
|:-------:|:-------:|:----------------------------------------------------------------------------------------------------:|
| Linux | amd64 | [linux-amd64](https://github.com/minio/console/releases/latest/download/console-linux-amd64) |
| Linux | arm64 | [linux-arm64](https://github.com/minio/console/releases/latest/download/console-linux-arm64) |
| Linux | ppc64le | [linux-ppc64le](https://github.com/minio/console/releases/latest/download/console-linux-ppc64le) |
| Linux | s390x | [linux-s390x](https://github.com/minio/console/releases/latest/download/console-linux-s390x) |
| Apple | amd64 | [darwin-amd64](https://github.com/minio/console/releases/latest/download/console-darwin-amd64) |
| Windows | amd64 | [windows-amd64](https://github.com/minio/console/releases/latest/download/console-windows-amd64.exe) |
You can also verify the binary with [minisign](https://jedisct1.github.io/minisign/) by downloading the corresponding [`.minisig`](https://github.com/minio/console/releases/latest) signature file. Then run:
```
minisign -Vm console-<OS>-<ARCH> -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
```
### Docker
Pull the latest release via:
```
docker pull minio/console
```
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.18
> Minimum version required is go1.21
```
go install github.com/minio/console/cmd/console@latest
@@ -103,60 +84,64 @@ EOF
```
```sh
mc admin policy add myminio/ consoleAdmin admin.json
mc admin policy create myminio/ consoleAdmin admin.json
```
### 3. Set the policy for the new `console` user
```sh
mc admin policy set myminio consoleAdmin user=console
mc admin policy attach myminio consoleAdmin --user=console
```
> NOTE: Additionally, you can create policies to limit the privileges for other `console` users, for example, if you want the user to only have access to dashboard, buckets, notifications and watch page, the policy should look like this:
> NOTE: Additionally, you can create policies to limit the privileges for other `console` users, for example, if you
> want the user to only have access to dashboard, buckets, notifications and watch page, the policy should look like
> this:
```json
{
"Version": "2012-10-17",
"Statement": [{
"Action": [
"admin:ServerInfo"
],
"Effect": "Allow",
"Sid": ""
},
{
"Action": [
"s3:ListenBucketNotification",
"s3:PutBucketNotification",
"s3:GetBucketNotification",
"s3:ListMultipartUploadParts",
"s3:ListBucketMultipartUploads",
"s3:ListBucket",
"s3:HeadBucket",
"s3:GetObject",
"s3:GetBucketLocation",
"s3:AbortMultipartUpload",
"s3:CreateBucket",
"s3:PutObject",
"s3:DeleteObject",
"s3:DeleteBucket",
"s3:PutBucketPolicy",
"s3:DeleteBucketPolicy",
"s3:GetBucketPolicy"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::*"
],
"Sid": ""
}
]
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"admin:ServerInfo"
],
"Effect": "Allow",
"Sid": ""
},
{
"Action": [
"s3:ListenBucketNotification",
"s3:PutBucketNotification",
"s3:GetBucketNotification",
"s3:ListMultipartUploadParts",
"s3:ListBucketMultipartUploads",
"s3:ListBucket",
"s3:HeadBucket",
"s3:GetObject",
"s3:GetBucketLocation",
"s3:AbortMultipartUpload",
"s3:CreateBucket",
"s3:PutObject",
"s3:DeleteObject",
"s3:DeleteBucket",
"s3:PutBucketPolicy",
"s3:DeleteBucketPolicy",
"s3:GetBucketPolicy"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::*"
],
"Sid": ""
}
]
}
```
## Start Console service:
Before running console service, following environment settings must be supplied
```sh
# Salt to encrypt JWT payload
export CONSOLE_PBKDF_PASSPHRASE=SECRET
@@ -169,6 +154,7 @@ export CONSOLE_MINIO_SERVER=http://localhost:9000
```
Now start the console service.
```
./console server
2021-01-19 02:36:08.893735 I | 2021/01/19 02:36:08 server.go:129: Serving console at http://localhost:9090
@@ -189,6 +175,7 @@ Copy your `public.crt` and `private.key` to `~/.console/certs`, then:
For advanced users, `console` has support for multiple certificates to service clients through multiple domains.
Following tree structure is expected for supporting multiple domains:
```sh
certs/
@@ -219,4 +206,5 @@ export CONSOLE_MINIO_SERVER=https://localhost:9000
You can verify that the apis work by doing the request on `localhost:9090/api/v1/...`
# Contribute to console Project
Please follow console [Contributor's Guide](https://github.com/minio/console/blob/master/CONTRIBUTING.md)

View File

@@ -18,9 +18,10 @@ you need access credentials for a successful exploit).
If you have not received a reply to your email within 48 hours or you have not heard from the security team
for the past five days please contact the security team directly:
- Primary security coordinator: lenin@min.io
- Secondary coordinator: security@min.io
- If you receive no response: dev@min.io
- Primary security coordinator: daniel@min.io
- Secondary coordinator: security@min.io
- If you receive no response: dev@min.io
### Disclosure Process

View File

@@ -14,16 +14,16 @@
// 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 restapi
package api
import (
"context"
systemApi "github.com/minio/console/restapi/operations/system"
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"
"github.com/minio/console/restapi/operations"
)
func registerAdminArnsHandlers(api *operations.ConsoleAPI) {
@@ -31,7 +31,7 @@ func registerAdminArnsHandlers(api *operations.ConsoleAPI) {
api.SystemArnListHandler = systemApi.ArnListHandlerFunc(func(params systemApi.ArnListParams, session *models.Principal) middleware.Responder {
arnsResp, err := getArnsResponse(session, params)
if err != nil {
return systemApi.NewArnListDefault(int(err.Code)).WithPayload(err)
return systemApi.NewArnListDefault(err.Code).WithPayload(err.APIError)
}
return systemApi.NewArnListOK().WithPayload(arnsResp)
})
@@ -50,10 +50,10 @@ func getArns(ctx context.Context, client MinioAdmin) (*models.ArnsResponse, erro
}
// getArnsResponse returns a list of active arns in the instance
func getArnsResponse(session *models.Principal, params systemApi.ArnListParams) (*models.ArnsResponse, *models.Error) {
func getArnsResponse(session *models.Principal, params systemApi.ArnListParams) (*models.ArnsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -25,21 +25,21 @@ import (
"testing"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations/system"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations/system"
"github.com/go-openapi/loads"
"github.com/minio/console/restapi/operations"
"github.com/minio/madmin-go"
"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{}
adminClient := AdminClientMock{}
// Test-1 : getArns() returns proper arn list
minioServerInfoMock = func(ctx context.Context) (madmin.InfoMessage, error) {
MinioServerInfoMock = func(_ context.Context) (madmin.InfoMessage, error) {
return madmin.InfoMessage{
SQSARN: []string{"uno"},
}, nil
@@ -54,7 +54,7 @@ func TestArnsList(t *testing.T) {
assert.Nil(err, "Error should have been nil")
// Test-2 : getArns(ctx) fails for whatever reason
minioServerInfoMock = func(ctx context.Context) (madmin.InfoMessage, error) {
MinioServerInfoMock = func(_ context.Context) (madmin.InfoMessage, error) {
return madmin.InfoMessage{}, errors.New("some reason")
}

402
api/admin_client_mock.go Normal file
View File

@@ -0,0 +1,402 @@
// 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"
"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 *iampolicy.Policy, 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
}
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 *iampolicy.Policy, 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)
}

316
api/admin_config.go Normal file
View File

@@ -0,0 +1,316 @@
// 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

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -28,55 +28,21 @@ import (
"github.com/stretchr/testify/assert"
"github.com/minio/console/models"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
)
const (
NotifyPostgresSubSys = "notify_postgres"
PostgresFormat = "format"
PostgresConnectionString = "connection_string"
PostgresTable = "table"
PostgresHost = "host"
PostgresPort = "port"
PostgresUsername = "username"
PostgresPassword = "password"
PostgresDatabase = "database"
PostgresQueueDir = "queue_dir"
PostgresQueueLimit = "queue_limit"
PostgresMaxOpenConnections = "max_open_connections"
NotifyPostgresSubSys = "notify_postgres"
PostgresFormat = "format"
PostgresConnectionString = "connection_string"
PostgresTable = "table"
PostgresQueueDir = "queue_dir"
PostgresQueueLimit = "queue_limit"
)
// assigning mock at runtime instead of compile time
var minioHelpConfigKVMock func(subSys, key string, envOnly bool) (madmin.Help, error)
var (
minioGetConfigKVMock func(key string) ([]byte, error)
minioSetConfigKVMock func(kv string) (restart bool, err error)
minioDelConfigKVMock func(name string) (err error)
)
// mock function helpConfigKV()
func (ac adminClientMock) helpConfigKV(ctx context.Context, subSys, key string, envOnly bool) (madmin.Help, error) {
return minioHelpConfigKVMock(subSys, key, envOnly)
}
// mock function getConfigKV()
func (ac adminClientMock) getConfigKV(ctx context.Context, name string) ([]byte, error) {
return minioGetConfigKVMock(name)
}
// mock function setConfigKV()
func (ac adminClientMock) setConfigKV(ctx context.Context, kv string) (restart bool, err error) {
return minioSetConfigKVMock(kv)
}
func (ac adminClientMock) delConfigKV(ctx context.Context, name string) (err error) {
return minioDelConfigKVMock(name)
}
func TestListConfig(t *testing.T) {
assert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
function := "listConfig()"
// Test-1 : listConfig() get list of two configurations and ensure is output correctly
configListMock := []madmin.HelpKV{
@@ -97,7 +63,7 @@ func TestListConfig(t *testing.T) {
}
expectedKeysDesc := mockConfigList.KeysHelp
// mock function response from listConfig()
minioHelpConfigKVMock = func(subSys, key string, envOnly bool) (madmin.Help, error) {
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return mockConfigList, nil
}
configList, err := listConfig(adminClient)
@@ -114,7 +80,7 @@ func TestListConfig(t *testing.T) {
// Test-2 : listConfig() Return error and see that the error is handled correctly and returned
// mock function response from listConfig()
minioHelpConfigKVMock = func(subSys, key string, envOnly bool) (madmin.Help, error) {
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return madmin.Help{}, errors.New("error")
}
_, err = listConfig(adminClient)
@@ -125,10 +91,10 @@ func TestListConfig(t *testing.T) {
func TestSetConfig(t *testing.T) {
assert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
function := "setConfig()"
// mock function response from setConfig()
minioSetConfigKVMock = func(kv string) (restart bool, err error) {
minioSetConfigKVMock = func(_ string) (restart bool, err error) {
return false, nil
}
configName := "notify_postgres"
@@ -153,7 +119,7 @@ func TestSetConfig(t *testing.T) {
assert.Equal(restart, false)
// Test-2 : setConfig() returns error, handle properly
minioSetConfigKVMock = func(kv string) (restart bool, err error) {
minioSetConfigKVMock = func(_ string) (restart bool, err error) {
return false, errors.New("error")
}
restart, err = setConfig(ctx, adminClient, &configName, kvs)
@@ -163,7 +129,7 @@ func TestSetConfig(t *testing.T) {
assert.Equal(restart, false)
// Test-4 : setConfig() set config, need restart
minioSetConfigKVMock = func(kv string) (restart bool, err error) {
minioSetConfigKVMock = func(_ string) (restart bool, err error) {
return true, nil
}
restart, err = setConfig(ctx, adminClient, &configName, kvs)
@@ -175,10 +141,10 @@ func TestSetConfig(t *testing.T) {
func TestDelConfig(t *testing.T) {
assert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
function := "resetConfig()"
// mock function response from setConfig()
minioDelConfigKVMock = func(name string) (err error) {
minioDelConfigKVMock = func(_ string) (err error) {
return nil
}
configName := "region"
@@ -192,7 +158,7 @@ func TestDelConfig(t *testing.T) {
}
// Test-2 : resetConfig() returns error, handle properly
minioDelConfigKVMock = func(name string) (err error) {
minioDelConfigKVMock = func(_ string) (err error) {
return errors.New("error")
}
@@ -254,7 +220,7 @@ func Test_buildConfig(t *testing.T) {
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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)
}
@@ -264,7 +230,7 @@ func Test_buildConfig(t *testing.T) {
func Test_setConfigWithARN(t *testing.T) {
assert := assert.New(t)
client := adminClientMock{}
client := AdminClientMock{}
type args struct {
ctx context.Context
@@ -294,7 +260,7 @@ func Test_setConfigWithARN(t *testing.T) {
},
arn: "1",
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
wantErr: false,
@@ -314,7 +280,7 @@ func Test_setConfigWithARN(t *testing.T) {
},
arn: "1",
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return true, nil
},
wantErr: false,
@@ -334,7 +300,7 @@ func Test_setConfigWithARN(t *testing.T) {
},
arn: "",
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
wantErr: false,
@@ -354,7 +320,7 @@ func Test_setConfigWithARN(t *testing.T) {
},
arn: "",
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, errors.New("error")
},
wantErr: true,
@@ -362,7 +328,7 @@ func Test_setConfigWithARN(t *testing.T) {
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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)
@@ -375,7 +341,7 @@ func Test_setConfigWithARN(t *testing.T) {
}
func Test_getConfig(t *testing.T) {
client := adminClientMock{}
client := AdminClientMock{}
type args struct {
client MinioAdmin
name string
@@ -395,7 +361,7 @@ func Test_getConfig(t *testing.T) {
},
mock: func() {
// mock function response from getConfig()
minioGetConfigKVMock = func(key string) ([]byte, error) {
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
}
@@ -441,7 +407,7 @@ func Test_getConfig(t *testing.T) {
KeysHelp: configListMock,
}
// mock function response from listConfig()
minioHelpConfigKVMock = func(subSys, key string, envOnly bool) (madmin.Help, error) {
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return mockConfigList, nil
}
},
@@ -469,7 +435,7 @@ func Test_getConfig(t *testing.T) {
},
mock: func() {
// mock function response from getConfig()
minioGetConfigKVMock = func(key string) ([]byte, error) {
minioGetConfigKVMock = func(_ string) ([]byte, error) {
return []byte(`notify_postgres:_`), nil
}
@@ -515,7 +481,7 @@ func Test_getConfig(t *testing.T) {
KeysHelp: configListMock,
}
// mock function response from listConfig()
minioHelpConfigKVMock = func(subSys, key string, envOnly bool) (madmin.Help, error) {
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return mockConfigList, nil
}
},
@@ -530,7 +496,7 @@ func Test_getConfig(t *testing.T) {
},
mock: func() {
// mock function response from getConfig()
minioGetConfigKVMock = func(key string) ([]byte, error) {
minioGetConfigKVMock = func(_ string) ([]byte, error) {
x := make(map[string]string)
x["x"] = "x"
j, _ := json.Marshal(x)
@@ -579,7 +545,7 @@ func Test_getConfig(t *testing.T) {
KeysHelp: configListMock,
}
// mock function response from listConfig()
minioHelpConfigKVMock = func(subSys, key string, envOnly bool) (madmin.Help, error) {
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return mockConfigList, nil
}
},
@@ -594,13 +560,13 @@ func Test_getConfig(t *testing.T) {
},
mock: func() {
// mock function response from getConfig()
minioGetConfigKVMock = func(key string) ([]byte, error) {
minioGetConfigKVMock = func(_ string) ([]byte, error) {
return nil, errors.New("invalid config")
}
mockConfigList := madmin.Help{}
// mock function response from listConfig()
minioHelpConfigKVMock = func(subSys, key string, envOnly bool) (madmin.Help, error) {
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return mockConfigList, nil
}
},
@@ -615,11 +581,11 @@ func Test_getConfig(t *testing.T) {
},
mock: func() {
// mock function response from getConfig()
minioGetConfigKVMock = func(key string) ([]byte, error) {
minioGetConfigKVMock = func(_ string) ([]byte, error) {
return nil, errors.New("invalid config")
}
// mock function response from listConfig()
minioHelpConfigKVMock = func(subSys, key string, envOnly bool) (madmin.Help, error) {
minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
return madmin.Help{}, errors.New("no help")
}
},
@@ -629,7 +595,7 @@ func Test_getConfig(t *testing.T) {
}
for _, tt := range tests {
tt.mock()
t.Run(tt.name, func(t *testing.T) {
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)

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -22,8 +22,8 @@ import (
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
"github.com/minio/websocket"
)
const logTimeFormat string = "15:04:05 MST 01/02/2006"

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -22,21 +22,13 @@ import (
"fmt"
"testing"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
)
// assigning mock at runtime instead of compile time
var minioGetLogsMock func(ctx context.Context, node string, lineCnt int, logKind string) <-chan madmin.LogInfo
// mock function of listPolicies()
func (ac adminClientMock) getLogs(ctx context.Context, node string, lineCnt int, logKind string) <-chan madmin.LogInfo {
return minioGetLogsMock(ctx, node, lineCnt, logKind)
}
func TestAdminConsoleLog(t *testing.T) {
assert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
mockWSConn := mockConn{}
function := "startConsoleLog(ctx, )"
ctx, cancel := context.WithCancel(context.Background())
@@ -48,7 +40,7 @@ func TestAdminConsoleLog(t *testing.T) {
// Test-1: Serve Console with no errors until Console finishes sending
// define mock function behavior for minio server Console
minioGetLogsMock = func(ctx context.Context, node string, lineCnt int, logKind string) <-chan madmin.LogInfo {
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) {
@@ -66,7 +58,7 @@ func TestAdminConsoleLog(t *testing.T) {
}
writesCount := 1
// mock connection WriteMessage() no error
connWriteMessageMock = func(messageType int, data []byte) error {
connWriteMessageMock = func(_ int, data []byte) error {
// emulate that receiver gets the message written
var t madmin.LogInfo
_ = json.Unmarshal(data, &t)
@@ -90,7 +82,7 @@ func TestAdminConsoleLog(t *testing.T) {
}
// Test-2: if error happens while writing, return error
connWriteMessageMock = func(messageType int, data []byte) 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) {
@@ -99,7 +91,7 @@ func TestAdminConsoleLog(t *testing.T) {
// Test-3: error happens on GetLogs Minio, Console should stop
// and error shall be returned.
minioGetLogsMock = func(ctx context.Context, node string, lineCnt int, logKind string) <-chan madmin.LogInfo {
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) {
@@ -116,7 +108,7 @@ func TestAdminConsoleLog(t *testing.T) {
}(ch)
return ch
}
connWriteMessageMock = func(messageType int, data []byte) error {
connWriteMessageMock = func(_ int, _ []byte) error {
return nil
}
if err := startConsoleLog(ctx, mockWSConn, adminClient, LogRequest{node: "", logType: "all"}); assert.Error(err) {

View File

@@ -14,18 +14,18 @@
// 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 restapi
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/console/restapi/operations"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
groupApi "github.com/minio/console/restapi/operations/group"
groupApi "github.com/minio/console/api/operations/group"
"github.com/minio/console/models"
)
@@ -35,7 +35,7 @@ func registerGroupsHandlers(api *operations.ConsoleAPI) {
api.GroupListGroupsHandler = groupApi.ListGroupsHandlerFunc(func(params groupApi.ListGroupsParams, session *models.Principal) middleware.Responder {
listGroupsResponse, err := getListGroupsResponse(session, params)
if err != nil {
return groupApi.NewListGroupsDefault(int(err.Code)).WithPayload(err)
return groupApi.NewListGroupsDefault(err.Code).WithPayload(err.APIError)
}
return groupApi.NewListGroupsOK().WithPayload(listGroupsResponse)
})
@@ -43,21 +43,21 @@ func registerGroupsHandlers(api *operations.ConsoleAPI) {
api.GroupGroupInfoHandler = groupApi.GroupInfoHandlerFunc(func(params groupApi.GroupInfoParams, session *models.Principal) middleware.Responder {
groupInfo, err := getGroupInfoResponse(session, params)
if err != nil {
return groupApi.NewGroupInfoDefault(int(err.Code)).WithPayload(err)
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(int(err.Code)).WithPayload(err)
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(int(err.Code)).WithPayload(err)
return groupApi.NewRemoveGroupDefault(err.Code).WithPayload(err.APIError)
}
return groupApi.NewRemoveGroupNoContent()
})
@@ -65,17 +65,17 @@ func registerGroupsHandlers(api *operations.ConsoleAPI) {
api.GroupUpdateGroupHandler = groupApi.UpdateGroupHandlerFunc(func(params groupApi.UpdateGroupParams, session *models.Principal) middleware.Responder {
groupUpdateResp, err := getUpdateGroupResponse(session, params)
if err != nil {
return groupApi.NewUpdateGroupDefault(int(err.Code)).WithPayload(err)
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, *models.Error) {
func getListGroupsResponse(session *models.Principal, params groupApi.ListGroupsParams) (*models.ListGroupsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -107,10 +107,10 @@ func groupInfo(ctx context.Context, client MinioAdmin, group string) (*madmin.Gr
}
// getGroupInfoResponse performs groupInfo() and serializes it to the handler's output
func getGroupInfoResponse(session *models.Principal, params groupApi.GroupInfoParams) (*models.Group, *models.Error) {
func getGroupInfoResponse(session *models.Principal, params groupApi.GroupInfoParams) (*models.Group, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -153,7 +153,7 @@ func addGroup(ctx context.Context, client MinioAdmin, group string, members []st
}
// getAddGroupResponse performs addGroup() and serializes it to the handler's output
func getAddGroupResponse(session *models.Principal, params groupApi.AddGroupParams) *models.Error {
func getAddGroupResponse(session *models.Principal, params groupApi.AddGroupParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// AddGroup request needed to proceed
@@ -161,7 +161,7 @@ func getAddGroupResponse(session *models.Principal, params groupApi.AddGroupPara
return ErrorWithContext(ctx, ErrGroupBodyNotInRequest)
}
groupRequest := params.Body
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -198,13 +198,13 @@ func removeGroup(ctx context.Context, client MinioAdmin, group string) error {
}
// getRemoveGroupResponse performs removeGroup() and serializes it to the handler's output
func getRemoveGroupResponse(session *models.Principal, params groupApi.RemoveGroupParams) *models.Error {
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(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -282,7 +282,7 @@ func setGroupStatus(ctx context.Context, client MinioAdmin, group, status string
// 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, *models.Error) {
func getUpdateGroupResponse(session *models.Principal, params groupApi.UpdateGroupParams) (*models.Group, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params.Name == "" {
@@ -298,7 +298,7 @@ func getUpdateGroupResponse(session *models.Principal, params groupApi.UpdateGro
return nil, ErrorWithContext(ctx, err)
}
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -24,42 +24,13 @@ import (
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
)
// assigning mock at runtime instead of compile time
var minioListGroupsMock func() ([]string, error)
var (
minioUpdateGroupMembersMock func(madmin.GroupAddRemove) error
minioGetGroupDescriptionMock func(group string) (*madmin.GroupDesc, error)
minioSetGroupStatusMock func(group string, status madmin.GroupStatus) error
)
// mock function of listGroups()
func (ac adminClientMock) listGroups(ctx context.Context) ([]string, error) {
return minioListGroupsMock()
}
// mock function of updateGroupMembers()
func (ac adminClientMock) updateGroupMembers(ctx context.Context, req madmin.GroupAddRemove) error {
return minioUpdateGroupMembersMock(req)
}
// mock function of getGroupDescription()
func (ac adminClientMock) getGroupDescription(ctx context.Context, group string) (*madmin.GroupDesc, error) {
return minioGetGroupDescriptionMock(group)
}
// mock function setGroupStatus()
func (ac adminClientMock) setGroupStatus(ctx context.Context, group string, status madmin.GroupStatus) error {
return minioSetGroupStatusMock(group, status)
}
func TestListGroups(t *testing.T) {
assert := assert.New(t)
adminClient := adminClientMock{}
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()
@@ -95,7 +66,7 @@ func TestListGroups(t *testing.T) {
func TestAddGroup(t *testing.T) {
assert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : addGroup() add a new group with two members
@@ -122,7 +93,7 @@ func TestAddGroup(t *testing.T) {
func TestRemoveGroup(t *testing.T) {
assert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : removeGroup() remove group assume it has no members
@@ -147,7 +118,7 @@ func TestRemoveGroup(t *testing.T) {
func TestGroupInfo(t *testing.T) {
assert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : groupInfo() get group info
@@ -159,7 +130,7 @@ func TestGroupInfo(t *testing.T) {
Status: "enabled",
}
// mock function response from updateGroupMembers()
minioGetGroupDescriptionMock = func(group string) (*madmin.GroupDesc, error) {
minioGetGroupDescriptionMock = func(_ string) (*madmin.GroupDesc, error) {
return mockResponse, nil
}
function := "groupInfo()"
@@ -173,7 +144,7 @@ func TestGroupInfo(t *testing.T) {
assert.Equal("enabled", info.Status)
// Test-2 : groupInfo() Return error and see that the error is handled correctly and returned
minioGetGroupDescriptionMock = func(group string) (*madmin.GroupDesc, error) {
minioGetGroupDescriptionMock = func(_ string) (*madmin.GroupDesc, error) {
return nil, errors.New("error")
}
_, err = groupInfo(ctx, adminClient, groupName)
@@ -184,7 +155,7 @@ func TestGroupInfo(t *testing.T) {
func TestUpdateGroup(t *testing.T) {
assert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1 : addOrDeleteMembers() update group members add user3 and delete user2
@@ -255,7 +226,7 @@ func TestUpdateGroup(t *testing.T) {
// the function twice but the second time returned an error
is2ndRunGroupInfo := false
// mock function response from updateGroupMembers()
minioGetGroupDescriptionMock = func(group string) (*madmin.GroupDesc, error) {
minioGetGroupDescriptionMock = func(_ string) (*madmin.GroupDesc, error) {
if is2ndRunGroupInfo {
return mockResponseAfterUpdate, nil
}
@@ -265,7 +236,7 @@ func TestUpdateGroup(t *testing.T) {
minioUpdateGroupMembersMock = func(madmin.GroupAddRemove) error {
return nil
}
minioSetGroupStatusMock = func(group string, status madmin.GroupStatus) error {
minioSetGroupStatusMock = func(_ string, _ madmin.GroupStatus) error {
return nil
}
groupUpdated, err := groupUpdate(ctx, adminClient, groupName, expectedGroupUpdate)
@@ -280,14 +251,14 @@ func TestUpdateGroup(t *testing.T) {
func TestSetGroupStatus(t *testing.T) {
assert := assert.New(t)
adminClient := adminClientMock{}
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(group string, status madmin.GroupStatus) error {
minioSetGroupStatusMock = func(_ string, _ madmin.GroupStatus) error {
return nil
}
if err := setGroupStatus(ctx, adminClient, groupName, expectedStatus); err != nil {
@@ -295,7 +266,7 @@ func TestSetGroupStatus(t *testing.T) {
}
// Test-2: setGroupStatus() update valid enabled status
expectedStatus = "enabled"
minioSetGroupStatusMock = func(group string, status madmin.GroupStatus) error {
minioSetGroupStatusMock = func(_ string, _ madmin.GroupStatus) error {
return nil
}
if err := setGroupStatus(ctx, adminClient, groupName, expectedStatus); err != nil {
@@ -303,7 +274,7 @@ func TestSetGroupStatus(t *testing.T) {
}
// Test-3: setGroupStatus() update invalid status, should send error
expectedStatus = "invalid"
minioSetGroupStatusMock = func(group string, status madmin.GroupStatus) error {
minioSetGroupStatusMock = func(_ string, _ madmin.GroupStatus) error {
return nil
}
if err := setGroupStatus(ctx, adminClient, groupName, expectedStatus); assert.Error(err) {
@@ -311,7 +282,7 @@ func TestSetGroupStatus(t *testing.T) {
}
// Test-4: setGroupStatus() handler error correctly
expectedStatus = "enabled"
minioSetGroupStatusMock = func(group string, status madmin.GroupStatus) error {
minioSetGroupStatusMock = func(_ string, _ madmin.GroupStatus) error {
return errors.New("error")
}
if err := setGroupStatus(ctx, adminClient, groupName, expectedStatus); assert.Error(err) {

177
api/admin_health_info.go Normal file
View File

@@ -0,0 +1,177 @@
// 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"
b64 "encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/minio/console/pkg/utils"
"github.com/klauspost/compress/gzip"
xhttp "github.com/minio/console/pkg/http"
subnet "github.com/minio/console/pkg/subnet"
"github.com/minio/madmin-go/v3"
"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 := tarGZ(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())
subnetResp, err := sendHealthInfoToSubnet(ctx, healthInfo, client)
report := messageReport{
Encoded: encodedDiag,
ServerHealthInfo: healthInfo,
SubnetResponse: subnetResp,
}
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)
}
// compress and tar MinIO diagnostics output
func tarGZ(healthInfo interface{}, version string) ([]byte, error) {
buffer := bytes.NewBuffer(nil)
gzWriter := gzip.NewWriter(buffer)
enc := json.NewEncoder(gzWriter)
header := struct {
Version string `json:"version"`
}{Version: version}
if err := enc.Encode(header); err != nil {
return nil, err
}
if err := enc.Encode(healthInfo); err != nil {
return nil, err
}
err := gzWriter.Close()
if err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
// 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 sendHealthInfoToSubnet(ctx context.Context, healthInfo interface{}, client MinioAdmin) (string, error) {
filename := fmt.Sprintf("health_%d.json", time.Now().Unix())
clientIP := utils.ClientIPFromContext(ctx)
subnetUploadURL := subnet.UploadURL("health", filename)
subnetHTTPClient := &xhttp.Client{Client: GetConsoleHTTPClient("", clientIP)}
subnetTokenConfig, e := GetSubnetKeyFromMinIOConfig(ctx, client)
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
}
}
headers := subnet.UploadAuthHeaders(apiKey)
uploadInfo, formDataType, e := subnet.ProcessUploadInfo(healthInfo, "health", filename)
if e != nil {
return "", e
}
resp, e := subnet.UploadFileToSubnet(uploadInfo, subnetHTTPClient, subnetUploadURL, headers, formDataType)
if e != nil {
return "", e
}
type SubnetResponse struct {
ClusterURL string `json:"cluster_url,omitempty"`
}
var subnetResp SubnetResponse
e = json.Unmarshal([]byte(resp), &subnetResp)
if e != nil {
return "", e
}
if len(subnetResp.ClusterURL) != 0 {
subnetClusterURL := strings.ReplaceAll(subnetResp.ClusterURL, "%2f", "/")
return subnetClusterURL, nil
}
return "", ErrSubnetUploadFail
}

View File

@@ -0,0 +1,147 @@
// 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
}
})
}
}

290
api/admin_idp.go Normal file
View File

@@ -0,0 +1,290 @@
// 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
}

319
api/admin_idp_test.go Normal file
View File

@@ -0,0 +1,319 @@
// 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())
}
}

1192
api/admin_info.go Normal file

File diff suppressed because it is too large Load Diff

152
api/admin_info_test.go Normal file
View File

@@ -0,0 +1,152 @@
// 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

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"encoding/base64"
@@ -26,10 +26,10 @@ import (
"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/console/restapi/operations"
inspectApi "github.com/minio/console/restapi/operations/inspect"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
"github.com/secure-io/sio-go"
)
@@ -45,16 +45,16 @@ func registerInspectHandler(api *operations.ConsoleAPI) {
k, r, err := getInspectResult(principal, &params)
if err != nil {
return inspectApi.NewInspectDefault(int(err.Code)).WithPayload(err)
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, *models.Error) {
func getInspectResult(session *models.Principal, params *inspectApi.InspectParams) ([]byte, io.ReadCloser, *CodedAPIError) {
ctx := params.HTTPRequest.Context()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, nil, ErrorWithContext(ctx, err)
}
@@ -120,7 +120,7 @@ func processInspectResponse(params *inspectApi.InspectParams, k []byte, r io.Rea
_, err := io.Copy(w, r)
if err != nil {
LogError("Unable to write all the data: %v", err)
LogError("unable to write all the data: %v", err)
}
}
}

View File

@@ -15,17 +15,18 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package restapi
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/console/restapi/operations"
kmsAPI "github.com/minio/console/restapi/operations/k_m_s"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
)
func registerKMSHandlers(api *operations.ConsoleAPI) {
@@ -39,7 +40,7 @@ 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(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSStatusDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSStatusOK().WithPayload(resp)
})
@@ -47,7 +48,7 @@ func registerKMSStatusHandlers(api *operations.ConsoleAPI) {
api.KmsKMSMetricsHandler = kmsAPI.KMSMetricsHandlerFunc(func(params kmsAPI.KMSMetricsParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSMetricsResponse(session, params)
if err != nil {
return kmsAPI.NewKMSMetricsDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSMetricsDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSMetricsOK().WithPayload(resp)
})
@@ -55,7 +56,7 @@ func registerKMSStatusHandlers(api *operations.ConsoleAPI) {
api.KmsKMSAPIsHandler = kmsAPI.KMSAPIsHandlerFunc(func(params kmsAPI.KMSAPIsParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSAPIsResponse(session, params)
if err != nil {
return kmsAPI.NewKMSAPIsDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSAPIsDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSAPIsOK().WithPayload(resp)
})
@@ -63,23 +64,23 @@ func registerKMSStatusHandlers(api *operations.ConsoleAPI) {
api.KmsKMSVersionHandler = kmsAPI.KMSVersionHandlerFunc(func(params kmsAPI.KMSVersionParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSVersionResponse(session, params)
if err != nil {
return kmsAPI.NewKMSVersionDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSVersionDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSVersionOK().WithPayload(resp)
})
}
func GetKMSStatusResponse(session *models.Principal, params kmsAPI.KMSStatusParams) (*models.KmsStatusResponse, *models.Error) {
func GetKMSStatusResponse(session *models.Principal, params kmsAPI.KMSStatusParams) (*models.KmsStatusResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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, *models.Error) {
func kmsStatus(ctx context.Context, minioClient MinioAdmin) (*models.KmsStatusResponse, *CodedAPIError) {
st, err := minioClient.kmsStatus(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
@@ -98,17 +99,17 @@ func parseStatusEndpoints(endpoints map[string]madmin.ItemState) (kmsEndpoints [
return kmsEndpoints
}
func GetKMSMetricsResponse(session *models.Principal, params kmsAPI.KMSMetricsParams) (*models.KmsMetricsResponse, *models.Error) {
func GetKMSMetricsResponse(session *models.Principal, params kmsAPI.KMSMetricsParams) (*models.KmsMetricsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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, *models.Error) {
func kmsMetrics(ctx context.Context, minioClient MinioAdmin) (*models.KmsMetricsResponse, *CodedAPIError) {
metrics, err := minioClient.kmsMetrics(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
@@ -120,7 +121,7 @@ func kmsMetrics(ctx context.Context, minioClient MinioAdmin) (*models.KmsMetrics
RequestActive: &metrics.RequestActive,
AuditEvents: &metrics.AuditEvents,
ErrorEvents: &metrics.ErrorEvents,
LatencyHistogram: nil,
LatencyHistogram: parseHistogram(metrics.LatencyHistogram),
Uptime: &metrics.UpTime,
Cpus: &metrics.CPUs,
UsableCPUs: &metrics.UsableCPUs,
@@ -131,17 +132,28 @@ func kmsMetrics(ctx context.Context, minioClient MinioAdmin) (*models.KmsMetrics
}, nil
}
func GetKMSAPIsResponse(session *models.Principal, params kmsAPI.KMSAPIsParams) (*models.KmsAPIsResponse, *models.Error) {
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(session)
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, *models.Error) {
func kmsAPIs(ctx context.Context, minioClient MinioAdmin) (*models.KmsAPIsResponse, *CodedAPIError) {
apis, err := minioClient.kmsAPIs(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
@@ -163,17 +175,17 @@ func parseApis(apis []madmin.KMSAPI) (data []*models.KmsAPI) {
return data
}
func GetKMSVersionResponse(session *models.Principal, params kmsAPI.KMSVersionParams) (*models.KmsVersionResponse, *models.Error) {
func GetKMSVersionResponse(session *models.Principal, params kmsAPI.KMSVersionParams) (*models.KmsVersionResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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, *models.Error) {
func kmsVersion(ctx context.Context, minioClient MinioAdmin) (*models.KmsVersionResponse, *CodedAPIError) {
version, err := minioClient.kmsVersion(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
@@ -187,7 +199,7 @@ 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(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSCreateKeyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSCreateKeyCreated()
})
@@ -195,7 +207,7 @@ func registerKMSKeyHandlers(api *operations.ConsoleAPI) {
api.KmsKMSImportKeyHandler = kmsAPI.KMSImportKeyHandlerFunc(func(params kmsAPI.KMSImportKeyParams, session *models.Principal) middleware.Responder {
err := GetKMSImportKeyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSImportKeyDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSImportKeyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSImportKeyCreated()
})
@@ -203,7 +215,7 @@ func registerKMSKeyHandlers(api *operations.ConsoleAPI) {
api.KmsKMSListKeysHandler = kmsAPI.KMSListKeysHandlerFunc(func(params kmsAPI.KMSListKeysParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSListKeysResponse(session, params)
if err != nil {
return kmsAPI.NewKMSListKeysDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSListKeysDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSListKeysOK().WithPayload(resp)
})
@@ -211,7 +223,7 @@ func registerKMSKeyHandlers(api *operations.ConsoleAPI) {
api.KmsKMSKeyStatusHandler = kmsAPI.KMSKeyStatusHandlerFunc(func(params kmsAPI.KMSKeyStatusParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSKeyStatusResponse(session, params)
if err != nil {
return kmsAPI.NewKMSKeyStatusDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSKeyStatusDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSKeyStatusOK().WithPayload(resp)
})
@@ -219,33 +231,33 @@ func registerKMSKeyHandlers(api *operations.ConsoleAPI) {
api.KmsKMSDeleteKeyHandler = kmsAPI.KMSDeleteKeyHandlerFunc(func(params kmsAPI.KMSDeleteKeyParams, session *models.Principal) middleware.Responder {
err := GetKMSDeleteKeyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSDeleteKeyDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSDeleteKeyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSDeleteKeyOK()
})
}
func GetKMSCreateKeyResponse(session *models.Principal, params kmsAPI.KMSCreateKeyParams) *models.Error {
func GetKMSCreateKeyResponse(session *models.Principal, params kmsAPI.KMSCreateKeyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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) *models.Error {
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) *models.Error {
func GetKMSImportKeyResponse(session *models.Principal, params kmsAPI.KMSImportKeyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -256,17 +268,17 @@ func GetKMSImportKeyResponse(session *models.Principal, params kmsAPI.KMSImportK
return importKey(ctx, params.Name, bytes, AdminClient{Client: mAdmin})
}
func importKey(ctx context.Context, key string, bytes []byte, minioClient MinioAdmin) *models.Error {
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, *models.Error) {
func GetKMSListKeysResponse(session *models.Principal, params kmsAPI.KMSListKeysParams) (*models.KmsListKeysResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -277,7 +289,7 @@ func GetKMSListKeysResponse(session *models.Principal, params kmsAPI.KMSListKeys
return listKeys(ctx, pattern, AdminClient{Client: mAdmin})
}
func listKeys(ctx context.Context, pattern string, minioClient MinioAdmin) (*models.KmsListKeysResponse, *models.Error) {
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)
@@ -296,17 +308,17 @@ func parseKeys(results []madmin.KMSKeyInfo) (data []*models.KmsKeyInfo) {
return data
}
func GetKMSKeyStatusResponse(session *models.Principal, params kmsAPI.KMSKeyStatusParams) (*models.KmsKeyStatusResponse, *models.Error) {
func GetKMSKeyStatusResponse(session *models.Principal, params kmsAPI.KMSKeyStatusParams) (*models.KmsKeyStatusResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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, *models.Error) {
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)
@@ -318,17 +330,17 @@ func keyStatus(ctx context.Context, key string, minioClient MinioAdmin) (*models
}, nil
}
func GetKMSDeleteKeyResponse(session *models.Principal, params kmsAPI.KMSDeleteKeyParams) *models.Error {
func GetKMSDeleteKeyResponse(session *models.Principal, params kmsAPI.KMSDeleteKeyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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) *models.Error {
func deleteKey(ctx context.Context, key string, minioClient MinioAdmin) *CodedAPIError {
if err := minioClient.deleteKey(ctx, key); err != nil {
return ErrorWithContext(ctx, err)
}
@@ -339,7 +351,7 @@ 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(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSSetPolicyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSSetPolicyOK()
})
@@ -347,7 +359,7 @@ func registerKMSPolicyHandlers(api *operations.ConsoleAPI) {
api.KmsKMSAssignPolicyHandler = kmsAPI.KMSAssignPolicyHandlerFunc(func(params kmsAPI.KMSAssignPolicyParams, session *models.Principal) middleware.Responder {
err := GetKMSAssignPolicyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSAssignPolicyDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSAssignPolicyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSAssignPolicyOK()
})
@@ -355,7 +367,7 @@ func registerKMSPolicyHandlers(api *operations.ConsoleAPI) {
api.KmsKMSDescribePolicyHandler = kmsAPI.KMSDescribePolicyHandlerFunc(func(params kmsAPI.KMSDescribePolicyParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSDescribePolicyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSDescribePolicyDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSDescribePolicyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSDescribePolicyOK().WithPayload(resp)
})
@@ -363,7 +375,7 @@ func registerKMSPolicyHandlers(api *operations.ConsoleAPI) {
api.KmsKMSGetPolicyHandler = kmsAPI.KMSGetPolicyHandlerFunc(func(params kmsAPI.KMSGetPolicyParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSGetPolicyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSGetPolicyDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSGetPolicyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSGetPolicyOK().WithPayload(resp)
})
@@ -371,7 +383,7 @@ func registerKMSPolicyHandlers(api *operations.ConsoleAPI) {
api.KmsKMSListPoliciesHandler = kmsAPI.KMSListPoliciesHandlerFunc(func(params kmsAPI.KMSListPoliciesParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSListPoliciesResponse(session, params)
if err != nil {
return kmsAPI.NewKMSListPoliciesDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSListPoliciesDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSListPoliciesOK().WithPayload(resp)
})
@@ -379,16 +391,16 @@ func registerKMSPolicyHandlers(api *operations.ConsoleAPI) {
api.KmsKMSDeletePolicyHandler = kmsAPI.KMSDeletePolicyHandlerFunc(func(params kmsAPI.KMSDeletePolicyParams, session *models.Principal) middleware.Responder {
err := GetKMSDeletePolicyResponse(session, params)
if err != nil {
return kmsAPI.NewKMSDeletePolicyDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSDeletePolicyDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSDeletePolicyOK()
})
}
func GetKMSSetPolicyResponse(session *models.Principal, params kmsAPI.KMSSetPolicyParams) *models.Error {
func GetKMSSetPolicyResponse(session *models.Principal, params kmsAPI.KMSSetPolicyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -399,17 +411,17 @@ func GetKMSSetPolicyResponse(session *models.Principal, params kmsAPI.KMSSetPoli
return setPolicy(ctx, *params.Body.Policy, bytes, AdminClient{Client: mAdmin})
}
func setPolicy(ctx context.Context, policy string, content []byte, minioClient MinioAdmin) *models.Error {
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) *models.Error {
func GetKMSAssignPolicyResponse(session *models.Principal, params kmsAPI.KMSAssignPolicyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -420,24 +432,24 @@ func GetKMSAssignPolicyResponse(session *models.Principal, params kmsAPI.KMSAssi
return assignPolicy(ctx, params.Name, bytes, AdminClient{Client: mAdmin})
}
func assignPolicy(ctx context.Context, policy string, content []byte, minioClient MinioAdmin) *models.Error {
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, *models.Error) {
func GetKMSDescribePolicyResponse(session *models.Principal, params kmsAPI.KMSDescribePolicyParams) (*models.KmsDescribePolicyResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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, *models.Error) {
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)
@@ -449,17 +461,17 @@ func describePolicy(ctx context.Context, policy string, minioClient MinioAdmin)
}, nil
}
func GetKMSGetPolicyResponse(session *models.Principal, params kmsAPI.KMSGetPolicyParams) (*models.KmsGetPolicyResponse, *models.Error) {
func GetKMSGetPolicyResponse(session *models.Principal, params kmsAPI.KMSGetPolicyParams) (*models.KmsGetPolicyResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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, *models.Error) {
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)
@@ -470,10 +482,10 @@ func getPolicy(ctx context.Context, policy string, minioClient MinioAdmin) (*mod
}, nil
}
func GetKMSListPoliciesResponse(session *models.Principal, params kmsAPI.KMSListPoliciesParams) (*models.KmsListPoliciesResponse, *models.Error) {
func GetKMSListPoliciesResponse(session *models.Principal, params kmsAPI.KMSListPoliciesParams) (*models.KmsListPoliciesResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -484,7 +496,7 @@ func GetKMSListPoliciesResponse(session *models.Principal, params kmsAPI.KMSList
return listKMSPolicies(ctx, pattern, AdminClient{Client: mAdmin})
}
func listKMSPolicies(ctx context.Context, pattern string, minioClient MinioAdmin) (*models.KmsListPoliciesResponse, *models.Error) {
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)
@@ -503,17 +515,17 @@ func parsePolicies(results []madmin.KMSPolicyInfo) (data []*models.KmsPolicyInfo
return data
}
func GetKMSDeletePolicyResponse(session *models.Principal, params kmsAPI.KMSDeletePolicyParams) *models.Error {
func GetKMSDeletePolicyResponse(session *models.Principal, params kmsAPI.KMSDeletePolicyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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) *models.Error {
func deletePolicy(ctx context.Context, policy string, minioClient MinioAdmin) *CodedAPIError {
if err := minioClient.deletePolicy(ctx, policy); err != nil {
return ErrorWithContext(ctx, err)
}
@@ -524,7 +536,7 @@ 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(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSDescribeIdentityDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSDescribeIdentityOK().WithPayload(resp)
})
@@ -532,7 +544,7 @@ func registerKMSIdentityHandlers(api *operations.ConsoleAPI) {
api.KmsKMSDescribeSelfIdentityHandler = kmsAPI.KMSDescribeSelfIdentityHandlerFunc(func(params kmsAPI.KMSDescribeSelfIdentityParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSDescribeSelfIdentityResponse(session, params)
if err != nil {
return kmsAPI.NewKMSDescribeSelfIdentityDefault(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSDescribeSelfIdentityDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSDescribeSelfIdentityOK().WithPayload(resp)
})
@@ -540,30 +552,30 @@ func registerKMSIdentityHandlers(api *operations.ConsoleAPI) {
api.KmsKMSListIdentitiesHandler = kmsAPI.KMSListIdentitiesHandlerFunc(func(params kmsAPI.KMSListIdentitiesParams, session *models.Principal) middleware.Responder {
resp, err := GetKMSListIdentitiesResponse(session, params)
if err != nil {
return kmsAPI.NewKMSListIdentitiesDefault(int(err.Code)).WithPayload(err)
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(int(err.Code)).WithPayload(err)
return kmsAPI.NewKMSDeleteIdentityDefault(err.Code).WithPayload(err.APIError)
}
return kmsAPI.NewKMSDeleteIdentityOK()
})
}
func GetKMSDescribeIdentityResponse(session *models.Principal, params kmsAPI.KMSDescribeIdentityParams) (*models.KmsDescribeIdentityResponse, *models.Error) {
func GetKMSDescribeIdentityResponse(session *models.Principal, params kmsAPI.KMSDescribeIdentityParams) (*models.KmsDescribeIdentityResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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, *models.Error) {
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)
@@ -577,17 +589,17 @@ func describeIdentity(ctx context.Context, identity string, minioClient MinioAdm
}, nil
}
func GetKMSDescribeSelfIdentityResponse(session *models.Principal, params kmsAPI.KMSDescribeSelfIdentityParams) (*models.KmsDescribeSelfIdentityResponse, *models.Error) {
func GetKMSDescribeSelfIdentityResponse(session *models.Principal, params kmsAPI.KMSDescribeSelfIdentityParams) (*models.KmsDescribeSelfIdentityResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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, *models.Error) {
func describeSelfIdentity(ctx context.Context, minioClient MinioAdmin) (*models.KmsDescribeSelfIdentityResponse, *CodedAPIError) {
i, err := minioClient.describeSelfIdentity(ctx)
if err != nil {
return nil, ErrorWithContext(ctx, err)
@@ -604,10 +616,10 @@ func describeSelfIdentity(ctx context.Context, minioClient MinioAdmin) (*models.
}, nil
}
func GetKMSListIdentitiesResponse(session *models.Principal, params kmsAPI.KMSListIdentitiesParams) (*models.KmsListIdentitiesResponse, *models.Error) {
func GetKMSListIdentitiesResponse(session *models.Principal, params kmsAPI.KMSListIdentitiesParams) (*models.KmsListIdentitiesResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -618,7 +630,7 @@ func GetKMSListIdentitiesResponse(session *models.Principal, params kmsAPI.KMSLi
return listIdentities(ctx, pattern, AdminClient{Client: mAdmin})
}
func listIdentities(ctx context.Context, pattern string, minioClient MinioAdmin) (*models.KmsListIdentitiesResponse, *models.Error) {
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)
@@ -639,17 +651,17 @@ func parseIdentities(results []madmin.KMSIdentityInfo) (data []*models.KmsIdenti
return data
}
func GetKMSDeleteIdentityResponse(session *models.Principal, params kmsAPI.KMSDeleteIdentityParams) *models.Error {
func GetKMSDeleteIdentityResponse(session *models.Principal, params kmsAPI.KMSDeleteIdentityParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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) *models.Error {
func deleteIdentity(ctx context.Context, identity string, minioClient MinioAdmin) *CodedAPIError {
if err := minioClient.deleteIdentity(ctx, identity); err != nil {
return ErrorWithContext(ctx, err)
}

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -23,110 +23,25 @@ import (
"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/minio/console/restapi/operations"
kmsAPI "github.com/minio/console/restapi/operations/k_m_s"
"github.com/minio/madmin-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
func (ac adminClientMock) kmsStatus(ctx 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(ctx context.Context) ([]madmin.KMSAPI, error) {
return []madmin.KMSAPI{{Method: "GET", Path: "/mock"}}, nil
}
func (ac adminClientMock) kmsMetrics(ctx context.Context) (*madmin.KMSMetrics, error) {
return &madmin.KMSMetrics{}, nil
}
func (ac adminClientMock) kmsVersion(ctx context.Context) (*madmin.KMSVersion, error) {
return &madmin.KMSVersion{Version: "test-version"}, nil
}
func (ac adminClientMock) createKey(ctx context.Context, key string) error {
return nil
}
func (ac adminClientMock) importKey(ctx context.Context, key string, content []byte) error {
return nil
}
func (ac adminClientMock) listKeys(ctx context.Context, pattern string) ([]madmin.KMSKeyInfo, error) {
return []madmin.KMSKeyInfo{{
Name: "name",
CreatedBy: "by",
}}, nil
}
func (ac adminClientMock) keyStatus(ctx context.Context, key string) (*madmin.KMSKeyStatus, error) {
return &madmin.KMSKeyStatus{KeyID: "key"}, nil
}
func (ac adminClientMock) deleteKey(ctx context.Context, key string) error {
return nil
}
func (ac adminClientMock) setKMSPolicy(ctx context.Context, policy string, content []byte) error {
return nil
}
func (ac adminClientMock) assignPolicy(ctx context.Context, policy string, content []byte) error {
return nil
}
func (ac adminClientMock) describePolicy(ctx context.Context, policy string) (*madmin.KMSDescribePolicy, error) {
return &madmin.KMSDescribePolicy{Name: "name"}, nil
}
func (ac adminClientMock) getKMSPolicy(ctx context.Context, policy string) (*madmin.KMSPolicy, error) {
return &madmin.KMSPolicy{Allow: []string{""}, Deny: []string{""}}, nil
}
func (ac adminClientMock) listKMSPolicies(ctx context.Context, pattern string) ([]madmin.KMSPolicyInfo, error) {
return []madmin.KMSPolicyInfo{{
Name: "name",
CreatedBy: "by",
}}, nil
}
func (ac adminClientMock) deletePolicy(ctx context.Context, policy string) error {
return nil
}
func (ac adminClientMock) describeIdentity(ctx context.Context, identity string) (*madmin.KMSDescribeIdentity, error) {
return &madmin.KMSDescribeIdentity{}, nil
}
func (ac adminClientMock) describeSelfIdentity(ctx context.Context) (*madmin.KMSDescribeSelfIdentity, error) {
return &madmin.KMSDescribeSelfIdentity{
Policy: &madmin.KMSPolicy{Allow: []string{}, Deny: []string{}},
}, nil
}
func (ac adminClientMock) deleteIdentity(ctx context.Context, identity string) error {
return nil
}
func (ac adminClientMock) listIdentities(ctx context.Context, pattern string) ([]madmin.KMSIdentityInfo, error) {
return []madmin.KMSIdentityInfo{{Identity: "identity"}}, nil
}
type KMSTestSuite struct {
suite.Suite
assert *assert.Assertions
currentServer string
isServerSet bool
server *httptest.Server
adminClient adminClientMock
adminClient AdminClientMock
}
func (suite *KMSTestSuite) SetupSuite() {
suite.assert = assert.New(suite.T())
suite.adminClient = adminClientMock{}
suite.adminClient = AdminClientMock{}
}
func (suite *KMSTestSuite) SetupTest() {
@@ -135,7 +50,7 @@ func (suite *KMSTestSuite) SetupTest() {
os.Setenv(ConsoleMinIOServer, suite.server.URL)
}
func (suite *KMSTestSuite) serverHandler(w http.ResponseWriter, r *http.Request) {
func (suite *KMSTestSuite) serverHandler(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(400)
}

View File

@@ -14,32 +14,32 @@
// 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 restapi
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"
"github.com/minio/console/restapi/operations"
systemApi "github.com/minio/console/restapi/operations/system"
)
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(int(err.Code)).WithPayload(err)
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, *models.Error) {
func getListNodesResponse(session *models.Principal, params systemApi.ListNodesParams) ([]string, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}

View File

@@ -14,16 +14,16 @@
// 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 restapi
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"
"github.com/minio/console/restapi/operations"
configurationApi "github.com/minio/console/restapi/operations/configuration"
)
func registerAdminNotificationEndpointsHandlers(api *operations.ConsoleAPI) {
@@ -31,7 +31,7 @@ func registerAdminNotificationEndpointsHandlers(api *operations.ConsoleAPI) {
api.ConfigurationNotificationEndpointListHandler = configurationApi.NotificationEndpointListHandlerFunc(func(params configurationApi.NotificationEndpointListParams, session *models.Principal) middleware.Responder {
notifEndpoints, err := getNotificationEndpointsResponse(session, params)
if err != nil {
return configurationApi.NewNotificationEndpointListDefault(int(err.Code)).WithPayload(err)
return configurationApi.NewNotificationEndpointListDefault(err.Code).WithPayload(err.APIError)
}
return configurationApi.NewNotificationEndpointListOK().WithPayload(notifEndpoints)
})
@@ -39,7 +39,7 @@ func registerAdminNotificationEndpointsHandlers(api *operations.ConsoleAPI) {
api.ConfigurationAddNotificationEndpointHandler = configurationApi.AddNotificationEndpointHandlerFunc(func(params configurationApi.AddNotificationEndpointParams, session *models.Principal) middleware.Responder {
notifEndpoints, err := getAddNotificationEndpointResponse(session, params)
if err != nil {
return configurationApi.NewAddNotificationEndpointDefault(int(err.Code)).WithPayload(err)
return configurationApi.NewAddNotificationEndpointDefault(err.Code).WithPayload(err.APIError)
}
return configurationApi.NewAddNotificationEndpointCreated().WithPayload(notifEndpoints)
})
@@ -73,10 +73,10 @@ func getNotificationEndpoints(ctx context.Context, client MinioAdmin) (*models.N
}
// getNotificationEndpointsResponse returns a list of notification endpoints in the instance
func getNotificationEndpointsResponse(session *models.Principal, params configurationApi.NotificationEndpointListParams) (*models.NotifEndpointResponse, *models.Error) {
func getNotificationEndpointsResponse(session *models.Principal, params configurationApi.NotificationEndpointListParams) (*models.NotifEndpointResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -143,10 +143,10 @@ func addNotificationEndpoint(ctx context.Context, client MinioAdmin, params *con
}
// getNotificationEndpointsResponse returns a list of notification endpoints in the instance
func getAddNotificationEndpointResponse(session *models.Principal, params configurationApi.AddNotificationEndpointParams) (*models.SetNotificationEndpointResponse, *models.Error) {
func getAddNotificationEndpointResponse(session *models.Principal, params configurationApi.AddNotificationEndpointParams) (*models.SetNotificationEndpointResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -24,12 +24,12 @@ import (
"github.com/go-openapi/swag"
cfgApi "github.com/minio/console/api/operations/configuration"
"github.com/minio/console/models"
cfgApi "github.com/minio/console/restapi/operations/configuration"
)
func Test_addNotificationEndpoint(t *testing.T) {
client := adminClientMock{}
client := AdminClientMock{}
type args struct {
ctx context.Context
@@ -61,7 +61,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
@@ -94,7 +94,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, errors.New("error")
},
want: nil,
@@ -118,7 +118,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
@@ -149,7 +149,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
@@ -178,7 +178,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
@@ -208,7 +208,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
@@ -240,7 +240,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
@@ -273,7 +273,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
@@ -305,7 +305,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
@@ -335,7 +335,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
@@ -365,7 +365,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, nil
},
want: &models.SetNotificationEndpointResponse{
@@ -397,7 +397,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return false, errors.New("invalid config")
},
want: nil,
@@ -421,7 +421,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
},
},
mockSetConfig: func(kv string) (restart bool, err error) {
mockSetConfig: func(_ string) (restart bool, err error) {
return true, nil
},
want: &models.SetNotificationEndpointResponse{
@@ -438,7 +438,7 @@ func Test_addNotificationEndpoint(t *testing.T) {
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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)

104
api/admin_objects.go Normal file
View File

@@ -0,0 +1,104 @@
// 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/base64"
"time"
"github.com/minio/mc/cmd"
"github.com/minio/minio-go/v7"
)
type objectsListOpts struct {
BucketName string
Prefix string
Date time.Time
}
type ObjectsRequest struct {
Mode string `json:"mode,omitempty"`
BucketName string `json:"bucket_name"`
Prefix string `json:"prefix"`
Date string `json:"date"`
RequestID int64 `json:"request_id"`
}
type WSResponse struct {
RequestID int64 `json:"request_id,omitempty"`
Error *CodedAPIError `json:"error,omitempty"`
RequestEnd bool `json:"request_end,omitempty"`
Prefix string `json:"prefix,omitempty"`
BucketName string `json:"bucketName,omitempty"`
Data []ObjectResponse `json:"data,omitempty"`
}
type ObjectResponse struct {
Name string `json:"name,omitempty"`
LastModified string `json:"last_modified,omitempty"`
Size int64 `json:"size,omitempty"`
VersionID string `json:"version_id,omitempty"`
DeleteMarker bool `json:"delete_flag,omitempty"`
IsLatest bool `json:"is_latest,omitempty"`
}
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)
}
if request.Mode == "rewind" {
parsedDate, errDate := time.Parse(time.RFC3339, request.Date)
if errDate != nil {
return nil, errDate
}
pOptions.Date = parsedDate
}
return &pOptions, nil
}
func startObjectsListing(ctx context.Context, client MinioClient, objOpts *objectsListOpts) <-chan minio.ObjectInfo {
opts := minio.ListObjectsOptions{
Prefix: objOpts.Prefix,
}
return client.listObjects(ctx, objOpts.BucketName, opts)
}
func startRewindListing(ctx context.Context, client MCClient, objOpts *objectsListOpts) <-chan *cmd.ClientContent {
lsRewind := client.list(ctx, cmd.ListOptions{TimeRef: objOpts.Date, WithDeleteMarkers: true})
return lsRewind
}

236
api/admin_objects_test.go Normal file
View File

@@ -0,0 +1,236 @@
// 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"
"testing"
"time"
mc "github.com/minio/mc/cmd"
"github.com/minio/minio-go/v7"
"github.com/stretchr/testify/assert"
)
func TestWSRewindObjects(t *testing.T) {
assert := assert.New(t)
client := s3ClientMock{}
tests := []struct {
name string
testOptions objectsListOpts
testMessages []*mc.ClientContent
}{
{
name: "Get list with multiple elements",
testOptions: objectsListOpts{
BucketName: "buckettest",
Prefix: "/",
Date: time.Now(),
},
testMessages: []*mc.ClientContent{
{
BucketName: "buckettest",
URL: mc.ClientURL{Path: "/file1.txt"},
},
{
BucketName: "buckettest",
URL: mc.ClientURL{Path: "/file2.txt"},
},
{
BucketName: "buckettest",
URL: mc.ClientURL{Path: "/path1"},
},
},
},
{
name: "Empty list of elements",
testOptions: objectsListOpts{
BucketName: "emptybucket",
Prefix: "/",
Date: time.Now(),
},
testMessages: []*mc.ClientContent{},
},
{
name: "Get list with one element",
testOptions: objectsListOpts{
BucketName: "buckettest",
Prefix: "/",
Date: time.Now(),
},
testMessages: []*mc.ClientContent{
{
BucketName: "buckettestsingle",
URL: mc.ClientURL{Path: "/file12.txt"},
},
},
},
{
name: "Get data from subpaths",
testOptions: objectsListOpts{
BucketName: "buckettest",
Prefix: "/path1/path2",
Date: time.Now(),
},
testMessages: []*mc.ClientContent{
{
BucketName: "buckettestsingle",
URL: mc.ClientURL{Path: "/path1/path2/file12.txt"},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mcListMock = func(_ context.Context, _ mc.ListOptions) <-chan *mc.ClientContent {
ch := make(chan *mc.ClientContent)
go func() {
defer close(ch)
for _, m := range tt.testMessages {
ch <- m
}
}()
return ch
}
rewindList := startRewindListing(ctx, client, &tt.testOptions)
// check that the rewindList got the same number of data from Console.
totalItems := 0
for data := range rewindList {
// Compare elements as we are defining the channel responses
assert.Equal(tt.testMessages[totalItems].URL.Path, data.URL.Path)
totalItems++
}
assert.Equal(len(tt.testMessages), totalItems)
})
}
}
func TestWSListObjects(t *testing.T) {
assert := assert.New(t)
client := minioClientMock{}
tests := []struct {
name string
wantErr bool
testOptions objectsListOpts
testMessages []minio.ObjectInfo
}{
{
name: "Get list with multiple elements",
wantErr: false,
testOptions: objectsListOpts{
BucketName: "buckettest",
Prefix: "/",
},
testMessages: []minio.ObjectInfo{
{
Key: "/file1.txt",
Size: 500,
IsLatest: true,
LastModified: time.Now(),
},
{
Key: "/file2.txt",
Size: 500,
IsLatest: true,
LastModified: time.Now(),
},
{
Key: "/path1",
},
},
},
{
name: "Empty list of elements",
wantErr: false,
testOptions: objectsListOpts{
BucketName: "emptybucket",
Prefix: "/",
},
testMessages: []minio.ObjectInfo{},
},
{
name: "Get list with one element",
wantErr: false,
testOptions: objectsListOpts{
BucketName: "buckettest",
Prefix: "/",
},
testMessages: []minio.ObjectInfo{
{
Key: "/file2.txt",
Size: 500,
IsLatest: true,
LastModified: time.Now(),
},
},
},
{
name: "Get data from subpaths",
wantErr: false,
testOptions: objectsListOpts{
BucketName: "buckettest",
Prefix: "/path1/path2",
},
testMessages: []minio.ObjectInfo{
{
Key: "/path1/path2/file1.txt",
Size: 500,
IsLatest: true,
LastModified: time.Now(),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
minioListObjectsMock = func(_ context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo {
ch := make(chan minio.ObjectInfo)
go func() {
defer close(ch)
for _, m := range tt.testMessages {
ch <- m
}
}()
return ch
}
objectsListing := startObjectsListing(ctx, client, &tt.testOptions)
// check that the TestReceiver got the same number of data from Console
totalItems := 0
for data := range objectsListing {
// Compare elements as we are defining the channel responses
assert.Equal(tt.testMessages[totalItems].Key, data.Key)
totalItems++
}
assert.Equal(len(tt.testMessages), totalItems)
})
}
}

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"bytes"
@@ -24,17 +24,17 @@ import (
"sort"
"strings"
bucketApi "github.com/minio/console/api/operations/bucket"
policyApi "github.com/minio/console/api/operations/policy"
"github.com/minio/console/pkg/utils"
bucketApi "github.com/minio/console/restapi/operations/bucket"
policyApi "github.com/minio/console/restapi/operations/policy"
s3 "github.com/minio/minio-go/v7"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
iampolicy "github.com/minio/pkg/iam/policy"
iampolicy "github.com/minio/pkg/v2/policy"
policies "github.com/minio/console/restapi/policy"
policies "github.com/minio/console/api/policy"
)
func registersPoliciesHandler(api *operations.ConsoleAPI) {
@@ -42,7 +42,7 @@ func registersPoliciesHandler(api *operations.ConsoleAPI) {
api.PolicyListPoliciesHandler = policyApi.ListPoliciesHandlerFunc(func(params policyApi.ListPoliciesParams, session *models.Principal) middleware.Responder {
listPoliciesResponse, err := getListPoliciesResponse(session, params)
if err != nil {
return policyApi.NewListPoliciesDefault(int(err.Code)).WithPayload(err)
return policyApi.NewListPoliciesDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewListPoliciesOK().WithPayload(listPoliciesResponse)
})
@@ -50,7 +50,7 @@ func registersPoliciesHandler(api *operations.ConsoleAPI) {
api.PolicyPolicyInfoHandler = policyApi.PolicyInfoHandlerFunc(func(params policyApi.PolicyInfoParams, session *models.Principal) middleware.Responder {
policyInfo, err := getPolicyInfoResponse(session, params)
if err != nil {
return policyApi.NewPolicyInfoDefault(int(err.Code)).WithPayload(err)
return policyApi.NewPolicyInfoDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewPolicyInfoOK().WithPayload(policyInfo)
})
@@ -58,78 +58,78 @@ func registersPoliciesHandler(api *operations.ConsoleAPI) {
api.PolicyAddPolicyHandler = policyApi.AddPolicyHandlerFunc(func(params policyApi.AddPolicyParams, session *models.Principal) middleware.Responder {
policyResponse, err := getAddPolicyResponse(session, params)
if err != nil {
return policyApi.NewAddPolicyDefault(int(err.Code)).WithPayload(err)
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(int(err.Code)).WithPayload(err)
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(int(err.Code)).WithPayload(err)
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(int(err.Code)).WithPayload(err)
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(int(err.Code)).WithPayload(err)
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(int(err.Code)).WithPayload(err)
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(int(err.Code)).WithPayload(err)
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(int(err.Code)).WithPayload(err)
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(int(err.Code)).WithPayload(err)
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(int(err.Code)).WithPayload(err)
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(session)
userPolicyResponse, err := getUserPolicyResponse(params.HTTPRequest.Context(), session)
if err != nil {
return policyApi.NewGetUserPolicyDefault(int(err.Code)).WithPayload(err)
return policyApi.NewGetUserPolicyDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewGetUserPolicyOK().WithPayload(userPolicyResponse)
})
@@ -137,17 +137,17 @@ func registersPoliciesHandler(api *operations.ConsoleAPI) {
api.PolicyGetSAUserPolicyHandler = policyApi.GetSAUserPolicyHandlerFunc(func(params policyApi.GetSAUserPolicyParams, session *models.Principal) middleware.Responder {
userPolicyResponse, err := getSAUserPolicyResponse(session, params)
if err != nil {
return policyApi.NewGetSAUserPolicyDefault(int(err.Code)).WithPayload(err)
return policyApi.NewGetSAUserPolicyDefault(err.Code).WithPayload(err.APIError)
}
return policyApi.NewGetSAUserPolicyOK().WithPayload(userPolicyResponse)
})
}
func getListAccessRulesWithBucketResponse(session *models.Principal, params bucketApi.ListAccessRulesWithBucketParams) (*models.ListAccessRulesResponse, *models.Error) {
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, "")
client, err := newS3BucketClient(session, bucket, "", getClientIP(params.HTTPRequest))
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -159,11 +159,11 @@ func getListAccessRulesWithBucketResponse(session *models.Principal, params buck
return &models.ListAccessRulesResponse{AccessRules: accessRuleList}, nil
}
func getSetAccessRuleWithBucketResponse(session *models.Principal, params bucketApi.SetAccessRuleWithBucketParams) (bool, *models.Error) {
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)
client, err := newS3BucketClient(session, params.Bucket, prefixAccess.Prefix, getClientIP(params.HTTPRequest))
if err != nil {
return false, ErrorWithContext(ctx, err)
}
@@ -179,12 +179,12 @@ func getSetAccessRuleWithBucketResponse(session *models.Principal, params bucket
return true, nil
}
func getDeleteAccessRuleWithBucketResponse(session *models.Principal, params bucketApi.DeleteAccessRuleWithBucketParams) (bool, *models.Error) {
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)
client, err := newS3BucketClient(session, bucket, prefix.Prefix, getClientIP(params.HTTPRequest))
if err != nil {
return false, ErrorWithContext(ctx, err)
}
@@ -195,10 +195,10 @@ func getDeleteAccessRuleWithBucketResponse(session *models.Principal, params buc
return true, nil
}
func getListPoliciesWithBucketResponse(session *models.Principal, params bucketApi.ListPoliciesWithBucketParams) (*models.ListPoliciesResponse, *models.Error) {
func getListPoliciesWithBucketResponse(session *models.Principal, params bucketApi.ListPoliciesWithBucketParams) (*models.ListPoliciesResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -281,10 +281,10 @@ func listPolicies(ctx context.Context, client MinioAdmin) ([]*models.Policy, err
}
// getListPoliciesResponse performs listPolicies() and serializes it to the handler's output
func getListPoliciesResponse(session *models.Principal, params policyApi.ListPoliciesParams) (*models.ListPoliciesResponse, *models.Error) {
func getListPoliciesResponse(session *models.Principal, params policyApi.ListPoliciesParams) (*models.ListPoliciesResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -305,14 +305,14 @@ func getListPoliciesResponse(session *models.Principal, params policyApi.ListPol
}
// getListUsersForPoliciesResponse performs lists users affected by a given policy.
func getListUsersForPolicyResponse(session *models.Principal, params policyApi.ListUsersForPolicyParams) ([]string, *models.Error) {
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(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -350,8 +350,8 @@ func getListUsersForPolicyResponse(session *models.Principal, params policyApi.L
return filteredUsers, nil
}
func getUserPolicyResponse(session *models.Principal) (string, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getUserPolicyResponse(ctx context.Context, session *models.Principal) (string, *CodedAPIError) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// serialize output
if session == nil {
@@ -360,7 +360,7 @@ func getUserPolicyResponse(session *models.Principal) (string, *models.Error) {
tokenClaims, _ := getClaimsFromToken(session.STSSessionToken)
// initialize admin client
mAdminClient, err := NewMinioAdminClient(&models.Principal{
mAdminClient, err := NewMinioAdminClient(ctx, &models.Principal{
STSAccessKeyID: session.STSAccessKeyID,
STSSecretAccessKey: session.STSSecretAccessKey,
STSSessionToken: session.STSSessionToken,
@@ -379,7 +379,7 @@ func getUserPolicyResponse(session *models.Principal) (string, *models.Error) {
return string(rawPolicy), nil
}
func getSAUserPolicyResponse(session *models.Principal, params policyApi.GetSAUserPolicyParams) (*models.AUserPolicyResponse, *models.Error) {
func getSAUserPolicyResponse(session *models.Principal, params policyApi.GetSAUserPolicyParams) (*models.AUserPolicyResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// serialize output
@@ -387,7 +387,7 @@ func getSAUserPolicyResponse(session *models.Principal, params policyApi.GetSAUs
return nil, ErrorWithContext(ctx, ErrPolicyNotFound)
}
// initialize admin client
mAdminClient, err := NewMinioAdminClient(&models.Principal{
mAdminClient, err := NewMinioAdminClient(params.HTTPRequest.Context(), &models.Principal{
STSAccessKeyID: session.STSAccessKeyID,
STSSecretAccessKey: session.STSSecretAccessKey,
STSSessionToken: session.STSSessionToken,
@@ -458,10 +458,10 @@ func getSAUserPolicyResponse(session *models.Principal, params policyApi.GetSAUs
return getUserPoliciesResponse, nil
}
func getListGroupsForPolicyResponse(session *models.Principal, params policyApi.ListGroupsForPolicyParams) ([]string, *models.Error) {
func getListGroupsForPolicyResponse(session *models.Principal, params policyApi.ListGroupsForPolicyParams) ([]string, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -518,7 +518,7 @@ func removePolicy(ctx context.Context, client MinioAdmin, name string) error {
}
// getRemovePolicyResponse() performs removePolicy() and serializes it to the handler's output
func getRemovePolicyResponse(session *models.Principal, params policyApi.RemovePolicyParams) *models.Error {
func getRemovePolicyResponse(session *models.Principal, params policyApi.RemovePolicyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params.Name == "" {
@@ -528,7 +528,7 @@ func getRemovePolicyResponse(session *models.Principal, params policyApi.RemoveP
if err != nil {
return ErrorWithContext(ctx, err)
}
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -562,7 +562,7 @@ func addPolicy(ctx context.Context, client MinioAdmin, name, policy string) (*mo
}
// getAddPolicyResponse performs addPolicy() and serializes it to the handler's output
func getAddPolicyResponse(session *models.Principal, params policyApi.AddPolicyParams) (*models.Policy, *models.Error) {
func getAddPolicyResponse(session *models.Principal, params policyApi.AddPolicyParams) (*models.Policy, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params.Body == nil {
@@ -571,7 +571,7 @@ func getAddPolicyResponse(session *models.Principal, params policyApi.AddPolicyP
if strings.Contains(*params.Body.Name, " ") {
return nil, ErrorWithContext(ctx, ErrPolicyNameContainsSpace)
}
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -613,10 +613,10 @@ func getPolicyStatements(ctx context.Context, client MinioAdmin, name string) ([
}
// getPolicyInfoResponse performs policyInfo() and serializes it to the handler's output
func getPolicyInfoResponse(session *models.Principal, params policyApi.PolicyInfoParams) (*models.Policy, *models.Error) {
func getPolicyInfoResponse(session *models.Principal, params policyApi.PolicyInfoParams) (*models.Policy, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -644,11 +644,11 @@ func SetPolicy(ctx context.Context, client MinioAdmin, name, entityName string,
}
// getSetPolicyResponse() performs SetPolicy() and serializes it to the handler's output
func getSetPolicyResponse(session *models.Principal, params policyApi.SetPolicyParams) *models.Error {
func getSetPolicyResponse(session *models.Principal, params policyApi.SetPolicyParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// Removing this section
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -662,10 +662,10 @@ func getSetPolicyResponse(session *models.Principal, params policyApi.SetPolicyP
return nil
}
func getSetPolicyMultipleResponse(session *models.Principal, params policyApi.SetPolicyMultipleParams) *models.Error {
func getSetPolicyMultipleResponse(session *models.Principal, params policyApi.SetPolicyMultipleParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"bytes"
@@ -26,50 +26,15 @@ import (
"testing"
"github.com/minio/console/models"
iampolicy "github.com/minio/pkg/iam/policy"
iampolicy "github.com/minio/pkg/v2/policy"
"github.com/stretchr/testify/assert"
)
// assigning mock at runtime instead of compile time
var minioListPoliciesMock func() (map[string]*iampolicy.Policy, error)
var (
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
)
// mock function of listPolicies()
func (ac adminClientMock) listPolicies(ctx context.Context) (map[string]*iampolicy.Policy, error) {
return minioListPoliciesMock()
}
// mock function of getPolicy()
func (ac adminClientMock) getPolicy(ctx context.Context, name string) (*iampolicy.Policy, error) {
return minioGetPolicyMock(name)
}
// mock function of removePolicy()
func (ac adminClientMock) removePolicy(ctx context.Context, name string) error {
return minioRemovePolicyMock(name)
}
// mock function of addPolicy()
func (ac adminClientMock) addPolicy(ctx context.Context, name string, policy *iampolicy.Policy) error {
return minioAddPolicyMock(name, policy)
}
// mock function SetPolicy()
func (ac adminClientMock) setPolicy(ctx context.Context, policyName, entityName string, isGroup bool) error {
return minioSetPolicyMock(policyName, entityName, isGroup)
}
func TestListPolicies(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
funcAssert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
// mock function response from listPolicies()
minioListPoliciesMock = func() (map[string]*iampolicy.Policy, error) {
var readonly iampolicy.Policy
@@ -115,10 +80,10 @@ func TestRemovePolicy(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
funcAssert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
// Test-1 : removePolicy() remove an existing policy
policyToRemove := "console-policy"
minioRemovePolicyMock = func(name string) error {
minioRemovePolicyMock = func(_ string) error {
return nil
}
function := "removePolicy()"
@@ -126,7 +91,7 @@ func TestRemovePolicy(t *testing.T) {
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(name string) error {
minioRemovePolicyMock = func(_ string) error {
return errors.New("error")
}
if err := removePolicy(ctx, adminClient, policyToRemove); funcAssert.Error(err) {
@@ -138,13 +103,13 @@ func TestAddPolicy(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
funcAssert := assert.New(t)
adminClient := adminClientMock{}
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(name string, policy *iampolicy.Policy) error {
minioAddPolicyMock = func(_ string, _ *iampolicy.Policy) error {
return nil
}
minioGetPolicyMock = func(name string) (*iampolicy.Policy, error) {
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 {
@@ -173,17 +138,17 @@ func TestAddPolicy(t *testing.T) {
funcAssert.Equal(expectedPolicy, actualPolicy)
}
// Test-2 : addPolicy() got an error while adding policy
minioAddPolicyMock = func(name string, policy *iampolicy.Policy) error {
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(name string, policy *iampolicy.Policy) error {
minioAddPolicyMock = func(_ string, _ *iampolicy.Policy) error {
return nil
}
minioGetPolicyMock = func(name string) (*iampolicy.Policy, error) {
minioGetPolicyMock = func(_ string) (*iampolicy.Policy, error) {
return nil, errors.New("error")
}
if _, err := addPolicy(ctx, adminClient, policyName, policyDefinition); funcAssert.Error(err) {
@@ -195,11 +160,11 @@ func TestSetPolicy(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
funcAssert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
policyName := "readOnly"
entityName := "alevsk"
entityObject := models.PolicyEntityUser
minioSetPolicyMock = func(policyName, entityName string, isGroup bool) error {
minioSetPolicyMock = func(_, _ string, _ bool) error {
return nil
}
// Test-1 : SetPolicy() set policy to user
@@ -216,7 +181,7 @@ func TestSetPolicy(t *testing.T) {
}
// Test-3 : SetPolicy() set policy to user and get error
entityObject = models.PolicyEntityUser
minioSetPolicyMock = func(policyName, entityName string, isGroup bool) error {
minioSetPolicyMock = func(_, _ string, _ bool) error {
return errors.New("error")
}
if err := SetPolicy(ctx, adminClient, policyName, entityName, entityObject); funcAssert.Error(err) {
@@ -224,7 +189,7 @@ func TestSetPolicy(t *testing.T) {
}
// Test-4 : SetPolicy() set policy to group and get error
entityObject = models.PolicyEntityGroup
minioSetPolicyMock = func(policyName, entityName string, isGroup bool) error {
minioSetPolicyMock = func(_, _ string, _ bool) error {
return errors.New("error")
}
if err := SetPolicy(ctx, adminClient, policyName, entityName, entityObject); funcAssert.Error(err) {
@@ -235,7 +200,7 @@ func TestSetPolicy(t *testing.T) {
func Test_SetPolicyMultiple(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
type args struct {
policyName string
@@ -254,7 +219,7 @@ func Test_SetPolicyMultiple(t *testing.T) {
policyName: "readonly",
users: []models.IamEntity{"user1", "user2"},
groups: []models.IamEntity{"group1", "group2"},
setPolicyFunc: func(policyName, entityName string, isGroup bool) error {
setPolicyFunc: func(_, _ string, _ bool) error {
return nil
},
},
@@ -266,7 +231,7 @@ func Test_SetPolicyMultiple(t *testing.T) {
policyName: "readonly",
users: []models.IamEntity{"user1", "user2"},
groups: []models.IamEntity{"group1", "group2"},
setPolicyFunc: func(policyName, entityName string, isGroup bool) error {
setPolicyFunc: func(_, _ string, _ bool) error {
return errors.New("error set")
},
},
@@ -279,7 +244,7 @@ func Test_SetPolicyMultiple(t *testing.T) {
policyName: "readonly",
users: []models.IamEntity{},
groups: []models.IamEntity{},
setPolicyFunc: func(policyName, entityName string, isGroup bool) error {
setPolicyFunc: func(_, _ string, _ bool) error {
return nil
},
},
@@ -287,7 +252,7 @@ func Test_SetPolicyMultiple(t *testing.T) {
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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) {
@@ -408,7 +373,7 @@ func Test_policyMatchesBucket(t *testing.T) {
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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

@@ -14,16 +14,16 @@
// 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 restapi
package api
import (
"context"
"io/ioutil"
"io"
"net/http"
"github.com/gorilla/websocket"
"github.com/minio/console/models"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
"github.com/minio/websocket"
)
var items []*models.StartProfilingItem
@@ -55,7 +55,7 @@ func startProfiling(ctx context.Context, conn WSConn, client MinioAdmin, pOpts *
if err != nil {
return err
}
message, err := ioutil.ReadAll(zippedData)
message, err := io.ReadAll(zippedData)
if err != nil {
return err
}

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"bytes"
@@ -25,25 +25,10 @@ import (
"net/url"
"testing"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
)
var (
minioStartProfiling func(profiler madmin.ProfilerType) ([]madmin.StartProfilingResult, error)
minioStopProfiling func() (io.ReadCloser, error)
)
// mock function for startProfiling()
func (ac adminClientMock) startProfiling(ctx context.Context, profiler madmin.ProfilerType) ([]madmin.StartProfilingResult, error) {
return minioStartProfiling(profiler)
}
// mock function for stopProfiling()
func (ac adminClientMock) stopProfiling(ctx context.Context) (io.ReadCloser, error) {
return minioStopProfiling()
}
// Implementing fake closingBuffer to mock stopProfiling() (io.ReadCloser, error)
type ClosingBuffer struct {
*bytes.Buffer
@@ -58,7 +43,7 @@ func TestStartProfiling(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
assert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
mockWSConn := mockConn{}
function := "startProfiling()"
testOptions := &profileOptions{
@@ -67,7 +52,7 @@ func TestStartProfiling(t *testing.T) {
// Test-1 : startProfiling() Get response from MinIO server with one profiling object without errors
// mock function response from startProfiling()
minioStartProfiling = func(profiler madmin.ProfilerType) ([]madmin.StartProfilingResult, error) {
minioStartProfiling = func(_ madmin.ProfilerType) ([]madmin.StartProfilingResult, error) {
return []madmin.StartProfilingResult{
{
NodeName: "http://127.0.0.1:9000/",
@@ -86,7 +71,7 @@ func TestStartProfiling(t *testing.T) {
return &ClosingBuffer{bytes.NewBufferString("In memory string eaeae")}, nil
}
// mock function response from mockConn.writeMessage()
connWriteMessageMock = func(messageType int, p []byte) error {
connWriteMessageMock = func(_ int, _ []byte) error {
return nil
}
err := startProfiling(ctx, mockWSConn, adminClient, testOptions)
@@ -97,7 +82,7 @@ func TestStartProfiling(t *testing.T) {
// Test-2 : startProfiling() Correctly handles errors returned by MinIO
// mock function response from startProfiling()
minioStartProfiling = func(profiler madmin.ProfilerType) ([]madmin.StartProfilingResult, error) {
minioStartProfiling = func(_ madmin.ProfilerType) ([]madmin.StartProfilingResult, error) {
return nil, errors.New("error")
}
err = startProfiling(ctx, mockWSConn, adminClient, testOptions)

116
api/admin_releases.go Normal file
View File

@@ -0,0 +1,116 @@
// 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
}

104
api/admin_releases_test.go Normal file
View File

@@ -0,0 +1,104 @@
// 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

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -24,13 +24,15 @@ import (
"strconv"
"time"
"github.com/minio/madmin-go"
"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/console/restapi/operations"
bucketApi "github.com/minio/console/restapi/operations/bucket"
"github.com/minio/minio-go/v7/pkg/replication"
)
@@ -45,7 +47,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketListRemoteBucketsHandler = bucketApi.ListRemoteBucketsHandlerFunc(func(params bucketApi.ListRemoteBucketsParams, session *models.Principal) middleware.Responder {
listResp, err := getListRemoteBucketsResponse(session, params)
if err != nil {
return bucketApi.NewListRemoteBucketsDefault(int(err.Code)).WithPayload(err)
return bucketApi.NewListRemoteBucketsDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewListRemoteBucketsOK().WithPayload(listResp)
})
@@ -54,7 +56,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketRemoteBucketDetailsHandler = bucketApi.RemoteBucketDetailsHandlerFunc(func(params bucketApi.RemoteBucketDetailsParams, session *models.Principal) middleware.Responder {
response, err := getRemoteBucketDetailsResponse(session, params)
if err != nil {
return bucketApi.NewRemoteBucketDetailsDefault(int(err.Code)).WithPayload(err)
return bucketApi.NewRemoteBucketDetailsDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewRemoteBucketDetailsOK().WithPayload(response)
})
@@ -63,7 +65,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketDeleteRemoteBucketHandler = bucketApi.DeleteRemoteBucketHandlerFunc(func(params bucketApi.DeleteRemoteBucketParams, session *models.Principal) middleware.Responder {
err := getDeleteRemoteBucketResponse(session, params)
if err != nil {
return bucketApi.NewDeleteRemoteBucketDefault(int(err.Code)).WithPayload(err)
return bucketApi.NewDeleteRemoteBucketDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewDeleteRemoteBucketNoContent()
})
@@ -72,7 +74,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketAddRemoteBucketHandler = bucketApi.AddRemoteBucketHandlerFunc(func(params bucketApi.AddRemoteBucketParams, session *models.Principal) middleware.Responder {
err := getAddRemoteBucketResponse(session, params)
if err != nil {
return bucketApi.NewAddRemoteBucketDefault(int(err.Code)).WithPayload(err)
return bucketApi.NewAddRemoteBucketDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewAddRemoteBucketCreated()
})
@@ -81,17 +83,17 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketSetMultiBucketReplicationHandler = bucketApi.SetMultiBucketReplicationHandlerFunc(func(params bucketApi.SetMultiBucketReplicationParams, session *models.Principal) middleware.Responder {
response, err := setMultiBucketReplicationResponse(session, params)
if err != nil {
return bucketApi.NewSetMultiBucketReplicationDefault(int(err.Code)).WithPayload(err)
return bucketApi.NewSetMultiBucketReplicationDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewSetMultiBucketReplicationOK().WithPayload(response)
})
// list external buckets
api.BucketListExternalBucketsHandler = bucketApi.ListExternalBucketsHandlerFunc(func(params bucketApi.ListExternalBucketsParams, session *models.Principal) middleware.Responder {
api.BucketListExternalBucketsHandler = bucketApi.ListExternalBucketsHandlerFunc(func(params bucketApi.ListExternalBucketsParams, _ *models.Principal) middleware.Responder {
response, err := listExternalBucketsResponse(params)
if err != nil {
return bucketApi.NewListExternalBucketsDefault(int(err.Code)).WithPayload(err)
return bucketApi.NewListExternalBucketsDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewListExternalBucketsOK().WithPayload(response)
@@ -101,7 +103,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketDeleteBucketReplicationRuleHandler = bucketApi.DeleteBucketReplicationRuleHandlerFunc(func(params bucketApi.DeleteBucketReplicationRuleParams, session *models.Principal) middleware.Responder {
err := deleteReplicationRuleResponse(session, params)
if err != nil {
return bucketApi.NewDeleteBucketReplicationRuleDefault(int(err.Code)).WithPayload(err)
return bucketApi.NewDeleteBucketReplicationRuleDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewDeleteBucketReplicationRuleNoContent()
@@ -111,9 +113,14 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketDeleteAllReplicationRulesHandler = bucketApi.DeleteAllReplicationRulesHandlerFunc(func(params bucketApi.DeleteAllReplicationRulesParams, session *models.Principal) middleware.Responder {
err := deleteBucketReplicationRulesResponse(session, params)
if err != nil {
return bucketApi.NewDeleteAllReplicationRulesDefault(int(err.Code)).WithPayload(err)
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()
})
@@ -121,7 +128,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketDeleteSelectedReplicationRulesHandler = bucketApi.DeleteSelectedReplicationRulesHandlerFunc(func(params bucketApi.DeleteSelectedReplicationRulesParams, session *models.Principal) middleware.Responder {
err := deleteSelectedReplicationRulesResponse(session, params)
if err != nil {
return bucketApi.NewDeleteSelectedReplicationRulesDefault(int(err.Code)).WithPayload(err)
return bucketApi.NewDeleteSelectedReplicationRulesDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewDeleteSelectedReplicationRulesNoContent()
@@ -131,49 +138,38 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketUpdateMultiBucketReplicationHandler = bucketApi.UpdateMultiBucketReplicationHandlerFunc(func(params bucketApi.UpdateMultiBucketReplicationParams, session *models.Principal) middleware.Responder {
err := updateBucketReplicationResponse(session, params)
if err != nil {
return bucketApi.NewUpdateMultiBucketReplicationDefault(int(err.Code)).WithPayload(err)
return bucketApi.NewUpdateMultiBucketReplicationDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewUpdateMultiBucketReplicationCreated()
})
}
func getListRemoteBucketsResponse(session *models.Principal, params bucketApi.ListRemoteBucketsParams) (*models.ListRemoteBucketsResponse, *models.Error) {
func getListRemoteBucketsResponse(session *models.Principal, params bucketApi.ListRemoteBucketsParams) (*models.ListRemoteBucketsResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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}
buckets, err := listRemoteBuckets(ctx, adminClient)
if err != nil {
return nil, ErrorWithContext(ctx, fmt.Errorf("error listing remote buckets: %v", err))
}
return &models.ListRemoteBucketsResponse{
Buckets: buckets,
Total: int64(len(buckets)),
}, nil
return listRemoteBuckets(ctx, adminClient)
}
func getRemoteBucketDetailsResponse(session *models.Principal, params bucketApi.RemoteBucketDetailsParams) (*models.RemoteBucket, *models.Error) {
func getRemoteBucketDetailsResponse(session *models.Principal, params bucketApi.RemoteBucketDetailsParams) (*models.RemoteBucket, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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}
bucket, err := getRemoteBucket(ctx, adminClient, params.Name)
if err != nil {
return nil, ErrorWithContext(ctx, fmt.Errorf("error getting remote bucket details: %v", err))
}
return bucket, nil
return getRemoteBucket(ctx, adminClient, params.Name)
}
func getDeleteRemoteBucketResponse(session *models.Principal, params bucketApi.DeleteRemoteBucketParams) *models.Error {
func getDeleteRemoteBucketResponse(session *models.Principal, params bucketApi.DeleteRemoteBucketParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
}
@@ -185,10 +181,10 @@ func getDeleteRemoteBucketResponse(session *models.Principal, params bucketApi.D
return nil
}
func getAddRemoteBucketResponse(session *models.Principal, params bucketApi.AddRemoteBucketParams) *models.Error {
func getAddRemoteBucketResponse(session *models.Principal, params bucketApi.AddRemoteBucketParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
}
@@ -200,11 +196,11 @@ func getAddRemoteBucketResponse(session *models.Principal, params bucketApi.AddR
return nil
}
func listRemoteBuckets(ctx context.Context, client MinioAdmin) ([]*models.RemoteBucket, error) {
func listRemoteBuckets(ctx context.Context, client MinioAdmin) (*models.ListRemoteBucketsResponse, *CodedAPIError) {
var remoteBuckets []*models.RemoteBucket
buckets, err := client.listRemoteBuckets(ctx, "", "")
if err != nil {
return nil, err
return nil, ErrorWithContext(ctx, fmt.Errorf("error listing remote buckets: %v", err))
}
for _, bucket := range buckets {
remoteBucket := &models.RemoteBucket{
@@ -225,16 +221,20 @@ func listRemoteBuckets(ctx context.Context, client MinioAdmin) ([]*models.Remote
}
remoteBuckets = append(remoteBuckets, remoteBucket)
}
return remoteBuckets, nil
return &models.ListRemoteBucketsResponse{
Buckets: remoteBuckets,
Total: int64(len(remoteBuckets)),
}, nil
}
func getRemoteBucket(ctx context.Context, client MinioAdmin, name string) (*models.RemoteBucket, error) {
func getRemoteBucket(ctx context.Context, client MinioAdmin, name string) (*models.RemoteBucket, *CodedAPIError) {
remoteBucket, err := client.getRemoteBucket(ctx, name, "")
if err != nil {
return nil, err
return nil, ErrorWithContext(ctx, fmt.Errorf("error getting remote bucket details: %v", err))
}
if remoteBucket == nil {
return nil, errors.New("bucket not found")
return nil, ErrorWithContext(ctx, "error getting remote bucket details: bucket not found")
}
return &models.RemoteBucket{
AccessKey: &remoteBucket.Credentials.AccessKey,
@@ -312,8 +312,8 @@ func addBucketReplicationItem(ctx context.Context, session *models.Principal, mi
} else { // User picked priority, we try to set this manually
maxPrio = int(priority)
}
s3Client, err := newS3BucketClient(session, bucketName, prefix)
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
@@ -367,7 +367,8 @@ func editBucketReplicationItem(ctx context.Context, session *models.Principal, m
maxPrio := int(priority)
s3Client, err := newS3BucketClient(session, bucketName, prefix)
clientIP := utils.ClientIPFromContext(ctx)
s3Client, err := newS3BucketClient(session, bucketName, prefix, clientIP)
if err != nil {
return fmt.Errorf("error creating S3Client: %v", err)
}
@@ -501,17 +502,17 @@ func setMultiBucketReplication(ctx context.Context, session *models.Principal, c
return resultsList
}
func setMultiBucketReplicationResponse(session *models.Principal, params bucketApi.SetMultiBucketReplicationParams) (*models.MultiBucketResponseState, *models.Error) {
func setMultiBucketReplicationResponse(session *models.Principal, params bucketApi.SetMultiBucketReplicationParams) (*models.MultiBucketResponseState, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
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)
mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest))
if err != nil {
return nil, ErrorWithContext(ctx, fmt.Errorf("error creating MinIO Client: %v", err))
}
@@ -544,27 +545,26 @@ func setMultiBucketReplicationResponse(session *models.Principal, params bucketA
return &resultsParsed, nil
}
func listExternalBucketsResponse(params bucketApi.ListExternalBucketsParams) (*models.ListBucketsResponse, *models.Error) {
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)
}
// create a minioClient interface implementation
// defining the client to be used
remoteClient := AdminClient{Client: remoteAdmin}
buckets, err := getAccountBuckets(ctx, remoteClient)
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)
}
// serialize output
listBucketsResponse := &models.ListBucketsResponse{
return &models.ListBucketsResponse{
Buckets: buckets,
Total: int64(len(buckets)),
}
return listBucketsResponse, nil
}, nil
}
func getARNFromID(conf *replication.Config, rule string) string {
@@ -591,7 +591,8 @@ func getARNsFromIDs(conf *replication.Config, rules []string) []string {
}
func deleteReplicationRule(ctx context.Context, session *models.Principal, bucketName, ruleID string) error {
mClient, err := newMinioClient(session)
clientIP := utils.ClientIPFromContext(ctx)
mClient, err := newMinioClient(session, clientIP)
if err != nil {
return fmt.Errorf("error creating MinIO Client: %v", err)
}
@@ -604,12 +605,11 @@ func deleteReplicationRule(ctx context.Context, session *models.Principal, bucke
ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
}
s3Client, err := newS3BucketClient(session, bucketName, "")
s3Client, err := newS3BucketClient(session, bucketName, "", clientIP)
if err != nil {
return fmt.Errorf("error creating S3Client: %v", err)
}
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(ctx, session)
if err != nil {
return fmt.Errorf("error creating Admin Client: %v", err)
}
@@ -639,15 +639,16 @@ func deleteReplicationRule(ctx context.Context, session *models.Principal, bucke
}
func deleteAllReplicationRules(ctx context.Context, session *models.Principal, bucketName string) error {
s3Client, err := newS3BucketClient(session, bucketName, "")
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)
mClient, err := newMinioClient(session, clientIP)
if err != nil {
return fmt.Errorf("error creating MinIO Client: %v", err)
}
@@ -660,7 +661,7 @@ func deleteAllReplicationRules(ctx context.Context, session *models.Principal, b
ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
}
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(ctx, session)
if err != nil {
return fmt.Errorf("error creating Admin Client: %v", err)
}
@@ -669,7 +670,7 @@ func deleteAllReplicationRules(ctx context.Context, session *models.Principal, b
err2 := mcClient.deleteAllReplicationRules(ctx)
if err2 != nil {
return err
return err2.ToGoError()
}
for i := range cfg.Rules {
@@ -683,7 +684,8 @@ func deleteAllReplicationRules(ctx context.Context, session *models.Principal, b
}
func deleteSelectedReplicationRules(ctx context.Context, session *models.Principal, bucketName string, rules []string) error {
mClient, err := newMinioClient(session)
clientIP := utils.ClientIPFromContext(ctx)
mClient, err := newMinioClient(session, clientIP)
if err != nil {
return fmt.Errorf("error creating MinIO Client: %v", err)
}
@@ -696,7 +698,7 @@ func deleteSelectedReplicationRules(ctx context.Context, session *models.Princip
ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
}
s3Client, err := newS3BucketClient(session, bucketName, "")
s3Client, err := newS3BucketClient(session, bucketName, "", clientIP)
if err != nil {
return fmt.Errorf("error creating S3Client: %v", err)
}
@@ -704,7 +706,7 @@ func deleteSelectedReplicationRules(ctx context.Context, session *models.Princip
// defining the client to be used
mcClient := mcClient{client: s3Client}
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(ctx, session)
if err != nil {
return fmt.Errorf("error creating Admin Client: %v", err)
}
@@ -731,10 +733,10 @@ func deleteSelectedReplicationRules(ctx context.Context, session *models.Princip
return nil
}
func deleteReplicationRuleResponse(session *models.Principal, params bucketApi.DeleteBucketReplicationRuleParams) *models.Error {
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)
@@ -742,10 +744,10 @@ func deleteReplicationRuleResponse(session *models.Principal, params bucketApi.D
return nil
}
func deleteBucketReplicationRulesResponse(session *models.Principal, params bucketApi.DeleteAllReplicationRulesParams) *models.Error {
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)
@@ -753,10 +755,12 @@ func deleteBucketReplicationRulesResponse(session *models.Principal, params buck
return nil
}
func deleteSelectedReplicationRulesResponse(session *models.Principal, params bucketApi.DeleteSelectedReplicationRulesParams) *models.Error {
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)
@@ -764,11 +768,11 @@ func deleteSelectedReplicationRulesResponse(session *models.Principal, params bu
return nil
}
func updateBucketReplicationResponse(session *models.Principal, params bucketApi.UpdateMultiBucketReplicationParams) *models.Error {
func updateBucketReplicationResponse(session *models.Principal, params bucketApi.UpdateMultiBucketReplicationParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest))
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -792,7 +796,6 @@ func updateBucketReplicationResponse(session *models.Principal, params bucketApi
params.Body.Tags,
params.Body.Priority,
params.Body.StorageClass)
if err != nil {
return ErrorWithContext(ctx, err)
}

View File

@@ -0,0 +1,386 @@
// 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

@@ -14,32 +14,32 @@
// 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 restapi
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/console/restapi/operations"
siteRepApi "github.com/minio/console/restapi/operations/site_replication"
"github.com/minio/madmin-go"
"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(int(err.Code)).WithPayload(err)
return siteRepApi.NewGetSiteReplicationStatusDefault(err.Code).WithPayload(err.APIError)
}
return siteRepApi.NewGetSiteReplicationStatusOK().WithPayload(rInfo)
})
}
func getSRStatusResponse(session *models.Principal, params siteRepApi.GetSiteReplicationStatusParams) (*models.SiteReplicationStatusResponse, *models.Error) {
func getSRStatusResponse(session *models.Principal, params siteRepApi.GetSiteReplicationStatusParams) (*models.SiteReplicationStatusResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}

View File

@@ -14,24 +14,24 @@
// 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 restapi
package api
import (
"context"
"time"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/api/operations"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
svcApi "github.com/minio/console/restapi/operations/service"
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(int(err.Code)).WithPayload(err)
return svcApi.NewRestartServiceDefault(err.Code).WithPayload(err.APIError)
}
return svcApi.NewRestartServiceNoContent()
})
@@ -59,10 +59,10 @@ func serviceRestart(ctx context.Context, client MinioAdmin) error {
}
// getRestartServiceResponse performs serviceRestart()
func getRestartServiceResponse(session *models.Principal, params svcApi.RestartServiceParams) *models.Error {
func getRestartServiceResponse(session *models.Principal, params svcApi.RestartServiceParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}

68
api/admin_service_test.go Normal file
View File

@@ -0,0 +1,68 @@
// 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

@@ -14,23 +14,24 @@
// 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 restapi
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/console/restapi/operations"
siteRepApi "github.com/minio/console/restapi/operations/site_replication"
"github.com/minio/madmin-go"
"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(int(err.Code)).WithPayload(err)
return siteRepApi.NewGetSiteReplicationInfoDefault(err.Code).WithPayload(err.APIError)
}
return siteRepApi.NewGetSiteReplicationInfoOK().WithPayload(rInfo)
})
@@ -38,7 +39,7 @@ func registerSiteReplicationHandler(api *operations.ConsoleAPI) {
api.SiteReplicationSiteReplicationInfoAddHandler = siteRepApi.SiteReplicationInfoAddHandlerFunc(func(params siteRepApi.SiteReplicationInfoAddParams, session *models.Principal) middleware.Responder {
eInfo, err := getSRAddResponse(session, params)
if err != nil {
return siteRepApi.NewSiteReplicationInfoAddDefault(int(err.Code)).WithPayload(err)
return siteRepApi.NewSiteReplicationInfoAddDefault(err.Code).WithPayload(err.APIError)
}
return siteRepApi.NewSiteReplicationInfoAddOK().WithPayload(eInfo)
})
@@ -46,7 +47,7 @@ func registerSiteReplicationHandler(api *operations.ConsoleAPI) {
api.SiteReplicationSiteReplicationRemoveHandler = siteRepApi.SiteReplicationRemoveHandlerFunc(func(params siteRepApi.SiteReplicationRemoveParams, session *models.Principal) middleware.Responder {
remRes, err := getSRRemoveResponse(session, params)
if err != nil {
return siteRepApi.NewSiteReplicationRemoveDefault(int(err.Code)).WithPayload(err)
return siteRepApi.NewSiteReplicationRemoveDefault(err.Code).WithPayload(err.APIError)
}
return siteRepApi.NewSiteReplicationRemoveNoContent().WithPayload(remRes)
})
@@ -54,16 +55,16 @@ func registerSiteReplicationHandler(api *operations.ConsoleAPI) {
api.SiteReplicationSiteReplicationEditHandler = siteRepApi.SiteReplicationEditHandlerFunc(func(params siteRepApi.SiteReplicationEditParams, session *models.Principal) middleware.Responder {
eInfo, err := getSREditResponse(session, params)
if err != nil {
return siteRepApi.NewSiteReplicationRemoveDefault(int(err.Code)).WithPayload(err)
return siteRepApi.NewSiteReplicationRemoveDefault(err.Code).WithPayload(err.APIError)
}
return siteRepApi.NewSiteReplicationEditOK().WithPayload(eInfo)
})
}
func getSRInfoResponse(session *models.Principal, params siteRepApi.GetSiteReplicationInfoParams) (*models.SiteReplicationInfoResponse, *models.Error) {
func getSRInfoResponse(session *models.Principal, params siteRepApi.GetSiteReplicationInfoParams) (*models.SiteReplicationInfoResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -76,10 +77,10 @@ func getSRInfoResponse(session *models.Principal, params siteRepApi.GetSiteRepli
return res, nil
}
func getSRAddResponse(session *models.Principal, params siteRepApi.SiteReplicationInfoAddParams) (*models.SiteReplicationAddResponse, *models.Error) {
func getSRAddResponse(session *models.Principal, params siteRepApi.SiteReplicationInfoAddParams) (*models.SiteReplicationAddResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -92,10 +93,10 @@ func getSRAddResponse(session *models.Principal, params siteRepApi.SiteReplicati
return res, nil
}
func getSREditResponse(session *models.Principal, params siteRepApi.SiteReplicationEditParams) (*models.PeerSiteEditResponse, *models.Error) {
func getSREditResponse(session *models.Principal, params siteRepApi.SiteReplicationEditParams) (*models.PeerSiteEditResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -107,10 +108,10 @@ func getSREditResponse(session *models.Principal, params siteRepApi.SiteReplicat
return eRes, nil
}
func getSRRemoveResponse(session *models.Principal, params siteRepApi.SiteReplicationRemoveParams) (*models.PeerSiteRemoveResponse, *models.Error) {
func getSRRemoveResponse(session *models.Principal, params siteRepApi.SiteReplicationRemoveParams) (*models.PeerSiteRemoveResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -162,7 +163,13 @@ func addSiteReplication(ctx context.Context, client MinioAdmin, params *siteRepA
rSites = append(rSites, *pInfo)
}
}
cc, err := client.addSiteReplicationInfo(ctx, rSites)
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
}
@@ -183,7 +190,17 @@ func editSiteReplication(ctx context.Context, client MinioAdmin, params *siteRep
Name: params.Body.Name, // does not get updated.
DeploymentID: params.Body.DeploymentID, // readonly
}
eRes, err := client.editSiteReplicationInfo(ctx, *peerSiteInfo)
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
}

View File

@@ -0,0 +1,255 @@
// 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

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -25,8 +25,8 @@ import (
"time"
"github.com/dustin/go-humanize"
"github.com/gorilla/websocket"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
"github.com/minio/websocket"
)
// getSpeedtesthOptionsFromReq gets duration, size & concurrent requests from a websocket
@@ -64,10 +64,6 @@ func getSpeedtestOptionsFromReq(req *http.Request) (*madmin.SpeedtestOpts, error
return nil, fmt.Errorf("unable to parse object size")
}
if size < 0 {
return nil, fmt.Errorf("size is expected to be atleast 0 bytes")
}
optionsSet.Size = int(size)
paramConcurrent := queryPairs.Get("concurrent")

430
api/admin_subnet.go Normal file
View File

@@ -0,0 +1,430 @@
// 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)
subnetHTTPClient := GetConsoleHTTPClient("", clientIP)
subnetKey, err := GetSubnetKeyFromMinIOConfig(ctx, minioClient)
if err != nil {
return nil, err
}
proxy := getSubnetProxy()
if subnetKey.Proxy != "" {
proxy = subnetKey.Proxy
}
if proxy != "" {
subnetProxyURL, err := url.Parse(proxy)
if err != nil {
return nil, err
}
subnetHTTPClient.Transport.(*ConsoleTransport).Transport.Proxy = http.ProxyURL(subnetProxyURL)
}
clientI := &xhttp.Client{
Client: subnetHTTPClient,
}
return clientI, 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
}

233
api/admin_subnet_test.go Normal file
View File

@@ -0,0 +1,233 @@
// 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

@@ -14,25 +14,19 @@
// 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 restapi
package api
import (
"context"
"encoding/base64"
"log"
"regexp"
"strconv"
"strings"
"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/console/restapi/operations"
tieringApi "github.com/minio/console/restapi/operations/tiering"
"github.com/minio/madmin-go"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/madmin-go/v3"
)
func registerAdminTiersHandlers(api *operations.ConsoleAPI) {
@@ -40,7 +34,7 @@ func registerAdminTiersHandlers(api *operations.ConsoleAPI) {
api.TieringTiersListHandler = tieringApi.TiersListHandlerFunc(func(params tieringApi.TiersListParams, session *models.Principal) middleware.Responder {
tierList, err := getTiersResponse(session, params)
if err != nil {
return tieringApi.NewTiersListDefault(int(err.Code)).WithPayload(err)
return tieringApi.NewTiersListDefault(err.Code).WithPayload(err.APIError)
}
return tieringApi.NewTiersListOK().WithPayload(tierList)
})
@@ -48,7 +42,7 @@ func registerAdminTiersHandlers(api *operations.ConsoleAPI) {
api.TieringAddTierHandler = tieringApi.AddTierHandlerFunc(func(params tieringApi.AddTierParams, session *models.Principal) middleware.Responder {
err := getAddTierResponse(session, params)
if err != nil {
return tieringApi.NewAddTierDefault(int(err.Code)).WithPayload(err)
return tieringApi.NewAddTierDefault(err.Code).WithPayload(err.APIError)
}
return tieringApi.NewAddTierCreated()
})
@@ -56,7 +50,7 @@ func registerAdminTiersHandlers(api *operations.ConsoleAPI) {
api.TieringGetTierHandler = tieringApi.GetTierHandlerFunc(func(params tieringApi.GetTierParams, session *models.Principal) middleware.Responder {
notifEndpoints, err := getGetTierResponse(session, params)
if err != nil {
return tieringApi.NewGetTierDefault(int(err.Code)).WithPayload(err)
return tieringApi.NewGetTierDefault(err.Code).WithPayload(err.APIError)
}
return tieringApi.NewGetTierOK().WithPayload(notifEndpoints)
})
@@ -64,7 +58,7 @@ func registerAdminTiersHandlers(api *operations.ConsoleAPI) {
api.TieringEditTierCredentialsHandler = tieringApi.EditTierCredentialsHandlerFunc(func(params tieringApi.EditTierCredentialsParams, session *models.Principal) middleware.Responder {
err := getEditTierCredentialsResponse(session, params)
if err != nil {
return tieringApi.NewEditTierCredentialsDefault(int(err.Code)).WithPayload(err)
return tieringApi.NewEditTierCredentialsDefault(err.Code).WithPayload(err.APIError)
}
return tieringApi.NewEditTierCredentialsOK()
})
@@ -97,7 +91,6 @@ func getTiers(ctx context.Context, client MinioAdmin) (*models.TierListResponse,
break
}
}
switch tierData.Type {
case madmin.S3:
tiersList = append(tiersList, &models.Tier{
@@ -115,7 +108,7 @@ func getTiers(ctx context.Context, client MinioAdmin) (*models.TierListResponse,
Objects: strconv.Itoa(stats.NumObjects),
Versions: strconv.Itoa(stats.NumVersions),
},
Status: false,
Status: client.verifyTierStatus(ctx, tierData.Name) == nil,
})
case madmin.MinIO:
tiersList = append(tiersList, &models.Tier{
@@ -132,7 +125,7 @@ func getTiers(ctx context.Context, client MinioAdmin) (*models.TierListResponse,
Objects: strconv.Itoa(stats.NumObjects),
Versions: strconv.Itoa(stats.NumVersions),
},
Status: checkTierStatus(tierData.MinIO.Endpoint, tierData.MinIO.AccessKey, tierData.MinIO.SecretKey, tierData.MinIO.Bucket),
Status: client.verifyTierStatus(ctx, tierData.Name) == nil,
})
case madmin.GCS:
tiersList = append(tiersList, &models.Tier{
@@ -148,7 +141,7 @@ func getTiers(ctx context.Context, client MinioAdmin) (*models.TierListResponse,
Objects: strconv.Itoa(stats.NumObjects),
Versions: strconv.Itoa(stats.NumVersions),
},
Status: false,
Status: client.verifyTierStatus(ctx, tierData.Name) == nil,
})
case madmin.Azure:
tiersList = append(tiersList, &models.Tier{
@@ -165,14 +158,13 @@ func getTiers(ctx context.Context, client MinioAdmin) (*models.TierListResponse,
Objects: strconv.Itoa(stats.NumObjects),
Versions: strconv.Itoa(stats.NumVersions),
},
Status: false,
Status: client.verifyTierStatus(ctx, tierData.Name) == nil,
})
case madmin.Unsupported:
tiersList = append(tiersList, &models.Tier{
Type: models.TierTypeUnsupported,
Status: false,
Status: client.verifyTierStatus(ctx, tierData.Name) == nil,
})
}
}
// build response
@@ -182,10 +174,10 @@ func getTiers(ctx context.Context, client MinioAdmin) (*models.TierListResponse,
}
// getTiersResponse returns a response with a list of tiers
func getTiersResponse(session *models.Principal, params tieringApi.TiersListParams) (*models.TierListResponse, *models.Error) {
func getTiersResponse(session *models.Principal, params tieringApi.TiersListParams) (*models.TierListResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -284,10 +276,10 @@ func addTier(ctx context.Context, client MinioAdmin, params *tieringApi.AddTierP
}
// getAddTierResponse returns the response of admin tier
func getAddTierResponse(session *models.Principal, params tieringApi.AddTierParams) *models.Error {
func getAddTierResponse(session *models.Principal, params tieringApi.AddTierParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -366,10 +358,10 @@ func getTier(ctx context.Context, client MinioAdmin, params *tieringApi.GetTierP
}
// getGetTierResponse returns a tier
func getGetTierResponse(session *models.Principal, params tieringApi.GetTierParams) (*models.Tier, *models.Error) {
func getGetTierResponse(session *models.Principal, params tieringApi.GetTierParams) (*models.Tier, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -400,10 +392,10 @@ func editTierCredentials(ctx context.Context, client MinioAdmin, params *tiering
}
// getEditTierCredentialsResponse returns the result of editing credentials for a tier
func getEditTierCredentialsResponse(session *models.Principal, params tieringApi.EditTierCredentialsParams) *models.Error {
func getEditTierCredentialsResponse(session *models.Principal, params tieringApi.EditTierCredentialsParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -417,23 +409,3 @@ func getEditTierCredentialsResponse(session *models.Principal, params tieringApi
}
return nil
}
func checkTierStatus(endpoint string, accessKey string, secretKey string, bucketName string) bool {
// Initialize minio client object.
re := regexp.MustCompile(`(^\w+:|^)\/\/`)
s := re.ReplaceAllString(endpoint, "")
minioClient, err := minio.New(s, &minio.Options{
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
Secure: false,
})
if err != nil {
log.Println(err)
return false
}
bucketTest, err := minioClient.BucketExists(context.Background(), bucketName)
if err != nil {
log.Println(err)
return strings.Contains(err.Error(), "The request signature we calculated does not match the signature you provided. Check your key and signing method.")
}
return bucketTest
}

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -22,48 +22,16 @@ import (
"fmt"
"testing"
tieringApi "github.com/minio/console/api/operations/tiering"
"github.com/minio/console/models"
tieringApi "github.com/minio/console/restapi/operations/tiering"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
)
// assigning mock at runtime instead of compile time
var minioListTiersMock func(ctx context.Context) ([]*madmin.TierConfig, error)
// mock function of listTiers()
func (ac adminClientMock) listTiers(ctx context.Context) ([]*madmin.TierConfig, error) {
return minioListTiersMock(ctx)
}
// assigning mock at runtime instead of compile time
var minioTierStatsMock func(ctx context.Context) ([]madmin.TierInfo, error)
// mock function of tierStats()
func (ac adminClientMock) tierStats(ctx context.Context) ([]madmin.TierInfo, error) {
return minioTierStatsMock(ctx)
}
// assigning mock at runtime instead of compile time
var minioAddTiersMock func(ctx context.Context, tier *madmin.TierConfig) error
// mock function of addTier()
func (ac adminClientMock) addTier(ctx context.Context, tier *madmin.TierConfig) error {
return minioAddTiersMock(ctx, tier)
}
// assigning mock at runtime instead of compile time
var minioEditTiersMock func(ctx context.Context, tierName string, creds madmin.TierCreds) error
// mock function of editTierCreds()
func (ac adminClientMock) editTierCreds(ctx context.Context, tierName string, creds madmin.TierCreds) error {
return minioEditTiersMock(ctx, tierName, creds)
}
func TestGetTiers(t *testing.T) {
assert := assert.New(t)
// mock minIO client
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
function := "getTiers()"
ctx, cancel := context.WithCancel(context.Background())
@@ -121,11 +89,11 @@ func TestGetTiers(t *testing.T) {
},
}
minioListTiersMock = func(ctx context.Context) ([]*madmin.TierConfig, error) {
minioListTiersMock = func(_ context.Context) ([]*madmin.TierConfig, error) {
return returnListMock, nil
}
minioTierStatsMock = func(ctx context.Context) ([]madmin.TierInfo, error) {
minioTierStatsMock = func(_ context.Context) ([]madmin.TierInfo, error) {
return returnStatsMock, nil
}
@@ -170,7 +138,7 @@ func TestGetTiers(t *testing.T) {
// Test-2 : getBucketLifecycle() list is empty
returnListMockT2 := []*madmin.TierConfig{}
minioListTiersMock = func(ctx context.Context) ([]*madmin.TierConfig, error) {
minioListTiersMock = func(_ context.Context) ([]*madmin.TierConfig, error) {
return returnListMockT2, nil
}
@@ -187,13 +155,13 @@ func TestGetTiers(t *testing.T) {
func TestAddTier(t *testing.T) {
assert := assert.New(t)
// mock minIO client
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
function := "addTier()"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1: addTier() add new Tier
minioAddTiersMock = func(ctx context.Context, tier *madmin.TierConfig) error {
minioAddTiersMock = func(_ context.Context, _ *madmin.TierConfig) error {
return nil
}
@@ -217,7 +185,7 @@ func TestAddTier(t *testing.T) {
assert.Equal(nil, err, fmt.Sprintf("Failed on %s: Error returned", function))
// Test-2: addTier() error adding Tier
minioAddTiersMock = func(ctx context.Context, tier *madmin.TierConfig) error {
minioAddTiersMock = func(_ context.Context, _ *madmin.TierConfig) error {
return errors.New("error setting new tier")
}
@@ -229,13 +197,13 @@ func TestAddTier(t *testing.T) {
func TestUpdateTierCreds(t *testing.T) {
assert := assert.New(t)
// mock minIO client
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
function := "editTierCredentials()"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1: editTierCredentials() update Tier configuration
minioEditTiersMock = func(ctx context.Context, tierName string, creds madmin.TierCreds) error {
minioEditTiersMock = func(_ context.Context, _ string, _ madmin.TierCreds) error {
return nil
}
@@ -252,7 +220,7 @@ func TestUpdateTierCreds(t *testing.T) {
assert.Equal(nil, err, fmt.Sprintf("Failed on %s: Error returned", function))
// Test-2: editTierCredentials() update Tier configuration failure
minioEditTiersMock = func(ctx context.Context, tierName string, creds madmin.TierCreds) error {
minioEditTiersMock = func(_ context.Context, _ string, _ madmin.TierCreds) error {
return errors.New("error message")
}

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -23,8 +23,8 @@ import (
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
"github.com/minio/websocket"
)
// shortTraceMsg Short trace record

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -22,21 +22,13 @@ import (
"fmt"
"testing"
"github.com/minio/madmin-go"
"github.com/minio/madmin-go/v3"
"github.com/stretchr/testify/assert"
)
// assigning mock at runtime instead of compile time
var minioServiceTraceMock func(ctx context.Context, threshold int64, s3, internal, storage, os, errTrace bool) <-chan madmin.ServiceTraceInfo
// mock function of listPolicies()
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 TestAdminTrace(t *testing.T) {
assert := assert.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
mockWSConn := mockConn{}
function := "startTraceInfo(ctx, )"
ctx, cancel := context.WithCancel(context.Background())
@@ -49,7 +41,7 @@ func TestAdminTrace(t *testing.T) {
// Test-1: Serve Trace with no errors until trace finishes sending
// define mock function behavior for minio server Trace
minioServiceTraceMock = func(ctx context.Context, threshold int64, s3, internal, storage, os, errTrace bool) <-chan madmin.ServiceTraceInfo {
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) {
@@ -67,7 +59,7 @@ func TestAdminTrace(t *testing.T) {
}
writesCount := 1
// mock connection WriteMessage() no error
connWriteMessageMock = func(messageType int, data []byte) error {
connWriteMessageMock = func(_ int, data []byte) error {
// emulate that receiver gets the message written
var t shortTraceMsg
_ = json.Unmarshal(data, &t)
@@ -92,7 +84,7 @@ func TestAdminTrace(t *testing.T) {
}
// Test-2: if error happens while writing, return error
connWriteMessageMock = func(messageType int, data []byte) error {
connWriteMessageMock = func(_ int, _ []byte) error {
return fmt.Errorf("error on write")
}
if err := startTraceInfo(ctx, mockWSConn, adminClient, TraceRequest{}); assert.Error(err) {
@@ -101,7 +93,7 @@ func TestAdminTrace(t *testing.T) {
// Test-3: error happens on serviceTrace Minio, trace should stop
// and error shall be returned.
minioServiceTraceMock = func(ctx context.Context, threshold int64, s3, internal, storage, os, errTrace bool) <-chan madmin.ServiceTraceInfo {
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) {
@@ -118,7 +110,7 @@ func TestAdminTrace(t *testing.T) {
}(ch)
return ch
}
connWriteMessageMock = func(messageType int, data []byte) error {
connWriteMessageMock = func(_ int, _ []byte) error {
return nil
}
if err := startTraceInfo(ctx, mockWSConn, adminClient, TraceRequest{}); assert.Error(err) {

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -24,17 +24,15 @@ import (
"github.com/minio/console/pkg/utils"
"github.com/go-openapi/swag"
"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/console/restapi/operations"
accountApi "github.com/minio/console/restapi/operations/account"
bucketApi "github.com/minio/console/restapi/operations/bucket"
userApi "github.com/minio/console/restapi/operations/user"
"github.com/minio/madmin-go"
iampolicy "github.com/minio/pkg/iam/policy"
"github.com/minio/madmin-go/v3"
iampolicy "github.com/minio/pkg/v2/policy"
)
// Policy evaluated constants
@@ -49,7 +47,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.UserListUsersHandler = userApi.ListUsersHandlerFunc(func(params userApi.ListUsersParams, session *models.Principal) middleware.Responder {
listUsersResponse, err := getListUsersResponse(session, params)
if err != nil {
return userApi.NewListUsersDefault(int(err.Code)).WithPayload(err)
return userApi.NewListUsersDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewListUsersOK().WithPayload(listUsersResponse)
})
@@ -57,7 +55,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.UserAddUserHandler = userApi.AddUserHandlerFunc(func(params userApi.AddUserParams, session *models.Principal) middleware.Responder {
userResponse, err := getUserAddResponse(session, params)
if err != nil {
return userApi.NewAddUserDefault(int(err.Code)).WithPayload(err)
return userApi.NewAddUserDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewAddUserCreated().WithPayload(userResponse)
})
@@ -65,7 +63,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.UserRemoveUserHandler = userApi.RemoveUserHandlerFunc(func(params userApi.RemoveUserParams, session *models.Principal) middleware.Responder {
err := getRemoveUserResponse(session, params)
if err != nil {
return userApi.NewRemoveUserDefault(int(err.Code)).WithPayload(err)
return userApi.NewRemoveUserDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewRemoveUserNoContent()
})
@@ -73,7 +71,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.UserUpdateUserGroupsHandler = userApi.UpdateUserGroupsHandlerFunc(func(params userApi.UpdateUserGroupsParams, session *models.Principal) middleware.Responder {
userUpdateResponse, err := getUpdateUserGroupsResponse(session, params)
if err != nil {
return userApi.NewUpdateUserGroupsDefault(int(err.Code)).WithPayload(err)
return userApi.NewUpdateUserGroupsDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewUpdateUserGroupsOK().WithPayload(userUpdateResponse)
@@ -82,7 +80,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.UserGetUserInfoHandler = userApi.GetUserInfoHandlerFunc(func(params userApi.GetUserInfoParams, session *models.Principal) middleware.Responder {
userInfoResponse, err := getUserInfoResponse(session, params)
if err != nil {
return userApi.NewGetUserInfoDefault(int(err.Code)).WithPayload(err)
return userApi.NewGetUserInfoDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewGetUserInfoOK().WithPayload(userInfoResponse)
@@ -91,7 +89,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.UserUpdateUserInfoHandler = userApi.UpdateUserInfoHandlerFunc(func(params userApi.UpdateUserInfoParams, session *models.Principal) middleware.Responder {
userUpdateResponse, err := getUpdateUserResponse(session, params)
if err != nil {
return userApi.NewUpdateUserInfoDefault(int(err.Code)).WithPayload(err)
return userApi.NewUpdateUserInfoDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewUpdateUserInfoOK().WithPayload(userUpdateResponse)
@@ -100,7 +98,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.UserBulkUpdateUsersGroupsHandler = userApi.BulkUpdateUsersGroupsHandlerFunc(func(params userApi.BulkUpdateUsersGroupsParams, session *models.Principal) middleware.Responder {
err := getAddUsersListToGroupsResponse(session, params)
if err != nil {
return userApi.NewBulkUpdateUsersGroupsDefault(int(err.Code)).WithPayload(err)
return userApi.NewBulkUpdateUsersGroupsDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewBulkUpdateUsersGroupsOK()
@@ -108,7 +106,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.BucketListUsersWithAccessToBucketHandler = bucketApi.ListUsersWithAccessToBucketHandlerFunc(func(params bucketApi.ListUsersWithAccessToBucketParams, session *models.Principal) middleware.Responder {
response, err := getListUsersWithAccessToBucketResponse(session, params)
if err != nil {
return bucketApi.NewListUsersWithAccessToBucketDefault(int(err.Code)).WithPayload(err)
return bucketApi.NewListUsersWithAccessToBucketDefault(err.Code).WithPayload(err.APIError)
}
return bucketApi.NewListUsersWithAccessToBucketOK().WithPayload(response)
})
@@ -116,7 +114,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.AccountChangeUserPasswordHandler = accountApi.ChangeUserPasswordHandlerFunc(func(params accountApi.ChangeUserPasswordParams, session *models.Principal) middleware.Responder {
err := getChangeUserPasswordResponse(session, params)
if err != nil {
return accountApi.NewChangeUserPasswordDefault(int(err.Code)).WithPayload(err)
return accountApi.NewChangeUserPasswordDefault(err.Code).WithPayload(err.APIError)
}
return accountApi.NewChangeUserPasswordCreated()
})
@@ -124,7 +122,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
api.UserCheckUserServiceAccountsHandler = userApi.CheckUserServiceAccountsHandlerFunc(func(params userApi.CheckUserServiceAccountsParams, session *models.Principal) middleware.Responder {
userSAList, err := getCheckUserSAResponse(session, params)
if err != nil {
return userApi.NewCheckUserServiceAccountsDefault(int(err.Code)).WithPayload(err)
return userApi.NewCheckUserServiceAccountsDefault(err.Code).WithPayload(err.APIError)
}
return userApi.NewCheckUserServiceAccountsOK().WithPayload(userSAList)
})
@@ -154,10 +152,10 @@ func listUsers(ctx context.Context, client MinioAdmin) ([]*models.User, error) {
}
// getListUsersResponse performs listUsers() and serializes it to the handler's output
func getListUsersResponse(session *models.Principal, params userApi.ListUsersParams) (*models.ListUsersResponse, *models.Error) {
func getListUsersResponse(session *models.Principal, params userApi.ListUsersParams) (*models.ListUsersResponse, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -215,10 +213,10 @@ func addUser(ctx context.Context, client MinioAdmin, accessKey, secretKey *strin
return userRet, nil
}
func getUserAddResponse(session *models.Principal, params userApi.AddUserParams) (*models.User, *models.Error) {
func getUserAddResponse(session *models.Principal, params userApi.AddUserParams) (*models.User, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -252,10 +250,10 @@ func removeUser(ctx context.Context, client MinioAdmin, accessKey string) error
return client.removeUser(ctx, accessKey)
}
func getRemoveUserResponse(session *models.Principal, params userApi.RemoveUserParams) *models.Error {
func getRemoveUserResponse(session *models.Principal, params userApi.RemoveUserParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -284,11 +282,11 @@ func getUserInfo(ctx context.Context, client MinioAdmin, accessKey string) (*mad
return &userInfo, nil
}
func getUserInfoResponse(session *models.Principal, params userApi.GetUserInfoParams) (*models.User, *models.Error) {
func getUserInfoResponse(session *models.Principal, params userApi.GetUserInfoParams) (*models.User, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -306,9 +304,9 @@ func getUserInfoResponse(session *models.Principal, params userApi.GetUserInfoPa
if err != nil {
// User doesn't exist, return 404
if madmin.ToErrorResponse(err).Code == "XMinioAdminNoSuchUser" {
var errorCode int32 = 404
errorCode := 404
errorMessage := "User doesn't exist"
return nil, &models.Error{Code: errorCode, Message: swag.String(errorMessage), DetailedMessage: swag.String(err.Error())}
return nil, &CodedAPIError{Code: errorCode, APIError: &models.APIError{Message: errorMessage, DetailedMessage: err.Error()}}
}
return nil, ErrorWithContext(ctx, err)
}
@@ -435,11 +433,11 @@ func updateUserGroups(ctx context.Context, client MinioAdmin, user string, group
return userReturn, nil
}
func getUpdateUserGroupsResponse(session *models.Principal, params userApi.UpdateUserGroupsParams) (*models.User, *models.Error) {
func getUpdateUserGroupsResponse(session *models.Principal, params userApi.UpdateUserGroupsParams) (*models.User, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -476,11 +474,11 @@ func setUserStatus(ctx context.Context, client MinioAdmin, user string, status s
return client.setUserStatus(ctx, user, setStatus)
}
func getUpdateUserResponse(session *models.Principal, params userApi.UpdateUserInfoParams) (*models.User, *models.Error) {
func getUpdateUserResponse(session *models.Principal, params userApi.UpdateUserInfoParams) (*models.User, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -550,11 +548,11 @@ func addUsersListToGroups(ctx context.Context, client MinioAdmin, usersToUpdate
return nil
}
func getAddUsersListToGroupsResponse(session *models.Principal, params userApi.BulkUpdateUsersGroupsParams) *models.Error {
func getAddUsersListToGroupsResponse(session *models.Principal, params userApi.BulkUpdateUsersGroupsParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -573,10 +571,10 @@ func getAddUsersListToGroupsResponse(session *models.Principal, params userApi.B
return nil
}
func getListUsersWithAccessToBucketResponse(session *models.Principal, params bucketApi.ListUsersWithAccessToBucketParams) ([]string, *models.Error) {
func getListUsersWithAccessToBucketResponse(session *models.Principal, params bucketApi.ListUsersWithAccessToBucketParams) ([]string, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
@@ -684,10 +682,10 @@ func changeUserPassword(ctx context.Context, client MinioAdmin, selectedUser str
}
// getChangeUserPasswordResponse will change the password of selctedUser to newSecretKey
func getChangeUserPasswordResponse(session *models.Principal, params accountApi.ChangeUserPasswordParams) *models.Error {
func getChangeUserPasswordResponse(session *models.Principal, params accountApi.ChangeUserPasswordParams) *CodedAPIError {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return ErrorWithContext(ctx, err)
}
@@ -706,10 +704,10 @@ func getChangeUserPasswordResponse(session *models.Principal, params accountApi.
return nil
}
func getCheckUserSAResponse(session *models.Principal, params userApi.CheckUserServiceAccountsParams) (*models.UserServiceAccountSummary, *models.Error) {
func getCheckUserSAResponse(session *models.Principal, params userApi.CheckUserServiceAccountsParams) (*models.UserServiceAccountSummary, *CodedAPIError) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"bytes"
@@ -24,49 +24,14 @@ import (
"strings"
"testing"
"github.com/minio/madmin-go"
iampolicy "github.com/minio/pkg/iam/policy"
"github.com/minio/madmin-go/v3"
iampolicy "github.com/minio/pkg/v2/policy"
asrt "github.com/stretchr/testify/assert"
)
// assigning mock at runtime instead of compile time
var minioListUsersMock func() (map[string]madmin.UserInfo, error)
var (
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
)
// mock function of listUsers()
func (ac adminClientMock) listUsers(ctx context.Context) (map[string]madmin.UserInfo, error) {
return minioListUsersMock()
}
// mock function of addUser()
func (ac adminClientMock) addUser(ctx context.Context, accessKey, secretKey string) error {
return minioAddUserMock(accessKey, secretKey)
}
// mock function of removeUser()
func (ac adminClientMock) removeUser(ctx context.Context, accessKey string) error {
return minioRemoveUserMock(accessKey)
}
// mock function of getUserInfo()
func (ac adminClientMock) getUserInfo(ctx context.Context, accessKey string) (madmin.UserInfo, error) {
return minioGetUserInfoMock(accessKey)
}
// mock function of setUserStatus()
func (ac adminClientMock) setUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) error {
return minioSetUserStatusMock(accessKey, status)
}
func TestListUsers(t *testing.T) {
assert := asrt.New(t)
adminClient := adminClientMock{}
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()
@@ -120,7 +85,7 @@ func TestListUsers(t *testing.T) {
func TestAddUser(t *testing.T) {
assert := asrt.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Test-1: valid case of adding a user with a proper access key
@@ -137,15 +102,15 @@ func TestAddUser(t *testing.T) {
}
// mock function response from addUser() return no error
minioAddUserMock = func(accessKey, secretKey string) error {
minioAddUserMock = func(_, _ string) error {
return nil
}
minioGetUserInfoMock = func(accessKey string) (madmin.UserInfo, error) {
minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) {
return *mockResponse, nil
}
minioUpdateGroupMembersMock = func(remove madmin.GroupAddRemove) error {
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return nil
}
// Test-1: Add a user
@@ -170,7 +135,7 @@ func TestAddUser(t *testing.T) {
accessKey = "AB"
secretKey = "ABCDEFGHIABCDEFGHI"
// mock function response from addUser() return no error
minioAddUserMock = func(accessKey, secretKey string) error {
minioAddUserMock = func(_, _ string) error {
return errors.New("error")
}
@@ -185,7 +150,7 @@ func TestAddUser(t *testing.T) {
}
// Test-4: add groups function returns an error
minioUpdateGroupMembersMock = func(remove madmin.GroupAddRemove) error {
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return errors.New("error")
}
@@ -203,14 +168,14 @@ func TestAddUser(t *testing.T) {
func TestRemoveUser(t *testing.T) {
assert := asrt.New(t)
// mock minIO client
adminClient := adminClientMock{}
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(accessKey string) error {
minioRemoveUserMock = func(_ string) error {
return nil
}
@@ -220,7 +185,7 @@ func TestRemoveUser(t *testing.T) {
// Test-2: removeUser() make sure errors are handled correctly when error on DeleteUser()
// mock function response from removeUser(accessKey)
minioRemoveUserMock = func(accessKey string) error {
minioRemoveUserMock = func(_ string) error {
return errors.New("error")
}
@@ -232,7 +197,7 @@ func TestRemoveUser(t *testing.T) {
func TestUserGroups(t *testing.T) {
assert := asrt.New(t)
// mock minIO client
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -255,11 +220,11 @@ func TestUserGroups(t *testing.T) {
// Test-1: updateUserGroups() updates the groups for a user
// mock function response from updateUserGroups(accessKey, groupsToAssign)
minioGetUserInfoMock = func(accessKey string) (madmin.UserInfo, error) {
minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) {
return *mockResponse, nil
}
minioUpdateGroupMembersMock = func(remove madmin.GroupAddRemove) error {
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return nil
}
@@ -270,7 +235,7 @@ func TestUserGroups(t *testing.T) {
// Test-2: updateUserGroups() make sure errors are handled correctly when error on UpdateGroupMembersMock()
// mock function response from removeUser(accessKey)
minioUpdateGroupMembersMock = func(remove madmin.GroupAddRemove) error {
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return errors.New("error")
}
@@ -279,11 +244,11 @@ func TestUserGroups(t *testing.T) {
}
// Test-3: updateUserGroups() make sure we return the correct error when getUserInfo returns error
minioGetUserInfoMock = func(accessKey string) (madmin.UserInfo, error) {
minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) {
return *mockEmptyResponse, errors.New("error getting user ")
}
minioUpdateGroupMembersMock = func(remove madmin.GroupAddRemove) error {
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return nil
}
@@ -294,7 +259,7 @@ func TestUserGroups(t *testing.T) {
func TestGetUserInfo(t *testing.T) {
assert := asrt.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -314,7 +279,7 @@ func TestGetUserInfo(t *testing.T) {
}
// mock function response from getUserInfo()
minioGetUserInfoMock = func(username string) (madmin.UserInfo, error) {
minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) {
return *mockResponse, nil
}
function := "getUserInfo()"
@@ -329,7 +294,7 @@ func TestGetUserInfo(t *testing.T) {
assert.Equal(mockResponse.Status, info.Status)
// Test-2 : getUserInfo() Return error and see that the error is handled correctly and returned
minioGetUserInfoMock = func(username string) (madmin.UserInfo, error) {
minioGetUserInfoMock = func(_ string) (madmin.UserInfo, error) {
return *emptyMockResponse, errors.New("error")
}
_, err = getUserInfo(ctx, adminClient, userName)
@@ -340,7 +305,7 @@ func TestGetUserInfo(t *testing.T) {
func TestSetUserStatus(t *testing.T) {
assert := asrt.New(t)
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
function := "setUserStatus()"
userName := "userName123"
ctx, cancel := context.WithCancel(context.Background())
@@ -348,7 +313,7 @@ func TestSetUserStatus(t *testing.T) {
// Test-1: setUserStatus() update valid disabled status
expectedStatus := "disabled"
minioSetUserStatusMock = func(accessKey string, status madmin.AccountStatus) error {
minioSetUserStatusMock = func(_ string, _ madmin.AccountStatus) error {
return nil
}
if err := setUserStatus(ctx, adminClient, userName, expectedStatus); err != nil {
@@ -356,7 +321,7 @@ func TestSetUserStatus(t *testing.T) {
}
// Test-2: setUserStatus() update valid enabled status
expectedStatus = "enabled"
minioSetUserStatusMock = func(accessKey string, status madmin.AccountStatus) error {
minioSetUserStatusMock = func(_ string, _ madmin.AccountStatus) error {
return nil
}
if err := setUserStatus(ctx, adminClient, userName, expectedStatus); err != nil {
@@ -364,7 +329,7 @@ func TestSetUserStatus(t *testing.T) {
}
// Test-3: setUserStatus() update invalid status, should send error
expectedStatus = "invalid"
minioSetUserStatusMock = func(accessKey string, status madmin.AccountStatus) error {
minioSetUserStatusMock = func(_ string, _ madmin.AccountStatus) error {
return nil
}
if err := setUserStatus(ctx, adminClient, userName, expectedStatus); assert.Error(err) {
@@ -372,7 +337,7 @@ func TestSetUserStatus(t *testing.T) {
}
// Test-4: setUserStatus() handler error correctly
expectedStatus = "enabled"
minioSetUserStatusMock = func(accessKey string, status madmin.AccountStatus) error {
minioSetUserStatusMock = func(_ string, _ madmin.AccountStatus) error {
return errors.New("error")
}
if err := setUserStatus(ctx, adminClient, userName, expectedStatus); assert.Error(err) {
@@ -383,7 +348,7 @@ func TestSetUserStatus(t *testing.T) {
func TestUserGroupsBulk(t *testing.T) {
assert := asrt.New(t)
// mock minIO client
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -393,7 +358,7 @@ func TestUserGroupsBulk(t *testing.T) {
// Test-1: addUsersListToGroups() updates the groups for a users list
// mock function response from updateUserGroups(accessKey, groupsToAssign)
minioUpdateGroupMembersMock = func(remove madmin.GroupAddRemove) error {
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return nil
}
@@ -403,7 +368,7 @@ func TestUserGroupsBulk(t *testing.T) {
// Test-2: addUsersListToGroups() make sure errors are handled correctly when error on updateGroupMembers()
// mock function response from removeUser(accessKey)
minioUpdateGroupMembersMock = func(remove madmin.GroupAddRemove) error {
minioUpdateGroupMembersMock = func(_ madmin.GroupAddRemove) error {
return errors.New("error")
}
@@ -416,7 +381,7 @@ func TestListUsersWithAccessToBucket(t *testing.T) {
assert := asrt.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
adminClient := adminClientMock{}
adminClient := AdminClientMock{}
user1 := madmin.UserInfo{
SecretKey: "testtest",
PolicyName: "consoleAdmin,testPolicy,redundantPolicy",
@@ -562,7 +527,7 @@ func TestListUsersWithAccessToBucket(t *testing.T) {
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Run(tt.name, func(_ *testing.T) {
got, _ := listUsersWithAccessToBucket(ctx, adminClient, tt.args.bucket)
assert.Equal(got, tt.want)
})

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"bytes"
@@ -24,45 +24,19 @@ import (
"net"
"net/http"
"net/url"
"sync"
"strings"
"time"
"github.com/minio/console/pkg/utils"
"github.com/minio/console/models"
"github.com/minio/console/pkg"
"github.com/minio/madmin-go"
mcCmd "github.com/minio/mc/cmd"
"github.com/minio/mc/pkg/probe"
"github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7/pkg/credentials"
iampolicy "github.com/minio/pkg/iam/policy"
iampolicy "github.com/minio/pkg/v2/policy"
)
const globalAppName = "MinIO Console"
// NewAdminClientWithInsecure gives a new madmin client interface either secure or insecure based on parameter
func NewAdminClientWithInsecure(url, accessKey, secretKey, sessionToken string, insecure bool) (*madmin.AdminClient, *probe.Error) {
admClient, err := s3AdminNew(&mcCmd.Config{
HostURL: url,
AccessKey: accessKey,
SecretKey: secretKey,
SessionToken: sessionToken,
AppName: globalAppName,
AppVersion: pkg.Version,
Insecure: insecure,
})
if err != nil {
return nil, err.Trace(url)
}
stsClient := PrepareConsoleHTTPClient(insecure)
admClient.SetCustomTransport(stsClient.Transport)
// set user-agent to differentiate Console UI requests for auditing.
admClient.SetAppInfo("MinIO Console", pkg.Version)
return admClient, nil
}
// s3AdminNew returns an initialized minioAdmin structure. If debug is enabled,
// it also enables an internal trace transport.
var s3AdminNew = mcCmd.NewAdminFactory()
// MinioAdmin interface with all functions to be implemented
// by mock when testing, it should include all MinioAdmin respective api calls
// that are used within this project.
@@ -83,6 +57,7 @@ type MinioAdmin interface {
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
@@ -95,7 +70,7 @@ type MinioAdmin interface {
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 *iampolicy.Policy, user string, accessKey string, secretKey string) (madmin.Credentials, error)
addServiceAccount(ctx context.Context, policy *iampolicy.Policy, 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)
@@ -116,12 +91,14 @@ type MinioAdmin interface {
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) (*madmin.ReplicateAddStatus, error)
editSiteReplicationInfo(ctx context.Context, site madmin.PeerInfo) (*madmin.ReplicateEditStatus, 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
@@ -147,6 +124,15 @@ type MinioAdmin interface {
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
@@ -261,6 +247,11 @@ func (ac AdminClient) helpConfigKV(ctx context.Context, subSys, key string, envO
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)
@@ -293,7 +284,7 @@ func (ac AdminClient) stopProfiling(ctx context.Context) (io.ReadCloser, error)
}
// implements madmin.ServiceTrace()
func (ac AdminClient) serviceTrace(ctx context.Context, threshold int64, s3, internal, storage, os, errTrace bool) <-chan madmin.ServiceTraceInfo {
func (ac AdminClient) serviceTrace(ctx context.Context, threshold int64, _, internal, storage, os, errTrace bool) <-chan madmin.ServiceTraceInfo {
thresholdT := time.Duration(threshold)
tracingOptions := madmin.ServiceTraceOpts{
@@ -314,22 +305,25 @@ func (ac AdminClient) getLogs(ctx context.Context, node string, lineCnt int, log
}
// implements madmin.AddServiceAccount()
func (ac AdminClient) addServiceAccount(ctx context.Context, policy *iampolicy.Policy, user string, accessKey string, secretKey string) (madmin.Credentials, error) {
func (ac AdminClient) addServiceAccount(ctx context.Context, policy *iampolicy.Policy, user string, accessKey string, secretKey string, name string, description string, expiry *time.Time, comment string) (madmin.Credentials, error) {
buf, err := json.Marshal(policy)
if err != nil {
return madmin.Credentials{}, err
}
return ac.Client.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
Policy: buf,
TargetUser: user,
AccessKey: accessKey,
SecretKey: secretKey,
Policy: buf,
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) {
// TODO: Fix this
return ac.Client.ListServiceAccounts(ctx, user)
}
@@ -396,43 +390,29 @@ func (ac AdminClient) getBucketQuota(ctx context.Context, bucket string) (madmin
// 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) {
resp, version, err := ac.Client.ServerHealthInfo(ctx, healthDataTypes, deadline)
if err != nil {
return nil, version, err
}
info := madmin.HealthInfo{}
var healthInfo interface{}
decoder := json.NewDecoder(resp.Body)
switch version {
case madmin.HealthInfoVersion0:
info := madmin.HealthInfoV0{}
for {
if err = decoder.Decode(&info); err != nil {
break
}
}
// Old minio versions don't return the MinIO info in
// response of the healthinfo api. So fetch it separately
minioInfo, err := ac.Client.ServerInfo(ctx)
var version string
var tryCount int
for info.Version == "" && tryCount < 10 {
resp, version, err := ac.Client.ServerHealthInfo(ctx, healthDataTypes, deadline, "")
if err != nil {
info.Minio.Error = err.Error()
} else {
info.Minio.Info = minioInfo
return nil, version, err
}
healthInfo = mcCmd.MapHealthInfoToV1(info, nil)
version = madmin.HealthInfoVersion1
case madmin.HealthInfoVersion:
info := madmin.HealthInfo{}
decoder := json.NewDecoder(resp.Body)
for {
if err = decoder.Decode(&info); err != nil {
break
}
}
healthInfo = info
tryCount++
time.Sleep(2 * time.Second)
}
if info.Version == "" {
return nil, "", ErrHealthReportFail
}
healthInfo = info
return healthInfo, version, nil
}
@@ -462,8 +442,14 @@ func (ac AdminClient) editTierCreds(ctx context.Context, tierName string, creds
return ac.Client.EditTier(ctx, tierName, creds)
}
func NewMinioAdminClient(sessionClaims *models.Principal) (*madmin.AdminClient, error) {
adminClient, err := newAdminFromClaims(sessionClaims)
// implements madmin.VerifyTier()
func (ac AdminClient) verifyTierStatus(ctx context.Context, tierName string) error {
return ac.Client.VerifyTier(ctx, tierName)
}
func NewMinioAdminClient(ctx context.Context, sessionClaims *models.Principal) (*madmin.AdminClient, error) {
clientIP := utils.ClientIPFromContext(ctx)
adminClient, err := newAdminFromClaims(sessionClaims, clientIP)
if err != nil {
return nil, err
}
@@ -471,7 +457,7 @@ func NewMinioAdminClient(sessionClaims *models.Principal) (*madmin.AdminClient,
}
// newAdminFromClaims creates a minio admin from Decrypted claims using Assume role credentials
func newAdminFromClaims(claims *models.Principal) (*madmin.AdminClient, error) {
func newAdminFromClaims(claims *models.Principal, clientIP string) (*madmin.AdminClient, error) {
tlsEnabled := getMinIOEndpointIsSecure()
endpoint := getMinIOEndpoint()
@@ -482,7 +468,7 @@ func newAdminFromClaims(claims *models.Principal) (*madmin.AdminClient, error) {
if err != nil {
return nil, err
}
adminClient.SetCustomTransport(GetConsoleHTTPClient(getMinIOServer()).Transport)
adminClient.SetCustomTransport(GetConsoleHTTPClient(getMinIOServer(), clientIP).Transport)
return adminClient, nil
}
@@ -499,15 +485,6 @@ func newAdminFromCreds(accessKey, secretKey, endpoint string, tlsEnabled bool) (
return minioClient, nil
}
// httpClient is a custom http client, this client should not be called directly and instead be
// called using GetConsoleHTTPClient() to ensure is initialized and the certificates are loaded correctly
var httpClients = struct {
sync.Mutex
m map[string]*http.Client
}{
m: make(map[string]*http.Client),
}
// isLocalAddress returns true if the url contains an IPv4/IPv6 hostname
// that points to the local machine - FQDN are not supported
func isLocalIPEndpoint(addr string) bool {
@@ -531,24 +508,43 @@ func isLocalIPAddress(ipAddr string) bool {
// GetConsoleHTTPClient caches different http clients depending on the target endpoint while taking
// in consideration CA certs stored in ${HOME}/.console/certs/CAs and ${HOME}/.minio/certs/CAs
// If the target endpoint points to a loopback device, skip the TLS verification.
func GetConsoleHTTPClient(address string) *http.Client {
func GetConsoleHTTPClient(address string, clientIP string) *http.Client {
u, err := url.Parse(address)
if err == nil {
address = u.Hostname()
}
httpClients.Lock()
client, ok := httpClients.m[address]
httpClients.Unlock()
if ok {
return client
client := PrepareConsoleHTTPClient(isLocalIPAddress(address), clientIP)
return client
}
func getClientIP(r *http.Request) string {
// Try to get the IP address from the X-Real-IP header
// If the X-Real-IP header is not present, then it will return an empty string
xRealIP := r.Header.Get("X-Real-IP")
if xRealIP != "" {
return xRealIP
}
client = PrepareConsoleHTTPClient(isLocalIPAddress(address))
httpClients.Lock()
httpClients.m[address] = client
httpClients.Unlock()
return client
// Try to get the IP address from the X-Forwarded-For header
// If the X-Forwarded-For header is not present, then it will return an empty string
xForwardedFor := r.Header.Get("X-Forwarded-For")
if xForwardedFor != "" {
// X-Forwarded-For can contain multiple addresses, we return the first one
split := strings.Split(xForwardedFor, ",")
if len(split) > 0 {
return strings.TrimSpace(split[0])
}
}
// If neither header is present (or they were empty), then fall back to the connection's remote address
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
// In case there's an error, return an empty string
return ""
}
return ip
}
func (ac AdminClient) speedtest(ctx context.Context, opts madmin.SpeedtestOpts) (chan madmin.SpeedTestResult, error) {
@@ -569,8 +565,8 @@ func (ac AdminClient) getSiteReplicationInfo(ctx context.Context) (*madmin.SiteR
}, nil
}
func (ac AdminClient) addSiteReplicationInfo(ctx context.Context, sites []madmin.PeerSite) (*madmin.ReplicateAddStatus, error) {
res, err := ac.Client.SiteReplicationAdd(ctx, sites)
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
}
@@ -583,8 +579,8 @@ func (ac AdminClient) addSiteReplicationInfo(ctx context.Context, sites []madmin
}, nil
}
func (ac AdminClient) editSiteReplicationInfo(ctx context.Context, site madmin.PeerInfo) (*madmin.ReplicateEditStatus, error) {
res, err := ac.Client.SiteReplicationEdit(ctx, site)
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
}
@@ -689,3 +685,23 @@ func (ac AdminClient) deleteIdentity(ctx context.Context, identity string) error
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

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"context"
@@ -27,7 +27,7 @@ import (
"github.com/minio/minio-go/v7/pkg/replication"
"github.com/minio/minio-go/v7/pkg/sse"
xnet "github.com/minio/pkg/net"
xnet "github.com/minio/pkg/v2/net"
"github.com/minio/console/models"
"github.com/minio/console/pkg"
@@ -228,11 +228,11 @@ type MCClient interface {
addNotificationConfig(ctx context.Context, arn string, events []string, prefix, suffix string, ignoreExisting bool) *probe.Error
removeNotificationConfig(ctx context.Context, arn string, event string, prefix string, suffix string) *probe.Error
watch(ctx context.Context, options mc.WatchOptions) (*mc.WatchObject, *probe.Error)
remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass bool, contentCh <-chan *mc.ClientContent) <-chan mc.RemoveResult
remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass, forceDelete bool, contentCh <-chan *mc.ClientContent) <-chan mc.RemoveResult
list(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent
get(ctx context.Context, opts mc.GetOptions) (io.ReadCloser, *probe.Error)
shareDownload(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error)
setVersioning(ctx context.Context, status string) *probe.Error
setVersioning(ctx context.Context, status string, excludePrefix []string, excludeFolders bool) *probe.Error
}
// Interface implementation
@@ -265,12 +265,12 @@ func (c mcClient) deleteAllReplicationRules(ctx context.Context) *probe.Error {
return c.client.RemoveReplication(ctx)
}
func (c mcClient) setVersioning(ctx context.Context, status string) *probe.Error {
return c.client.SetVersion(ctx, status, []string{}, false)
func (c mcClient) setVersioning(ctx context.Context, status string, excludePrefix []string, excludeFolders bool) *probe.Error {
return c.client.SetVersion(ctx, status, excludePrefix, excludeFolders)
}
func (c mcClient) remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass bool, contentCh <-chan *mc.ClientContent) <-chan mc.RemoveResult {
return c.client.Remove(ctx, isIncomplete, isRemoveBucket, isBypass, false, contentCh)
func (c mcClient) remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass, forceDelete bool, contentCh <-chan *mc.ClientContent) <-chan mc.RemoveResult {
return c.client.Remove(ctx, isIncomplete, isRemoveBucket, isBypass, forceDelete, contentCh)
}
func (c mcClient) list(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent {
@@ -329,7 +329,26 @@ func (s consoleSTSAssumeRole) IsExpired() bool {
return s.stsAssumeRole.IsExpired()
}
func NewConsoleCredentials(accessKey, secretKey, location string) (*credentials.Credentials, error) {
func stsCredentials(minioURL, accessKey, secretKey, location, clientIP string) (*credentials.Credentials, error) {
if accessKey == "" || secretKey == "" {
return nil, errors.New("credentials endpoint, access and secret key are mandatory for AssumeRoleSTS")
}
opts := credentials.STSAssumeRoleOptions{
AccessKey: accessKey,
SecretKey: secretKey,
Location: location,
DurationSeconds: int(xjwt.GetConsoleSTSDuration().Seconds()),
}
stsAssumeRole := &credentials.STSAssumeRole{
Client: GetConsoleHTTPClient(minioURL, clientIP),
STSEndpoint: minioURL,
Options: opts,
}
consoleSTSWrapper := consoleSTSAssumeRole{stsAssumeRole: stsAssumeRole}
return credentials.New(consoleSTSWrapper), nil
}
func NewConsoleCredentials(accessKey, secretKey, location, clientIP string) (*credentials.Credentials, error) {
minioURL := getMinIOServer()
// Future authentication methods can be added under this switch statement
@@ -337,31 +356,41 @@ func NewConsoleCredentials(accessKey, secretKey, location string) (*credentials.
// LDAP authentication for Console
case ldap.GetLDAPEnabled():
{
creds, err := auth.GetCredentialsFromLDAP(GetConsoleHTTPClient(minioURL), minioURL, accessKey, secretKey)
creds, err := auth.GetCredentialsFromLDAP(GetConsoleHTTPClient(minioURL, clientIP), minioURL, accessKey, secretKey)
if err != nil {
return nil, err
}
// 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:
{
if accessKey == "" || secretKey == "" {
return nil, errors.New("credentials endpoint, access and secret key are mandatory for AssumeRoleSTS")
}
opts := credentials.STSAssumeRoleOptions{
AccessKey: accessKey,
SecretKey: secretKey,
Location: location,
DurationSeconds: int(xjwt.GetConsoleSTSDuration().Seconds()),
}
stsAssumeRole := &credentials.STSAssumeRole{
Client: GetConsoleHTTPClient(minioURL),
STSEndpoint: minioURL,
Options: opts,
}
consoleSTSWrapper := consoleSTSAssumeRole{stsAssumeRole: stsAssumeRole}
return credentials.New(consoleSTSWrapper), nil
return stsCredentials(minioURL, accessKey, secretKey, location, clientIP)
}
}
}
@@ -369,19 +398,22 @@ func NewConsoleCredentials(accessKey, secretKey, location string) (*credentials.
// getConsoleCredentialsFromSession returns the *consoleCredentials.Login associated to the
// provided session token, this is useful for running the Expire() or IsExpired() operations
func getConsoleCredentialsFromSession(claims *models.Principal) *credentials.Credentials {
if claims == nil {
return credentials.NewStaticV4("", "", "")
}
return credentials.NewStaticV4(claims.STSAccessKeyID, claims.STSSecretAccessKey, claims.STSSessionToken)
}
// newMinioClient creates a new MinIO client based on the ConsoleCredentials extracted
// from the provided session token
func newMinioClient(claims *models.Principal) (*minio.Client, error) {
func newMinioClient(claims *models.Principal, clientIP string) (*minio.Client, error) {
creds := getConsoleCredentialsFromSession(claims)
endpoint := getMinIOEndpoint()
secure := getMinIOEndpointIsSecure()
minioClient, err := minio.New(endpoint, &minio.Options{
Creds: creds,
Secure: secure,
Transport: GetConsoleHTTPClient(getMinIOServer()).Transport,
Transport: GetConsoleHTTPClient(getMinIOServer(), clientIP).Transport,
})
if err != nil {
return nil, err
@@ -409,7 +441,7 @@ func computeObjectURLWithoutEncode(bucketName, prefix string) (string, error) {
}
// newS3BucketClient creates a new mc S3Client to talk to the server based on a bucket
func newS3BucketClient(claims *models.Principal, bucketName string, prefix string) (*mc.S3Client, error) {
func newS3BucketClient(claims *models.Principal, bucketName string, prefix string, clientIP string) (*mc.S3Client, error) {
if claims == nil {
return nil, fmt.Errorf("the provided credentials are invalid")
}
@@ -418,7 +450,7 @@ func newS3BucketClient(claims *models.Principal, bucketName string, prefix strin
if err != nil {
return nil, fmt.Errorf("the provided endpoint is invalid")
}
s3Config := newS3Config(objectURL, claims.STSAccessKeyID, claims.STSSecretAccessKey, claims.STSSessionToken)
s3Config := newS3Config(objectURL, claims.STSAccessKeyID, claims.STSSecretAccessKey, claims.STSSessionToken, clientIP)
client, pErr := mc.S3New(s3Config)
if pErr != nil {
return nil, pErr.Cause
@@ -440,9 +472,10 @@ func pathJoinFinalSlash(elem ...string) string {
return path.Join(elem...)
}
// Deprecated
// newS3Config simply creates a new Config struct using the passed
// parameters.
func newS3Config(endpoint, accessKey, secretKey, sessionToken string) *mc.Config {
func newS3Config(endpoint, accessKey, secretKey, sessionToken string, clientIP string) *mc.Config {
// We have a valid alias and hostConfig. We populate the/
// consoleCredentials from the match found in the config file.
s3Config := new(mc.Config)
@@ -460,7 +493,7 @@ func newS3Config(endpoint, accessKey, secretKey, sessionToken string) *mc.Config
insecure := isLocalIPEndpoint(endpoint)
s3Config.Insecure = insecure
s3Config.Transport = PrepareSTSClientTransport(insecure)
s3Config.Transport = PrepareSTSClientTransport(insecure, clientIP).Transport
return s3Config
}

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import "testing"
@@ -76,7 +76,7 @@ func Test_computeObjectURLWithoutEncode(t *testing.T) {
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Run(tt.name, func(_ *testing.T) {
got, err := computeObjectURLWithoutEncode(tt.args.bucketName, tt.args.prefix)
if (err != nil) != tt.wantErr {
t.Errorf("computeObjectURLWithoutEncode() errors = %v, wantErr %v", err, tt.wantErr)

277
api/config.go Normal file
View File

@@ -0,0 +1,277 @@
// 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 (
"crypto/x509"
"net"
"strconv"
"strings"
"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"
)
var (
// Port console default port
Port = "9090"
// Hostname console hostname
// avoid listening on 0.0.0.0 by default
// instead listen on all IPv4 and IPv6
// - Hostname should be empty.
Hostname = ""
// TLSPort console tls port
TLSPort = "9443"
// TLSRedirect console tls redirect rule
TLSRedirect = "on"
ConsoleResourceName = "console-ui"
)
var (
// GlobalRootCAs is CA root certificates, a nil value means system certs pool will be used
GlobalRootCAs *x509.CertPool
// GlobalPublicCerts has certificates Console will use to serve clients
GlobalPublicCerts []*x509.Certificate
// GlobalTLSCertsManager custom TLS Manager for SNI support
GlobalTLSCertsManager *xcerts.Manager
)
// MinIOConfig represents application configuration passed in from the MinIO
// server to the console.
type MinIOConfig struct {
OpenIDProviders oauth2.OpenIDPCfg
}
// GlobalMinIOConfig is the global application configuration passed in from the
// MinIO server.
var GlobalMinIOConfig MinIOConfig
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, ""))
}
func getMinIOEndpoint() string {
u, err := xnet.ParseHTTPURL(getMinIOServer())
if err != nil {
panic(err)
}
return u.Host
}
func getMinIOEndpointIsSecure() bool {
u, err := xnet.ParseHTTPURL(getMinIOServer())
if err != nil {
panic(err)
}
return u.Scheme == "https"
}
// GetHostname gets console hostname set on env variable,
// default one or defined on run command
func GetHostname() string {
return strings.ToLower(env.Get(ConsoleHostname, Hostname))
}
// GetPort gets console por set on env variable
// or default one
func GetPort() int {
port, err := strconv.Atoi(env.Get(ConsolePort, Port))
if err != nil {
port = 9090
}
return port
}
// GetTLSPort gets console tls port set on env variable
// or default one
func GetTLSPort() int {
port, err := strconv.Atoi(env.Get(ConsoleTLSPort, TLSPort))
if err != nil {
port = 9443
}
return port
}
// If GetTLSRedirect is set to true, then only allow HTTPS requests. Default is true.
func GetTLSRedirect() string {
return strings.ToLower(env.Get(ConsoleSecureTLSRedirect, TLSRedirect))
}
// Get secure middleware env variable configurations
func GetSecureAllowedHosts() []string {
allowedHosts := env.Get(ConsoleSecureAllowedHosts, "")
if allowedHosts != "" {
return strings.Split(allowedHosts, ",")
}
return []string{}
}
// AllowedHostsAreRegex determines, if the provided AllowedHosts slice contains valid regular expressions. Default is false.
func GetSecureAllowedHostsAreRegex() bool {
return strings.ToLower(env.Get(ConsoleSecureAllowedHostsAreRegex, "off")) == "on"
}
// If FrameDeny is set to true, adds the X-Frame-Options header with the value of `DENY`. Default is true.
func GetSecureFrameDeny() bool {
return strings.ToLower(env.Get(ConsoleSecureFrameDeny, "on")) == "on"
}
// If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value `nosniff`. Default is true.
func GetSecureContentTypeNonSniff() bool {
return strings.ToLower(env.Get(ConsoleSecureContentTypeNoSniff, "on")) == "on"
}
// If BrowserXssFilter is true, adds the X-XSS-Protection header with the value `1; mode=block`. Default is true.
func GetSecureBrowserXSSFilter() bool {
return strings.ToLower(env.Get(ConsoleSecureBrowserXSSFilter, "on")) == "on"
}
// ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value. Default is "".
// Passing a template string will replace `$NONCE` with a dynamic nonce value of 16 bytes for each request which can be
// later retrieved using the Nonce function.
func GetSecureContentSecurityPolicy() string {
return env.Get(ConsoleSecureContentSecurityPolicy, "")
}
// ContentSecurityPolicyReportOnly allows the Content-Security-Policy-Report-Only header value to be set with a custom value. Default is "".
func GetSecureContentSecurityPolicyReportOnly() string {
return env.Get(ConsoleSecureContentSecurityPolicyReportOnly, "")
}
// HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request.
func GetSecureHostsProxyHeaders() []string {
allowedHosts := env.Get(ConsoleSecureHostsProxyHeaders, "")
if allowedHosts != "" {
return strings.Split(allowedHosts, ",")
}
return []string{}
}
// TLSHost is the host name that is used to redirect HTTP requests to HTTPS. Default is "", which indicates to use the same host.
func GetSecureTLSHost() string {
tlsHost := env.Get(ConsoleSecureTLSHost, "")
if tlsHost == "" && Hostname != "" {
return net.JoinHostPort(Hostname, TLSPort)
}
return ""
}
// STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header.
func GetSecureSTSSeconds() int64 {
seconds, err := strconv.Atoi(env.Get(ConsoleSecureSTSSeconds, "0"))
if err != nil {
seconds = 0
}
return int64(seconds)
}
// If STSIncludeSubdomains is set to true, the `includeSubdomains` will be appended to the Strict-Transport-Security header. Default is false.
func GetSecureSTSIncludeSubdomains() bool {
return strings.ToLower(env.Get(ConsoleSecureSTSIncludeSubdomains, "off")) == "on"
}
// If STSPreload is set to true, the `preload` flag will be appended to the Strict-Transport-Security header. Default is false.
func GetSecureSTSPreload() bool {
return strings.ToLower(env.Get(ConsoleSecureSTSPreload, "off")) == "on"
}
// If TLSTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301).
func GetSecureTLSTemporaryRedirect() bool {
return strings.ToLower(env.Get(ConsoleSecureTLSTemporaryRedirect, "off")) == "on"
}
// STS header is only included when the connection is HTTPS.
func GetSecureForceSTSHeader() bool {
return strings.ToLower(env.Get(ConsoleSecureForceSTSHeader, "off")) == "on"
}
// ReferrerPolicy allows the Referrer-Policy header with the value to be set with a custom value. Default is "".
func GetSecureReferrerPolicy() string {
return env.Get(ConsoleSecureReferrerPolicy, "")
}
// FeaturePolicy allows the Feature-Policy header with the value to be set with a custom value. Default is "".
func GetSecureFeaturePolicy() string {
return env.Get(ConsoleSecureFeaturePolicy, "")
}
func getLogSearchAPIToken() string {
if v := env.Get(ConsoleLogQueryAuthToken, ""); v != "" {
return v
}
return env.Get(LogSearchQueryAuthToken, "")
}
func getLogSearchURL() string {
return env.Get(ConsoleLogQueryURL, "")
}
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 {
return 10
}
return cu
}
func getMaxConcurrentDownloadsLimit() int64 {
cu, err := strconv.ParseInt(env.Get(ConsoleMaxConcurrentDownloads, "20"), 10, 64)
if err != nil {
return 20
}
return cu
}
func getConsoleDevMode() bool {
return strings.ToLower(env.Get(ConsoleDevMode, "off")) == "on"
}
func getConsoleAnimatedLogin() bool {
return strings.ToLower(env.Get(ConsoleAnimatedLogin, "on")) == "on"
}

393
api/config_test.go Normal file
View File

@@ -0,0 +1,393 @@
// 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 (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetHostname(t *testing.T) {
os.Setenv(ConsoleHostname, "x")
defer os.Unsetenv(ConsoleHostname)
assert.Equalf(t, "x", GetHostname(), "GetHostname()")
}
func TestGetPort(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want int
}{
{
name: "valid port",
args: args{
env: "9091",
},
want: 9091,
},
{
name: "invalid port",
args: args{
env: "duck",
},
want: 9090,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsolePort, tt.args.env)
assert.Equalf(t, tt.want, GetPort(), "GetPort()")
os.Unsetenv(ConsolePort)
})
}
}
func TestGetTLSPort(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want int
}{
{
name: "valid port",
args: args{
env: "9444",
},
want: 9444,
},
{
name: "invalid port",
args: args{
env: "duck",
},
want: 9443,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleTLSPort, tt.args.env)
assert.Equalf(t, tt.want, GetTLSPort(), "GetTLSPort()")
os.Unsetenv(ConsoleTLSPort)
})
}
}
func TestGetSecureAllowedHosts(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want []string
}{
{
name: "valid hosts",
args: args{
env: "host1,host2",
},
want: []string{"host1", "host2"},
},
{
name: "empty hosts",
args: args{
env: "",
},
want: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleSecureAllowedHosts, tt.args.env)
assert.Equalf(t, tt.want, GetSecureAllowedHosts(), "GetSecureAllowedHosts()")
os.Unsetenv(ConsoleSecureAllowedHosts)
})
}
}
func TestGetSecureHostsProxyHeaders(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want []string
}{
{
name: "valid headers",
args: args{
env: "header1,header2",
},
want: []string{"header1", "header2"},
},
{
name: "empty headers",
args: args{
env: "",
},
want: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleSecureHostsProxyHeaders, tt.args.env)
assert.Equalf(t, tt.want, GetSecureHostsProxyHeaders(), "GetSecureHostsProxyHeaders()")
os.Unsetenv(ConsoleSecureHostsProxyHeaders)
})
}
}
func TestGetSecureSTSSeconds(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want int64
}{
{
name: "valid",
args: args{
env: "1",
},
want: 1,
},
{
name: "invalid",
args: args{
env: "duck",
},
want: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleSecureSTSSeconds, tt.args.env)
assert.Equalf(t, tt.want, GetSecureSTSSeconds(), "GetSecureSTSSeconds()")
os.Unsetenv(ConsoleSecureSTSSeconds)
})
}
}
func Test_getLogSearchAPIToken(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want string
}{
{
name: "env set",
args: args{
env: "value",
},
want: "value",
},
{
name: "env not set",
args: args{
env: "",
},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleLogQueryAuthToken, tt.args.env)
assert.Equalf(t, tt.want, getLogSearchAPIToken(), "getLogSearchAPIToken()")
os.Setenv(ConsoleLogQueryAuthToken, tt.args.env)
})
}
}
func Test_getPrometheusURL(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want string
}{
{
name: "env set",
args: args{
env: "value",
},
want: "value",
},
{
name: "env not set",
args: args{
env: "",
},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(PrometheusURL, tt.args.env)
assert.Equalf(t, tt.want, getPrometheusURL(), "getPrometheusURL()")
os.Setenv(PrometheusURL, tt.args.env)
})
}
}
func Test_getPrometheusJobID(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want string
}{
{
name: "env set",
args: args{
env: "value",
},
want: "value",
},
{
name: "env not set",
args: args{
env: "",
},
want: "minio-job",
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(PrometheusJobID, tt.args.env)
assert.Equalf(t, tt.want, getPrometheusJobID(), "getPrometheusJobID()")
os.Setenv(PrometheusJobID, tt.args.env)
})
}
}
func Test_getMaxConcurrentUploadsLimit(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want int64
}{
{
name: "valid",
args: args{
env: "1",
},
want: 1,
},
{
name: "invalid",
args: args{
env: "duck",
},
want: 10,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleMaxConcurrentUploads, tt.args.env)
assert.Equalf(t, tt.want, getMaxConcurrentUploadsLimit(), "getMaxConcurrentUploadsLimit()")
os.Unsetenv(ConsoleMaxConcurrentUploads)
})
}
}
func Test_getMaxConcurrentDownloadsLimit(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want int64
}{
{
name: "valid",
args: args{
env: "1",
},
want: 1,
},
{
name: "invalid",
args: args{
env: "duck",
},
want: 20,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleMaxConcurrentDownloads, tt.args.env)
assert.Equalf(t, tt.want, getMaxConcurrentDownloadsLimit(), "getMaxConcurrentDownloadsLimit()")
os.Unsetenv(ConsoleMaxConcurrentDownloads)
})
}
}
func Test_getConsoleDevMode(t *testing.T) {
type args struct {
env string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "value set",
args: args{
env: "on",
},
want: true,
},
{
name: "value not set",
args: args{
env: "",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
os.Setenv(ConsoleDevMode, tt.args.env)
assert.Equalf(t, tt.want, getConsoleDevMode(), "getConsoleDevMode()")
os.Unsetenv(ConsoleDevMode)
})
}
}

View File

@@ -16,7 +16,7 @@
// This file is safe to edit. Once it exists it will not be overwritten
package restapi
package api
import (
"bytes"
@@ -35,22 +35,24 @@ import (
"sync"
"time"
"github.com/google/uuid"
"github.com/minio/console/pkg/logger"
"github.com/minio/console/pkg/utils"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/klauspost/compress/gzhttp"
portal_ui "github.com/minio/console/portal-ui"
"github.com/minio/pkg/env"
"github.com/minio/pkg/mimedb"
xnet "github.com/minio/pkg/net"
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/go-openapi/errors"
"github.com/go-openapi/swag"
"github.com/minio/console/api/operations"
"github.com/minio/console/models"
"github.com/minio/console/pkg/auth"
"github.com/minio/console/restapi/operations"
"github.com/unrolled/secure"
)
@@ -65,7 +67,7 @@ const (
)
var (
subPath = "/"
cfgSubPath = "/"
subPathOnce sync.Once
)
@@ -80,9 +82,12 @@ func configureFlags(api *operations.ConsoleAPI) {
func configureAPI(api *operations.ConsoleAPI) http.Handler {
// Applies when the "x-token" header is set
api.KeyAuth = func(token string, scopes []string) (*models.Principal, error) {
api.KeyAuth = func(token string, _ []string) (*models.Principal, error) {
// we are validating the session token by decrypting the claims inside, if the operation succeed that means the jwt
// was generated and signed by us in the first place
if token == "Anonymous" {
return &models.Principal{}, nil
}
claims, err := auth.ParseClaimsFromToken(token)
if err != nil {
api.Logger("Unable to validate the session token %s: %v", token, err)
@@ -98,6 +103,9 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
CustomStyleOb: claims.CustomStyleOB,
}, nil
}
api.AnonymousAuth = func(_ string) (*models.Principal, error) {
return &models.Principal{}, nil
}
// Register login handlers
registerLoginHandlers(api)
@@ -121,8 +129,6 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
registerServiceHandlers(api)
// Register session handlers
registerSessionHandlers(api)
// Register version handlers
registerVersionHandlers(api)
// Register admin info handlers
registerAdminInfoHandlers(api)
// Register admin arns handlers
@@ -139,6 +145,8 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
registerSubnetHandlers(api)
// Register admin KMS handlers
registerKMSHandlers(api)
// Register admin IDP handlers
registerIDPHandlers(api)
// Register Account handlers
registerAdminTiersHandlers(api)
// Register Inspect Handler
@@ -148,6 +156,8 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
registerSiteReplicationHandler(api)
registerSiteReplicationStatusHandler(api)
// Register Support Handler
registerSupportHandlers(api)
// Operator Console
@@ -158,10 +168,15 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
// Register Account handlers
registerAccountHandlers(api)
registerReleasesHandlers(api)
api.PreServerShutdown = func() {}
api.ServerShutdown = func() {}
// do an initial subnet plan caching
fetchLicensePlan()
return setupGlobalMiddleware(api.Serve(setupMiddlewares))
}
@@ -179,15 +194,12 @@ func setupMiddlewares(handler http.Handler) http.Handler {
func ContextMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID, err := utils.NewUUID()
if err != nil && err != auth.ErrNoAuthToken {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
requestID := uuid.NewString()
ctx := context.WithValue(r.Context(), utils.ContextRequestID, requestID)
ctx = context.WithValue(ctx, utils.ContextRequestUserAgent, r.UserAgent())
ctx = context.WithValue(ctx, utils.ContextRequestHost, r.Host)
ctx = context.WithValue(ctx, utils.ContextRequestRemoteAddr, r.RemoteAddr)
ctx = context.WithValue(ctx, utils.ContextClientIP, getClientIP(r))
next.ServeHTTP(w, r.WithContext(ctx))
})
}
@@ -242,10 +254,8 @@ func setupGlobalMiddleware(handler http.Handler) http.Handler {
BrowserXssFilter: GetSecureBrowserXSSFilter(),
ContentSecurityPolicy: GetSecureContentSecurityPolicy(),
ContentSecurityPolicyReportOnly: GetSecureContentSecurityPolicyReportOnly(),
PublicKey: GetSecurePublicKey(),
ReferrerPolicy: GetSecureReferrerPolicy(),
FeaturePolicy: GetSecureFeaturePolicy(),
ExpectCTHeader: GetSecureExpectCTHeader(),
IsDevelopment: false,
}
secureMiddleware := secure.New(secureOptions)
@@ -286,6 +296,8 @@ func AuthenticationMiddleware(next http.Handler) http.Handler {
// handle it appropriately.
if len(sessionToken) > 0 {
r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", string(sessionToken)))
} else {
r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", "Anonymous"))
}
ctx := r.Context()
claims, _ := auth.ParseClaimsFromToken(string(sessionToken))
@@ -311,7 +323,7 @@ func FileServerMiddleware(next http.Handler) http.Handler {
if err != nil {
panic(err)
}
wrapHandlerSinglePageApplication(http.FileServer(http.FS(buildFs))).ServeHTTP(w, r)
wrapHandlerSinglePageApplication(requestBounce(http.FileServer(http.FS(buildFs)))).ServeHTTP(w, r)
}
})
}
@@ -371,13 +383,15 @@ func handleSPA(w http.ResponseWriter, r *http.Request) {
sf.HideMenu = true
sf.ObjectBrowser = true
err := ValidateEncodedStyles(overridenStyles)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if overridenStyles != "" {
err := ValidateEncodedStyles(overridenStyles)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sf.CustomStyleOB = overridenStyles
sf.CustomStyleOB = overridenStyles
}
sessionID, err := login(consoleCreds, sf)
if err != nil {
@@ -406,6 +420,7 @@ func handleSPA(w http.ResponseWriter, r *http.Request) {
} else if getSubPath() != "/" {
indexPageBytes = replaceBaseInIndex(indexPageBytes, getSubPath())
}
indexPageBytes = replaceLicense(indexPageBytes)
mimeType := mimedb.TypeByExtension(filepath.Ext(r.URL.Path))
@@ -451,9 +466,9 @@ func configureServer(s *http.Server, _, _ string) {
func getSubPath() string {
subPathOnce.Do(func() {
subPath = parseSubPath(env.Get(SubPath, ""))
cfgSubPath = parseSubPath(env.Get(SubPath, ""))
})
return subPath
return cfgSubPath
}
func parseSubPath(v string) string {
@@ -463,7 +478,7 @@ func parseSubPath(v string) string {
}
// Replace all unnecessary `\` to `/`
// also add pro-actively at the end.
subPath = path.Clean(filepath.ToSlash(v))
subPath := path.Clean(filepath.ToSlash(v))
if !strings.HasPrefix(subPath, SlashSeparator) {
subPath = SlashSeparator + subPath
}
@@ -487,3 +502,22 @@ func replaceBaseInIndex(indexPageBytes []byte, basePath string) []byte {
}
return indexPageBytes
}
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=\"apgl\"/>", newPlan, 1)
indexPageBytes = []byte(indexPageStr)
return indexPageBytes
}
func requestBounce(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "/") {
http.NotFound(w, r)
return
}
handler.ServeHTTP(w, r)
})
}

View File

@@ -14,7 +14,7 @@
// 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 restapi
package api
import (
"os"
@@ -70,7 +70,7 @@ func Test_parseSubPath(t *testing.T) {
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Run(tt.name, func(_ *testing.T) {
assert.Equalf(t, tt.want, parseSubPath(tt.args.v), "parseSubPath(%v)", tt.args.v)
})
}
@@ -115,7 +115,7 @@ func Test_getSubPath(t *testing.T) {
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Run(tt.name, func(_ *testing.T) {
t.Setenv(SubPath, tt.args.envValue)
defer os.Unsetenv(SubPath)
subPathOnce = sync.Once{}

62
api/consts.go Normal file
View File

@@ -0,0 +1,62 @@
// 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
// list of all console environment constants
const (
// Constants for common configuration
ConsoleMinIOServer = "CONSOLE_MINIO_SERVER"
ConsoleSubnetProxy = "CONSOLE_SUBNET_PROXY"
ConsoleMinIORegion = "CONSOLE_MINIO_REGION"
ConsoleHostname = "CONSOLE_HOSTNAME"
ConsolePort = "CONSOLE_PORT"
ConsoleTLSPort = "CONSOLE_TLS_PORT"
// Constants for Secure middleware
ConsoleSecureAllowedHosts = "CONSOLE_SECURE_ALLOWED_HOSTS"
ConsoleSecureAllowedHostsAreRegex = "CONSOLE_SECURE_ALLOWED_HOSTS_ARE_REGEX"
ConsoleSecureFrameDeny = "CONSOLE_SECURE_FRAME_DENY"
ConsoleSecureContentTypeNoSniff = "CONSOLE_SECURE_CONTENT_TYPE_NO_SNIFF"
ConsoleSecureBrowserXSSFilter = "CONSOLE_SECURE_BROWSER_XSS_FILTER"
ConsoleSecureContentSecurityPolicy = "CONSOLE_SECURE_CONTENT_SECURITY_POLICY"
ConsoleSecureContentSecurityPolicyReportOnly = "CONSOLE_SECURE_CONTENT_SECURITY_POLICY_REPORT_ONLY"
ConsoleSecureHostsProxyHeaders = "CONSOLE_SECURE_HOSTS_PROXY_HEADERS"
ConsoleSecureSTSSeconds = "CONSOLE_SECURE_STS_SECONDS"
ConsoleSecureSTSIncludeSubdomains = "CONSOLE_SECURE_STS_INCLUDE_SUB_DOMAINS"
ConsoleSecureSTSPreload = "CONSOLE_SECURE_STS_PRELOAD"
ConsoleSecureTLSRedirect = "CONSOLE_SECURE_TLS_REDIRECT"
ConsoleSecureTLSHost = "CONSOLE_SECURE_TLS_HOST"
ConsoleSecureTLSTemporaryRedirect = "CONSOLE_SECURE_TLS_TEMPORARY_REDIRECT"
ConsoleSecureForceSTSHeader = "CONSOLE_SECURE_FORCE_STS_HEADER"
ConsoleSecurePublicKey = "CONSOLE_SECURE_PUBLIC_KEY"
ConsoleSecureReferrerPolicy = "CONSOLE_SECURE_REFERRER_POLICY"
ConsoleSecureFeaturePolicy = "CONSOLE_SECURE_FEATURE_POLICY"
ConsoleSecureExpectCTHeader = "CONSOLE_SECURE_EXPECT_CT_HEADER"
PrometheusURL = "CONSOLE_PROMETHEUS_URL"
PrometheusAuthToken = "CONSOLE_PROMETHEUS_AUTH_TOKEN"
PrometheusJobID = "CONSOLE_PROMETHEUS_JOB_ID"
PrometheusExtraLabels = "CONSOLE_PROMETHEUS_EXTRA_LABELS"
ConsoleLogQueryURL = "CONSOLE_LOG_QUERY_URL"
ConsoleLogQueryAuthToken = "CONSOLE_LOG_QUERY_AUTH_TOKEN"
ConsoleMaxConcurrentUploads = "CONSOLE_MAX_CONCURRENT_UPLOADS"
ConsoleMaxConcurrentDownloads = "CONSOLE_MAX_CONCURRENT_DOWNLOADS"
ConsoleDevMode = "CONSOLE_DEV_MODE"
ConsoleAnimatedLogin = "CONSOLE_ANIMATED_LOGIN"
LogSearchQueryAuthToken = "LOGSEARCH_QUERY_AUTH_TOKEN"
SlashSeparator = "/"
LocalAddress = "127.0.0.1"
)

556
api/custom-server.go Normal file
View File

@@ -0,0 +1,556 @@
// 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"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/go-openapi/runtime/flagext"
"github.com/go-openapi/swag"
flags "github.com/jessevdk/go-flags"
"golang.org/x/net/netutil"
"github.com/minio/console/api/operations"
)
const (
schemeHTTP = "http"
schemeHTTPS = "https"
schemeUnix = "unix"
)
var defaultSchemes []string
func init() {
defaultSchemes = []string{
schemeHTTP,
}
}
// NewServer creates a new api console server but does not configure it
func NewServer(api *operations.ConsoleAPI) *Server {
s := new(Server)
s.shutdown = make(chan struct{})
s.api = api
s.interrupt = make(chan os.Signal, 1)
return s
}
// ConfigureAPI configures the API and handlers.
func (s *Server) ConfigureAPI() {
if s.api != nil {
s.handler = configureAPI(s.api)
}
}
// ConfigureFlags configures the additional flags defined by the handlers. Needs to be called before the parser.Parse
func (s *Server) ConfigureFlags() {
if s.api != nil {
configureFlags(s.api)
}
}
// Server for the console API
type Server struct {
EnabledListeners []string `long:"scheme" description:"the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec"`
CleanupTimeout time.Duration `long:"cleanup-timeout" description:"grace period for which to wait before killing idle connections" default:"10s"`
GracefulTimeout time.Duration `long:"graceful-timeout" description:"grace period for which to wait before shutting down the server" default:"15s"`
MaxHeaderSize flagext.ByteSize `long:"max-header-size" description:"controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body." default:"1MiB"`
SocketPath flags.Filename `long:"socket-path" description:"the unix socket to listen on" default:"/var/run/console.sock"`
domainSocketL net.Listener
Host string `long:"host" description:"the IP to listen on" default:"localhost" env:"HOST"`
Port int `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" env:"PORT"`
ListenLimit int `long:"listen-limit" description:"limit the number of outstanding requests"`
KeepAlive time.Duration `long:"keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)" default:"3m"`
ReadTimeout time.Duration `long:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"`
WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"`
httpServerL []net.Listener
TLSHost string `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
TLSPort int `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"`
TLSCertificate flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"`
TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure connections" env:"TLS_PRIVATE_KEY"`
TLSCACertificate flags.Filename `long:"tls-ca" description:"the certificate authority file to be used with mutual tls auth" env:"TLS_CA_CERTIFICATE"`
TLSListenLimit int `long:"tls-listen-limit" description:"limit the number of outstanding requests"`
TLSKeepAlive time.Duration `long:"tls-keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)"`
TLSReadTimeout time.Duration `long:"tls-read-timeout" description:"maximum duration before timing out read of the request"`
TLSWriteTimeout time.Duration `long:"tls-write-timeout" description:"maximum duration before timing out write of the response"`
httpsServerL []net.Listener
api *operations.ConsoleAPI
handler http.Handler
hasListeners bool
shutdown chan struct{}
shuttingDown int32
interrupted bool
interrupt chan os.Signal
}
// Logf logs message either via defined user logger or via system one if no user logger is defined.
func (s *Server) Logf(f string, args ...interface{}) {
if s.api != nil && s.api.Logger != nil {
s.api.Logger(f, args...)
} else {
log.Printf(f, args...)
}
}
// Fatalf logs message either via defined user logger or via system one if no user logger is defined.
// Exits with non-zero status after printing
func (s *Server) Fatalf(f string, args ...interface{}) {
if s.api != nil && s.api.Logger != nil {
s.api.Logger(f, args...)
os.Exit(1)
}
log.Fatalf(f, args...)
}
// SetAPI configures the server with the specified API. Needs to be called before Serve
func (s *Server) SetAPI(api *operations.ConsoleAPI) {
if api == nil {
s.api = nil
s.handler = nil
return
}
s.api = api
s.handler = configureAPI(api)
}
func (s *Server) hasScheme(scheme string) bool {
schemes := s.EnabledListeners
if len(schemes) == 0 {
schemes = defaultSchemes
}
for _, v := range schemes {
if v == scheme {
return true
}
}
return false
}
// Serve the api
func (s *Server) Serve() (err error) {
if !s.hasListeners {
if err = s.Listen(); err != nil {
return err
}
}
// set default handler, if none is set
if s.handler == nil {
if s.api == nil {
return errors.New("can't create the default handler, as no api is set")
}
s.SetHandler(s.api.Serve(nil))
}
wg := new(sync.WaitGroup)
once := new(sync.Once)
signalNotify(s.interrupt)
go handleInterrupt(once, s)
servers := []*http.Server{}
if s.hasScheme(schemeUnix) {
domainSocket := new(http.Server)
domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize)
domainSocket.Handler = s.handler
if int64(s.CleanupTimeout) > 0 {
domainSocket.IdleTimeout = s.CleanupTimeout
}
configureServer(domainSocket, "unix", string(s.SocketPath))
servers = append(servers, domainSocket)
wg.Add(1)
s.Logf("Serving console at unix://%s", s.SocketPath)
go func(l net.Listener) {
defer wg.Done()
if err := domainSocket.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
}
s.Logf("Stopped serving console at unix://%s", s.SocketPath)
}(s.domainSocketL)
}
if s.hasScheme(schemeHTTP) {
httpServer := new(http.Server)
httpServer.MaxHeaderBytes = int(s.MaxHeaderSize)
httpServer.ReadTimeout = s.ReadTimeout
httpServer.WriteTimeout = s.WriteTimeout
httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0)
if s.ListenLimit > 0 {
for i := range s.httpServerL {
s.httpServerL[i] = netutil.LimitListener(s.httpServerL[i], s.ListenLimit)
}
}
if int64(s.CleanupTimeout) > 0 {
httpServer.IdleTimeout = s.CleanupTimeout
}
httpServer.Handler = s.handler
configureServer(httpServer, "http", s.httpServerL[0].Addr().String())
servers = append(servers, httpServer)
s.Logf("Serving console at http://%s", s.httpServerL[0].Addr())
for i := range s.httpServerL {
wg.Add(1)
go func(l net.Listener) {
defer wg.Done()
if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
}
s.Logf("Stopped serving console at http://%s", l.Addr())
}(s.httpServerL[i])
}
}
if s.hasScheme(schemeHTTPS) {
httpsServer := new(http.Server)
httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize)
httpsServer.ReadTimeout = s.TLSReadTimeout
httpsServer.WriteTimeout = s.TLSWriteTimeout
httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0)
if s.TLSListenLimit > 0 {
for i := range s.httpsServerL {
s.httpsServerL[i] = netutil.LimitListener(s.httpsServerL[i], s.TLSListenLimit)
}
}
if int64(s.CleanupTimeout) > 0 {
httpsServer.IdleTimeout = s.CleanupTimeout
}
httpsServer.Handler = s.handler
// Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
httpsServer.TLSConfig = &tls.Config{
// Causes servers to use Go's default ciphersuite preferences,
// which are tuned to avoid attacks. Does nothing on clients.
PreferServerCipherSuites: true,
// Only use curves which have assembly implementations
// https://github.com/golang/go/tree/master/src/crypto/elliptic
CurvePreferences: []tls.CurveID{tls.CurveP256},
// Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
NextProtos: []string{"h2", "http/1.1"},
// https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols
MinVersion: tls.VersionTLS12,
// These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
}
// build standard config from server options
if s.TLSCertificate != "" && s.TLSCertificateKey != "" {
httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(string(s.TLSCertificate), string(s.TLSCertificateKey))
if err != nil {
return err
}
}
if s.TLSCACertificate != "" {
// include specified CA certificate
caCert, caCertErr := os.ReadFile(string(s.TLSCACertificate))
if caCertErr != nil {
return caCertErr
}
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(caCert)
if !ok {
return fmt.Errorf("cannot parse CA certificate")
}
httpsServer.TLSConfig.ClientCAs = caCertPool
httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
// call custom TLS configurator
configureTLS(httpsServer.TLSConfig)
if len(httpsServer.TLSConfig.Certificates) == 0 && httpsServer.TLSConfig.GetCertificate == nil {
// after standard and custom config are passed, this ends up with no certificate
if s.TLSCertificate == "" {
if s.TLSCertificateKey == "" {
s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified")
}
s.Fatalf("the required flag `--tls-certificate` was not specified")
}
if s.TLSCertificateKey == "" {
s.Fatalf("the required flag `--tls-key` was not specified")
}
// this happens with a wrong custom TLS configurator
s.Fatalf("no certificate was configured for TLS")
}
configureServer(httpsServer, "https", s.httpsServerL[0].Addr().String())
servers = append(servers, httpsServer)
s.Logf("Serving console at https://%s", s.httpsServerL[0].Addr())
for i := range s.httpsServerL {
wg.Add(1)
go func(l net.Listener) {
defer wg.Done()
if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
}
s.Logf("Stopped serving console at https://%s", l.Addr())
}(tls.NewListener(s.httpsServerL[i], httpsServer.TLSConfig))
}
}
wg.Add(1)
go s.handleShutdown(wg, &servers)
wg.Wait()
return nil
}
// Listen creates the listeners for the server
func (s *Server) Listen() error {
if s.hasListeners { // already done this
return nil
}
if s.hasScheme(schemeHTTPS) {
// Use http host if https host wasn't defined
if s.TLSHost == "" {
s.TLSHost = s.Host
}
// Use http listen limit if https listen limit wasn't defined
if s.TLSListenLimit == 0 {
s.TLSListenLimit = s.ListenLimit
}
// Use http tcp keep alive if https tcp keep alive wasn't defined
if int64(s.TLSKeepAlive) == 0 {
s.TLSKeepAlive = s.KeepAlive
}
// Use http read timeout if https read timeout wasn't defined
if int64(s.TLSReadTimeout) == 0 {
s.TLSReadTimeout = s.ReadTimeout
}
// Use http write timeout if https write timeout wasn't defined
if int64(s.TLSWriteTimeout) == 0 {
s.TLSWriteTimeout = s.WriteTimeout
}
}
if s.hasScheme(schemeUnix) {
domSockListener, err := net.Listen("unix", string(s.SocketPath))
if err != nil {
return err
}
s.domainSocketL = domSockListener
}
lookup := func(addr string) []net.IP {
ips, err := net.LookupIP(addr)
if err == nil {
return ips
}
return []net.IP{net.ParseIP(addr)}
}
convert := func(ip net.IP) (string, string) {
if ip == nil {
return "", "tcp"
}
proto := "tcp4"
if ip.To4() == nil {
proto = "tcp6"
}
return ip.String(), proto
}
if s.hasScheme(schemeHTTP) {
for _, ip := range lookup(s.Host) {
host, proto := convert(ip)
listener, err := net.Listen(proto, net.JoinHostPort(host, strconv.Itoa(s.Port)))
if err != nil {
return err
}
if s.Host == "" || s.Port == 0 {
h, p, err := swag.SplitHostPort(listener.Addr().String())
if err != nil {
return err
}
s.Host = h
s.Port = p
}
s.httpServerL = append(s.httpServerL, listener)
}
}
if s.hasScheme(schemeHTTPS) {
for _, ip := range lookup(s.TLSHost) {
host, proto := convert(ip)
tlsListener, err := net.Listen(proto, net.JoinHostPort(host, strconv.Itoa(s.TLSPort)))
if err != nil {
return err
}
if s.TLSHost == "" || s.TLSPort == 0 {
sh, sp, err := swag.SplitHostPort(tlsListener.Addr().String())
if err != nil {
return err
}
s.TLSHost = sh
s.TLSPort = sp
}
s.httpsServerL = append(s.httpsServerL, tlsListener)
}
}
s.hasListeners = true
return nil
}
// Shutdown server and clean up resources
func (s *Server) Shutdown() error {
if atomic.CompareAndSwapInt32(&s.shuttingDown, 0, 1) {
close(s.shutdown)
}
return nil
}
func (s *Server) handleShutdown(wg *sync.WaitGroup, serversPtr *[]*http.Server) {
// wg.Done must occur last, after s.api.ServerShutdown()
// (to preserve old behavior)
defer wg.Done()
<-s.shutdown
servers := *serversPtr
ctx, cancel := context.WithTimeout(context.TODO(), s.GracefulTimeout)
defer cancel()
// first execute the pre-shutdown hook
s.api.PreServerShutdown()
shutdownChan := make(chan bool)
for i := range servers {
server := servers[i]
go func() {
var success bool
defer func() {
shutdownChan <- success
}()
if err := server.Shutdown(ctx); err != nil {
// Error from closing listeners, or context timeout:
s.Logf("HTTP server Shutdown: %v", err)
} else {
success = true
}
}()
}
// Wait until all listeners have successfully shut down before calling ServerShutdown
success := true
for range servers {
success = success && <-shutdownChan
}
if success {
s.api.ServerShutdown()
}
}
// GetHandler returns a handler useful for testing
func (s *Server) GetHandler() http.Handler {
return s.handler
}
// SetHandler allows for setting a http handler on this server
func (s *Server) SetHandler(handler http.Handler) {
s.handler = handler
}
// UnixListener returns the domain socket listener
func (s *Server) UnixListener() (net.Listener, error) {
if !s.hasListeners {
if err := s.Listen(); err != nil {
return nil, err
}
}
return s.domainSocketL, nil
}
// HTTPListener returns the http listener
func (s *Server) HTTPListener() ([]net.Listener, error) {
if !s.hasListeners {
if err := s.Listen(); err != nil {
return nil, err
}
}
return s.httpServerL, nil
}
// TLSListener returns the https listener
func (s *Server) TLSListener() ([]net.Listener, error) {
if !s.hasListeners {
if err := s.Listen(); err != nil {
return nil, err
}
}
return s.httpsServerL, nil
}
func handleInterrupt(once *sync.Once, s *Server) {
once.Do(func() {
for range s.interrupt {
if s.interrupted {
s.Logf("Server already shutting down")
continue
}
s.interrupted = true
s.Logf("Shutting down... ")
if err := s.Shutdown(); err != nil {
s.Logf("HTTP server Shutdown: %v", err)
}
}
})
}
func signalNotify(interrupt chan<- os.Signal) {
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
}

38
api/doc.go Normal file
View File

@@ -0,0 +1,38 @@
// 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 api MinIO Console Server
//
// Schemes:
// http
// ws
// Host: localhost
// BasePath: /api/v1
// Version: 0.1.0
//
// Consumes:
// - application/json
// - multipart/form-data
//
// Produces:
// - application/zip
// - application/octet-stream
// - application/json
//
// swagger:meta
package api

18497
api/embedded_spec.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -14,17 +14,17 @@
// 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 restapi
package api
import (
"context"
"errors"
"strings"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/madmin-go"
"github.com/minio/minio-go/v7"
"github.com/minio/console/models"
"github.com/minio/madmin-go/v3"
)
var (
@@ -53,6 +53,7 @@ var (
ErrAvoidSelfAccountDelete = errors.New("logged in user cannot be deleted by itself")
ErrAccessDenied = errors.New("access denied")
ErrOauth2Provider = errors.New("unable to contact configured identity provider")
ErrOauth2Login = errors.New("unable to login using configured identity provider")
ErrNonUniqueAccessKey = errors.New("access key already in use")
ErrRemoteTierExists = errors.New("specified remote tier already exists")
ErrRemoteTierNotFound = errors.New("specified remote tier was not found")
@@ -71,16 +72,25 @@ var (
ErrEncryptionConfigNotFound = errors.New("encryption configuration not found")
ErrPolicyNotFound = errors.New("policy does not exist")
ErrLoginNotAllowed = errors.New("login not allowed")
ErrSubnetUploadFail = errors.New("SUBNET upload failed")
ErrHealthReportFail = errors.New("failure to generate Health report")
)
type CodedAPIError struct {
Code int
APIError *models.APIError
}
// ErrorWithContext :
func ErrorWithContext(ctx context.Context, err ...interface{}) *models.Error {
errorCode := int32(500)
func ErrorWithContext(ctx context.Context, err ...interface{}) *CodedAPIError {
errorCode := 500
errorMessage := ErrDefault.Error()
var detailedMessage string
var err1 error
var exists bool
if len(err) > 0 {
if err1, exists = err[0].(error); exists {
detailedMessage = err1.Error()
var lastError error
if len(err) > 1 {
if err2, lastExists := err[1].(error); lastExists {
@@ -98,15 +108,22 @@ func ErrorWithContext(ctx context.Context, err ...interface{}) *models.Error {
errorMessage = ErrNotFound.Error()
}
if errors.Is(err1, ErrInvalidLogin) {
detailedMessage = ""
errorCode = 401
errorMessage = ErrInvalidLogin.Error()
}
if strings.Contains(strings.ToLower(err1.Error()), ErrAccessDenied.Error()) {
errorCode = 403
errorMessage = err1.Error()
}
// If the last error is ErrInvalidLogin, this is a login failure
if errors.Is(lastError, ErrInvalidLogin) {
detailedMessage = ""
errorCode = 401
errorMessage = err1.Error()
}
if strings.Contains(err1.Error(), ErrLoginNotAllowed.Error()) {
detailedMessage = ""
errorCode = 400
errorMessage = ErrLoginNotAllowed.Error()
}
@@ -200,6 +217,7 @@ func ErrorWithContext(ctx context.Context, err ...interface{}) *models.Error {
errorMessage = ErrAccessDenied.Error()
}
if madmin.ToErrorResponse(err1).Code == "InvalidAccessKeyId" {
errorCode = 401
errorMessage = ErrInvalidSession.Error()
}
@@ -239,6 +257,7 @@ func ErrorWithContext(ctx context.Context, err ...interface{}) *models.Error {
errorCode = 400
errorMessage = "Bucket already exists"
}
LogError("ErrorWithContext:%v", err...)
LogIf(ctx, err1, err...)
}
@@ -249,11 +268,11 @@ func ErrorWithContext(ctx context.Context, err ...interface{}) *models.Error {
}
}
}
return &models.Error{Code: errorCode, Message: swag.String(errorMessage), DetailedMessage: swag.String(err1.Error())}
return &CodedAPIError{Code: errorCode, APIError: &models.APIError{Message: errorMessage, DetailedMessage: detailedMessage}}
}
// Error receives an errors object and parse it against k8sErrors, returns the right errors code paired with a generic errors message
func Error(err ...interface{}) *models.Error {
func Error(err ...interface{}) *CodedAPIError {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
return ErrorWithContext(ctx, err...)

159
api/errors_test.go Normal file
View File

@@ -0,0 +1,159 @@
// 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"
"testing"
"github.com/minio/console/models"
"github.com/stretchr/testify/assert"
)
func TestError(t *testing.T) {
type args struct {
err []interface{}
}
type testError struct {
name string
args args
want *CodedAPIError
}
var tests []testError
type expectedError struct {
err error
code int
}
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},
"ErrNonUniqueAccessKey": {code: 500, err: ErrNonUniqueAccessKey},
"ErrRemoteTierExists": {code: 400, err: ErrRemoteTierExists},
"ErrRemoteTierNotFound": {code: 400, err: ErrRemoteTierNotFound},
"ErrRemoteTierUppercase": {code: 400, err: ErrRemoteTierUppercase},
"ErrRemoteTierBucketNotFound": {code: 400, err: ErrRemoteTierBucketNotFound},
"ErrRemoteInvalidCredentials": {code: 403, err: ErrRemoteInvalidCredentials},
"ErrTooFewNodes": {code: 500, err: ErrTooFewNodes},
"ErrUnableToGetTenantUsage": {code: 500, err: ErrUnableToGetTenantUsage},
"ErrTooManyNodes": {code: 500, err: ErrTooManyNodes},
"ErrAccessDenied": {code: 403, err: ErrAccessDenied},
"ErrTooFewAvailableNodes": {code: 500, err: ErrTooFewAvailableNodes},
"ErrFewerThanFourNodes": {code: 500, err: ErrFewerThanFourNodes},
"ErrUnableToGetTenantLogs": {code: 500, err: ErrUnableToGetTenantLogs},
"ErrUnableToUpdateTenantCertificates": {code: 500, err: ErrUnableToUpdateTenantCertificates},
"ErrUpdatingEncryptionConfig": {code: 500, err: ErrUpdatingEncryptionConfig},
"ErrDeletingEncryptionConfig": {code: 500, err: ErrDeletingEncryptionConfig},
"ErrEncryptionConfigNotFound": {code: 404, err: ErrEncryptionConfigNotFound},
}
for k, e := range appErrors {
tests = append(tests, testError{
name: fmt.Sprintf("%s error", k),
args: args{
err: []interface{}{e.err},
},
want: &CodedAPIError{
Code: e.code,
APIError: &models.APIError{Message: e.err.Error(), DetailedMessage: e.err.Error()},
},
})
}
tests = append(tests,
testError{
name: "passing multiple errors but ErrInvalidLogin is last",
args: args{
err: []interface{}{ErrDefault, ErrInvalidLogin},
},
want: &CodedAPIError{
Code: int(401),
APIError: &models.APIError{Message: ErrDefault.Error(), DetailedMessage: ""},
},
})
tests = append(tests,
testError{
name: "login error omits detailedMessage",
args: args{
err: []interface{}{ErrInvalidLogin},
},
want: &CodedAPIError{
Code: int(401),
APIError: &models.APIError{Message: ErrInvalidLogin.Error(), DetailedMessage: ""},
},
})
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
got := Error(tt.args.err...)
assert.Equalf(t, tt.want.Code, got.Code, "Error(%v) Got (%v)", tt.want.Code, got.Code)
assert.Equalf(t, tt.want.APIError.DetailedMessage, got.APIError.DetailedMessage, "Error(%s) Got (%s)", tt.want.APIError.DetailedMessage, got.APIError.DetailedMessage)
})
}
}
func TestErrorWithContext(t *testing.T) {
type args struct {
ctx context.Context
err []interface{}
}
tests := []struct {
name string
args args
want *CodedAPIError
}{
{
name: "default error",
args: args{
ctx: context.Background(),
err: []interface{}{ErrDefault},
},
want: &CodedAPIError{
Code: 500, APIError: &models.APIError{Message: ErrDefault.Error(), DetailedMessage: ErrDefault.Error()},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
assert.Equalf(t, tt.want, ErrorWithContext(tt.args.ctx, tt.args.err...), "ErrorWithContext(%v, %v)", tt.args.ctx, tt.args.err)
})
}
}

71
api/license.go Normal file
View File

@@ -0,0 +1,71 @@
// 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
}
}

83
api/logs.go Normal file
View File

@@ -0,0 +1,83 @@
// 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"
"log"
"os"
"github.com/minio/cli"
)
var (
infoLog = log.New(os.Stdout, "I: ", log.LstdFlags)
errorLog = log.New(os.Stdout, "E: ", log.LstdFlags)
)
func logInfo(msg string, data ...interface{}) {
infoLog.Printf(msg+"\n", data...)
}
func logError(msg string, data ...interface{}) {
errorLog.Printf(msg+"\n", data...)
}
func logIf(_ context.Context, _ error, _ ...interface{}) {
}
// globally changeable logger styles
var (
LogInfo = logInfo
LogError = logError
LogIf = logIf
)
// Context captures all command line flags values
type Context struct {
Host string
HTTPPort, HTTPSPort int
TLSRedirect string
// Legacy options, TODO: remove in future
TLSCertificate, TLSKey, TLSca string
}
// Load loads api Context from command line context.
func (c *Context) Load(ctx *cli.Context) error {
*c = Context{
Host: ctx.String("host"),
HTTPPort: ctx.Int("port"),
HTTPSPort: ctx.Int("tls-port"),
TLSRedirect: ctx.String("tls-redirect"),
// Legacy options to be removed.
TLSCertificate: ctx.String("tls-certificate"),
TLSKey: ctx.String("tls-key"),
TLSca: ctx.String("tls-ca"),
}
if c.HTTPPort > 65535 {
return errors.New("invalid argument --port out of range - ports can range from 1-65535")
}
if c.HTTPSPort > 65535 {
return errors.New("invalid argument --tls-port out of range - ports can range from 1-65535")
}
if c.TLSRedirect != "on" && c.TLSRedirect != "off" {
return errors.New("invalid argument --tls-redirect only accepts either 'on' or 'off'")
}
return nil
}

110
api/logs_test.go Normal file
View File

@@ -0,0 +1,110 @@
// 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 (
"flag"
"fmt"
"testing"
"github.com/minio/cli"
"github.com/stretchr/testify/assert"
)
func TestContext_Load(t *testing.T) {
type fields struct {
Host string
HTTPPort int
HTTPSPort int
TLSRedirect string
TLSCertificate string
TLSKey string
TLSca string
}
type args struct {
values map[string]string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "valid args",
args: args{
values: map[string]string{
"tls-redirect": "on",
},
},
wantErr: false,
},
{
name: "invalid args",
args: args{
values: map[string]string{
"tls-redirect": "aaaa",
},
},
wantErr: true,
},
{
name: "invalid port http",
args: args{
values: map[string]string{
"tls-redirect": "on",
"port": "65536",
},
},
wantErr: true,
},
{
name: "invalid port https",
args: args{
values: map[string]string{
"tls-redirect": "on",
"port": "65534",
"tls-port": "65536",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(_ *testing.T) {
c := &Context{}
fs := flag.NewFlagSet("flags", flag.ContinueOnError)
for k, v := range tt.args.values {
fs.String(k, v, "ok")
}
ctx := cli.NewContext(nil, fs, &cli.Context{})
err := c.Load(ctx)
if tt.wantErr {
assert.NotNilf(t, err, fmt.Sprintf("Load(%v)", err))
} else {
assert.Nilf(t, err, fmt.Sprintf("Load(%v)", err))
}
})
}
}
func Test_logInfo(_ *testing.T) {
logInfo("message", nil)
}

View File

@@ -1,7 +1,7 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
// 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

View File

@@ -1,7 +1,7 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
// 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

View File

@@ -1,7 +1,7 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
// 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
@@ -66,7 +66,7 @@ type AccountChangePasswordDefault struct {
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
Payload *models.APIError `json:"body,omitempty"`
}
// NewAccountChangePasswordDefault creates AccountChangePasswordDefault with default headers values
@@ -92,13 +92,13 @@ func (o *AccountChangePasswordDefault) SetStatusCode(code int) {
}
// WithPayload adds the payload to the account change password default response
func (o *AccountChangePasswordDefault) WithPayload(payload *models.Error) *AccountChangePasswordDefault {
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.Error) {
func (o *AccountChangePasswordDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}

View File

@@ -1,7 +1,7 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
// 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

View File

@@ -1,7 +1,7 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
// 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

View File

@@ -1,7 +1,7 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
// 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

View File

@@ -1,7 +1,7 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
// 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
@@ -66,7 +66,7 @@ type ChangeUserPasswordDefault struct {
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
Payload *models.APIError `json:"body,omitempty"`
}
// NewChangeUserPasswordDefault creates ChangeUserPasswordDefault with default headers values
@@ -92,13 +92,13 @@ func (o *ChangeUserPasswordDefault) SetStatusCode(code int) {
}
// WithPayload adds the payload to the change user password default response
func (o *ChangeUserPasswordDefault) WithPayload(payload *models.Error) *ChangeUserPasswordDefault {
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.Error) {
func (o *ChangeUserPasswordDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}

View File

@@ -1,7 +1,7 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
// 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

View File

@@ -0,0 +1,73 @@
// 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 auth
// 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"
)
// LoginHandlerFunc turns a function with the right signature into a login handler
type LoginHandlerFunc func(LoginParams) middleware.Responder
// Handle executing the request and returning a response
func (fn LoginHandlerFunc) Handle(params LoginParams) middleware.Responder {
return fn(params)
}
// LoginHandler interface for that can handle valid login params
type LoginHandler interface {
Handle(LoginParams) middleware.Responder
}
// NewLogin creates a new http.Handler for the login operation
func NewLogin(ctx *middleware.Context, handler LoginHandler) *Login {
return &Login{Context: ctx, Handler: handler}
}
/*
Login swagger:route POST /login Auth login
Login to Console
*/
type Login struct {
Context *middleware.Context
Handler LoginHandler
}
func (o *Login) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewLoginParams()
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) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,73 @@
// 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 auth
// 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"
)
// LoginDetailHandlerFunc turns a function with the right signature into a login detail handler
type LoginDetailHandlerFunc func(LoginDetailParams) middleware.Responder
// Handle executing the request and returning a response
func (fn LoginDetailHandlerFunc) Handle(params LoginDetailParams) middleware.Responder {
return fn(params)
}
// LoginDetailHandler interface for that can handle valid login detail params
type LoginDetailHandler interface {
Handle(LoginDetailParams) middleware.Responder
}
// NewLoginDetail creates a new http.Handler for the login detail operation
func NewLoginDetail(ctx *middleware.Context, handler LoginDetailHandler) *LoginDetail {
return &LoginDetail{Context: ctx, Handler: handler}
}
/*
LoginDetail swagger:route GET /login Auth loginDetail
Returns login strategy, form or sso.
*/
type LoginDetail struct {
Context *middleware.Context
Handler LoginDetailHandler
}
func (o *LoginDetail) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewLoginDetailParams()
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) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,63 @@
// 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 auth
// 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/errors"
"github.com/go-openapi/runtime/middleware"
)
// NewLoginDetailParams creates a new LoginDetailParams object
//
// There are no default values defined in the spec.
func NewLoginDetailParams() LoginDetailParams {
return LoginDetailParams{}
}
// LoginDetailParams contains all the bound params for the login detail operation
// typically these are obtained from a http.Request
//
// swagger:parameters LoginDetail
type LoginDetailParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}
// 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 NewLoginDetailParams() beforehand.
func (o *LoginDetailParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,135 @@
// 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 auth
// 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"
)
// LoginDetailOKCode is the HTTP code returned for type LoginDetailOK
const LoginDetailOKCode int = 200
/*
LoginDetailOK A successful response.
swagger:response loginDetailOK
*/
type LoginDetailOK struct {
/*
In: Body
*/
Payload *models.LoginDetails `json:"body,omitempty"`
}
// NewLoginDetailOK creates LoginDetailOK with default headers values
func NewLoginDetailOK() *LoginDetailOK {
return &LoginDetailOK{}
}
// WithPayload adds the payload to the login detail o k response
func (o *LoginDetailOK) WithPayload(payload *models.LoginDetails) *LoginDetailOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login detail o k response
func (o *LoginDetailOK) SetPayload(payload *models.LoginDetails) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginDetailOK) 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
}
}
}
/*
LoginDetailDefault Generic error response.
swagger:response loginDetailDefault
*/
type LoginDetailDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewLoginDetailDefault creates LoginDetailDefault with default headers values
func NewLoginDetailDefault(code int) *LoginDetailDefault {
if code <= 0 {
code = 500
}
return &LoginDetailDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the login detail default response
func (o *LoginDetailDefault) WithStatusCode(code int) *LoginDetailDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the login detail default response
func (o *LoginDetailDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the login detail default response
func (o *LoginDetailDefault) WithPayload(payload *models.APIError) *LoginDetailDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login detail default response
func (o *LoginDetailDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginDetailDefault) 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

@@ -0,0 +1,104 @@
// 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 auth
// 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"
)
// LoginDetailURL generates an URL for the login detail operation
type LoginDetailURL 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 *LoginDetailURL) WithBasePath(bp string) *LoginDetailURL {
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 *LoginDetailURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *LoginDetailURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/login"
_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 *LoginDetailURL) 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 *LoginDetailURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *LoginDetailURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on LoginDetailURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on LoginDetailURL")
}
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 *LoginDetailURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,73 @@
// 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 auth
// 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"
)
// LoginOauth2AuthHandlerFunc turns a function with the right signature into a login oauth2 auth handler
type LoginOauth2AuthHandlerFunc func(LoginOauth2AuthParams) middleware.Responder
// Handle executing the request and returning a response
func (fn LoginOauth2AuthHandlerFunc) Handle(params LoginOauth2AuthParams) middleware.Responder {
return fn(params)
}
// LoginOauth2AuthHandler interface for that can handle valid login oauth2 auth params
type LoginOauth2AuthHandler interface {
Handle(LoginOauth2AuthParams) middleware.Responder
}
// NewLoginOauth2Auth creates a new http.Handler for the login oauth2 auth operation
func NewLoginOauth2Auth(ctx *middleware.Context, handler LoginOauth2AuthHandler) *LoginOauth2Auth {
return &LoginOauth2Auth{Context: ctx, Handler: handler}
}
/*
LoginOauth2Auth swagger:route POST /login/oauth2/auth Auth loginOauth2Auth
Identity Provider oauth2 callback endpoint.
*/
type LoginOauth2Auth struct {
Context *middleware.Context
Handler LoginOauth2AuthHandler
}
func (o *LoginOauth2Auth) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewLoginOauth2AuthParams()
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) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,101 @@
// 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 auth
// 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"
)
// NewLoginOauth2AuthParams creates a new LoginOauth2AuthParams object
//
// There are no default values defined in the spec.
func NewLoginOauth2AuthParams() LoginOauth2AuthParams {
return LoginOauth2AuthParams{}
}
// LoginOauth2AuthParams contains all the bound params for the login oauth2 auth operation
// typically these are obtained from a http.Request
//
// swagger:parameters LoginOauth2Auth
type LoginOauth2AuthParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.LoginOauth2AuthRequest
}
// 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 NewLoginOauth2AuthParams() beforehand.
func (o *LoginOauth2AuthParams) 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.LoginOauth2AuthRequest
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

@@ -0,0 +1,115 @@
// 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 auth
// 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"
)
// LoginOauth2AuthNoContentCode is the HTTP code returned for type LoginOauth2AuthNoContent
const LoginOauth2AuthNoContentCode int = 204
/*
LoginOauth2AuthNoContent A successful login.
swagger:response loginOauth2AuthNoContent
*/
type LoginOauth2AuthNoContent struct {
}
// NewLoginOauth2AuthNoContent creates LoginOauth2AuthNoContent with default headers values
func NewLoginOauth2AuthNoContent() *LoginOauth2AuthNoContent {
return &LoginOauth2AuthNoContent{}
}
// WriteResponse to the client
func (o *LoginOauth2AuthNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(204)
}
/*
LoginOauth2AuthDefault Generic error response.
swagger:response loginOauth2AuthDefault
*/
type LoginOauth2AuthDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.APIError `json:"body,omitempty"`
}
// NewLoginOauth2AuthDefault creates LoginOauth2AuthDefault with default headers values
func NewLoginOauth2AuthDefault(code int) *LoginOauth2AuthDefault {
if code <= 0 {
code = 500
}
return &LoginOauth2AuthDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the login oauth2 auth default response
func (o *LoginOauth2AuthDefault) WithStatusCode(code int) *LoginOauth2AuthDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the login oauth2 auth default response
func (o *LoginOauth2AuthDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the login oauth2 auth default response
func (o *LoginOauth2AuthDefault) WithPayload(payload *models.APIError) *LoginOauth2AuthDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login oauth2 auth default response
func (o *LoginOauth2AuthDefault) SetPayload(payload *models.APIError) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginOauth2AuthDefault) 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

@@ -0,0 +1,104 @@
// 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 auth
// 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"
)
// LoginOauth2AuthURL generates an URL for the login oauth2 auth operation
type LoginOauth2AuthURL 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 *LoginOauth2AuthURL) WithBasePath(bp string) *LoginOauth2AuthURL {
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 *LoginOauth2AuthURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *LoginOauth2AuthURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/login/oauth2/auth"
_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 *LoginOauth2AuthURL) 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 *LoginOauth2AuthURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *LoginOauth2AuthURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on LoginOauth2AuthURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on LoginOauth2AuthURL")
}
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 *LoginOauth2AuthURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

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