diff --git a/go.mod b/go.mod index bc684cbf68..2cbd12f5ff 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/json-iterator/go v1.1.9 github.com/minio/cli v1.22.0 github.com/minio/mc v0.0.0-20200415193718-68b638f2f96c - github.com/minio/minio v0.0.0-20200428222040-c3c3e9087bc1 + github.com/minio/minio v0.0.0-20200501193630-d1c8e9f31ba0 github.com/minio/minio-go/v6 v6.0.55-0.20200424204115-7506d2996b22 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/satori/go.uuid v1.2.0 diff --git a/go.sum b/go.sum index b7d4677ca2..96d519812d 100644 --- a/go.sum +++ b/go.sum @@ -30,7 +30,6 @@ github.com/alecthomas/participle v0.2.1 h1:4AVLj1viSGa4LG5HDXKXrm5xRx19SB/rS/skP github.com/alecthomas/participle v0.2.1/go.mod h1:SW6HZGeZgSIpcUWX3fXpfZhuaWHnmoD5KCVaqSaNTkk= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 h1:nWDRPCyCltiTsANwC/n3QZH7Vww33Npq9MKqlwRzI/c= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -42,7 +41,6 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/aws-sdk-go v1.20.21/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2 h1:M+TYzBcNIRyzPRg66ndEqUMd7oWDmhvdQmaPC6EZNwM= github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2/go.mod h1:RDu/qcrnpEdJC/p8tx34+YBFqqX71lB7dOX9QE+ZC4M= @@ -403,10 +401,9 @@ github.com/minio/lsync v1.0.1 h1:AVvILxA976xc27hstd1oR+X9PQG0sPSom1MNb1ImfUs= github.com/minio/lsync v1.0.1/go.mod h1:tCFzfo0dlvdGl70IT4IAK/5Wtgb0/BrTmo/jE8pArKA= github.com/minio/mc v0.0.0-20200415193718-68b638f2f96c h1:JLr0fYpCleodj9nGB5hfsJU2zPdnNQKqa2bYsIvPhVw= github.com/minio/mc v0.0.0-20200415193718-68b638f2f96c/go.mod h1:l9PuOY62zT7AQJqopDjfo/T22AIBJSb2yXPVZf4RlhM= -github.com/minio/minio v0.0.0-20200415191640-bde0f444dbab h1:9hlqghJl3e3HorXa6ADWsz6ECq790t4iQs07VD9JctM= github.com/minio/minio v0.0.0-20200415191640-bde0f444dbab/go.mod h1:v8oQPMMaTkjDwp5cOz1WCElA4Ik+X+0y4On+VMk0fis= -github.com/minio/minio v0.0.0-20200428222040-c3c3e9087bc1 h1:DQjH/653WCerOeZCp3BxAgkmRiQybHYiprbTFs+brgA= -github.com/minio/minio v0.0.0-20200428222040-c3c3e9087bc1/go.mod h1:HxnN5FYGIii8ZH6d+LH5UNOSSIonbJkYPqP6gWelVO0= +github.com/minio/minio v0.0.0-20200501193630-d1c8e9f31ba0 h1:QxIz36O01LbKqJiz6HKeKCOC2afgydspkpahQ807msY= +github.com/minio/minio v0.0.0-20200501193630-d1c8e9f31ba0/go.mod h1:Vhlqz7Se0EgpgFiVxpvzF4Zz/h2LMx+EPKH96Aera5U= github.com/minio/minio-go/v6 v6.0.53 h1:8jzpwiOzZ5Iz7/goFWqNZRICbyWYShbb5rARjrnSCNI= github.com/minio/minio-go/v6 v6.0.53/go.mod h1:DIvC/IApeHX8q1BAMVCXSXwpmrmM+I+iBvhvztQorfI= github.com/minio/minio-go/v6 v6.0.55-0.20200424204115-7506d2996b22 h1:nZEve4vdUhwHBoV18zRvPDgjL6NYyDJE5QJvz3l9bRs= @@ -555,6 +552,7 @@ github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 h1:0ngsPmuP6XIjiFRN github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -596,6 +594,7 @@ github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -626,6 +625,7 @@ golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f h1:kz4KIr+xcPUsI3VMoqWfPMvtnJ6MGfiVwsWSVzphMO4= golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg= @@ -635,6 +635,8 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -665,6 +667,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -713,7 +717,13 @@ golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190914235951-31e00f45c22e h1:nOOVVcLC+/3MeovP40q5lCiWmP1Z1DaN8yn8ngU63hw= golang.org/x/tools v0.0.0-20190914235951-31e00f45c22e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200428211428-0c9eba77bc32 h1:Xvf3ZQTm5bjXPxhI7g+dwqsCqadK1rcNtwtszuatetk= +golang.org/x/tools v0.0.0-20200428211428-0c9eba77bc32/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.5.0 h1:lj9SyhMzyoa38fgFF0oO2T6pjs5IzkLPKfVtxpyCRMM= google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= diff --git a/models/service_account.go b/models/service_account_request.go similarity index 77% rename from models/service_account.go rename to models/service_account_request.go index 25ef80c891..a8ca885f4c 100644 --- a/models/service_account.go +++ b/models/service_account_request.go @@ -27,22 +27,22 @@ import ( "github.com/go-openapi/swag" ) -// ServiceAccount service account +// ServiceAccountRequest service account request // -// swagger:model serviceAccount -type ServiceAccount struct { +// swagger:model serviceAccountRequest +type ServiceAccountRequest struct { // policy to be applied to the Service Account if any Policy string `json:"policy,omitempty"` } -// Validate validates this service account -func (m *ServiceAccount) Validate(formats strfmt.Registry) error { +// Validate validates this service account request +func (m *ServiceAccountRequest) Validate(formats strfmt.Registry) error { return nil } // MarshalBinary interface implementation -func (m *ServiceAccount) MarshalBinary() ([]byte, error) { +func (m *ServiceAccountRequest) MarshalBinary() ([]byte, error) { if m == nil { return nil, nil } @@ -50,8 +50,8 @@ func (m *ServiceAccount) MarshalBinary() ([]byte, error) { } // UnmarshalBinary interface implementation -func (m *ServiceAccount) UnmarshalBinary(b []byte) error { - var res ServiceAccount +func (m *ServiceAccountRequest) UnmarshalBinary(b []byte) error { + var res ServiceAccountRequest if err := swag.ReadJSON(b, &res); err != nil { return err } diff --git a/models/service_accounts.go b/models/service_accounts.go new file mode 100644 index 0000000000..ed282fafcd --- /dev/null +++ b/models/service_accounts.go @@ -0,0 +1,37 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 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 . +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "github.com/go-openapi/strfmt" +) + +// ServiceAccounts service accounts +// +// swagger:model serviceAccounts +type ServiceAccounts []string + +// Validate validates this service accounts +func (m ServiceAccounts) Validate(formats strfmt.Registry) error { + return nil +} diff --git a/restapi/admin_info.go b/restapi/admin_info.go index 952aa938c4..d030dfc67f 100644 --- a/restapi/admin_info.go +++ b/restapi/admin_info.go @@ -41,21 +41,21 @@ func registerAdminInfoHandlers(api *operations.McsAPI) { } -type UsageInfo struct { +type usageInfo struct { Buckets int64 Objects int64 Usage int64 } -// getAdminInfo invokes admin info and returns a parsed `UsageInfo` structure -func getAdminInfo(ctx context.Context, client MinioAdmin) (*UsageInfo, error) { +// getAdminInfo invokes admin info and returns a parsed `usageInfo` structure +func getAdminInfo(ctx context.Context, client MinioAdmin) (*usageInfo, error) { serverInfo, err := client.serverInfo(ctx) if err != nil { return nil, err } // we are trimming uint64 to int64 this will report an incorrect measurement for numbers greater than // 9,223,372,036,854,775,807 - return &UsageInfo{ + return &usageInfo{ Buckets: int64(serverInfo.Buckets.Count), Objects: int64(serverInfo.Objects.Count), Usage: int64(serverInfo.Usage.Size), diff --git a/restapi/client-admin.go b/restapi/client-admin.go index 54ed2ed2a7..81ccef5b6e 100644 --- a/restapi/client-admin.go +++ b/restapi/client-admin.go @@ -83,6 +83,8 @@ type MinioAdmin interface { serviceTrace(ctx context.Context, allTrace, errTrace bool) <-chan madmin.ServiceTraceInfo // Service Accounts addServiceAccount(ctx context.Context, policy *iampolicy.Policy) (mauth.Credentials, error) + listServiceAccounts(ctx context.Context) (madmin.ListServiceAccountsResp, error) + deleteServiceAccount(ctx context.Context, serviceAccount string) error } // Interface implementation @@ -208,11 +210,30 @@ func (ac adminClient) addServiceAccount(ctx context.Context, policy *iampolicy.P return ac.client.AddServiceAccount(ctx, policy) } +// implements madmin.ListServiceAccounts() +func (ac adminClient) listServiceAccounts(ctx context.Context) (madmin.ListServiceAccountsResp, error) { + return ac.client.ListServiceAccounts(ctx) +} + +// implements madmin.DeleteServiceAccount() +func (ac adminClient) deleteServiceAccount(ctx context.Context, serviceAccount string) error { + return ac.client.DeleteServiceAccount(ctx, serviceAccount) +} + func newMAdminClient(jwt string) (*madmin.AdminClient, error) { claims, err := auth.JWTAuthenticate(jwt) if err != nil { return nil, err } + adminClient, err := newAdminFromClaims(claims) + if err != nil { + return nil, err + } + return adminClient, nil +} + +// newAdminFromClaims creates a minio admin from Decrypted claims using Assume role credentials +func newAdminFromClaims(claims *auth.DecryptedClaims) (*madmin.AdminClient, error) { adminClient, err := madmin.NewWithOptions(getMinIOEndpoint(), &madmin.Options{ Creds: credentials.NewStaticV4(claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken), Secure: getMinIOEndpointIsSecure(), diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index 1dacc48a34..1394c24ab2 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -993,6 +993,41 @@ func init() { } }, "/service-accounts": { + "get": { + "tags": [ + "UserAPI" + ], + "summary": "List User's Service Accounts", + "operationId": "ListUserServiceAccounts", + "parameters": [ + { + "type": "integer", + "format": "int32", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "format": "int32", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/serviceAccounts" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + }, "post": { "tags": [ "UserAPI" @@ -1005,7 +1040,7 @@ func init() { "in": "body", "required": true, "schema": { - "$ref": "#/definitions/serviceAccount" + "$ref": "#/definitions/serviceAccountRequest" } } ], @@ -1025,6 +1060,34 @@ func init() { } } }, + "/service-accounts/{access_key}": { + "delete": { + "tags": [ + "UserAPI" + ], + "summary": "Delete Service Account", + "operationId": "DeleteServiceAccount", + "parameters": [ + { + "type": "string", + "name": "access_key", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "A successful response." + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/service/restart": { "post": { "tags": [ @@ -1883,15 +1946,6 @@ func init() { } } }, - "serviceAccount": { - "type": "object", - "properties": { - "policy": { - "type": "string", - "title": "policy to be applied to the Service Account if any" - } - } - }, "serviceAccountCreds": { "type": "object", "properties": { @@ -1903,6 +1957,21 @@ func init() { } } }, + "serviceAccountRequest": { + "type": "object", + "properties": { + "policy": { + "type": "string", + "title": "policy to be applied to the Service Account if any" + } + } + }, + "serviceAccounts": { + "type": "array", + "items": { + "type": "string" + } + }, "sessionResponse": { "type": "object", "properties": { @@ -3033,6 +3102,41 @@ func init() { } }, "/service-accounts": { + "get": { + "tags": [ + "UserAPI" + ], + "summary": "List User's Service Accounts", + "operationId": "ListUserServiceAccounts", + "parameters": [ + { + "type": "integer", + "format": "int32", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "format": "int32", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/serviceAccounts" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + }, "post": { "tags": [ "UserAPI" @@ -3045,7 +3149,7 @@ func init() { "in": "body", "required": true, "schema": { - "$ref": "#/definitions/serviceAccount" + "$ref": "#/definitions/serviceAccountRequest" } } ], @@ -3065,6 +3169,34 @@ func init() { } } }, + "/service-accounts/{access_key}": { + "delete": { + "tags": [ + "UserAPI" + ], + "summary": "Delete Service Account", + "operationId": "DeleteServiceAccount", + "parameters": [ + { + "type": "string", + "name": "access_key", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "A successful response." + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/service/restart": { "post": { "tags": [ @@ -3923,15 +4055,6 @@ func init() { } } }, - "serviceAccount": { - "type": "object", - "properties": { - "policy": { - "type": "string", - "title": "policy to be applied to the Service Account if any" - } - } - }, "serviceAccountCreds": { "type": "object", "properties": { @@ -3943,6 +4066,21 @@ func init() { } } }, + "serviceAccountRequest": { + "type": "object", + "properties": { + "policy": { + "type": "string", + "title": "policy to be applied to the Service Account if any" + } + } + }, + "serviceAccounts": { + "type": "array", + "items": { + "type": "string" + } + }, "sessionResponse": { "type": "object", "properties": { diff --git a/restapi/operations/mcs_api.go b/restapi/operations/mcs_api.go index d60e825156..c57c0ae649 100644 --- a/restapi/operations/mcs_api.go +++ b/restapi/operations/mcs_api.go @@ -105,6 +105,9 @@ func NewMcsAPI(spec *loads.Document) *McsAPI { UserAPIDeleteBucketEventHandler: user_api.DeleteBucketEventHandlerFunc(func(params user_api.DeleteBucketEventParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation user_api.DeleteBucketEvent has not yet been implemented") }), + UserAPIDeleteServiceAccountHandler: user_api.DeleteServiceAccountHandlerFunc(func(params user_api.DeleteServiceAccountParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation user_api.DeleteServiceAccount has not yet been implemented") + }), AdminAPIGetUserInfoHandler: admin_api.GetUserInfoHandlerFunc(func(params admin_api.GetUserInfoParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.GetUserInfo has not yet been implemented") }), @@ -126,6 +129,9 @@ func NewMcsAPI(spec *loads.Document) *McsAPI { AdminAPIListPoliciesHandler: admin_api.ListPoliciesHandlerFunc(func(params admin_api.ListPoliciesParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.ListPolicies has not yet been implemented") }), + UserAPIListUserServiceAccountsHandler: user_api.ListUserServiceAccountsHandlerFunc(func(params user_api.ListUserServiceAccountsParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation user_api.ListUserServiceAccounts has not yet been implemented") + }), AdminAPIListUsersHandler: admin_api.ListUsersHandlerFunc(func(params admin_api.ListUsersParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.ListUsers has not yet been implemented") }), @@ -263,6 +269,8 @@ type McsAPI struct { UserAPIDeleteBucketHandler user_api.DeleteBucketHandler // UserAPIDeleteBucketEventHandler sets the operation handler for the delete bucket event operation UserAPIDeleteBucketEventHandler user_api.DeleteBucketEventHandler + // UserAPIDeleteServiceAccountHandler sets the operation handler for the delete service account operation + UserAPIDeleteServiceAccountHandler user_api.DeleteServiceAccountHandler // AdminAPIGetUserInfoHandler sets the operation handler for the get user info operation AdminAPIGetUserInfoHandler admin_api.GetUserInfoHandler // AdminAPIGroupInfoHandler sets the operation handler for the group info operation @@ -277,6 +285,8 @@ type McsAPI struct { AdminAPIListGroupsHandler admin_api.ListGroupsHandler // AdminAPIListPoliciesHandler sets the operation handler for the list policies operation AdminAPIListPoliciesHandler admin_api.ListPoliciesHandler + // UserAPIListUserServiceAccountsHandler sets the operation handler for the list user service accounts operation + UserAPIListUserServiceAccountsHandler user_api.ListUserServiceAccountsHandler // AdminAPIListUsersHandler sets the operation handler for the list users operation AdminAPIListUsersHandler admin_api.ListUsersHandler // UserAPILoginHandler sets the operation handler for the login operation @@ -432,6 +442,9 @@ func (o *McsAPI) Validate() error { if o.UserAPIDeleteBucketEventHandler == nil { unregistered = append(unregistered, "user_api.DeleteBucketEventHandler") } + if o.UserAPIDeleteServiceAccountHandler == nil { + unregistered = append(unregistered, "user_api.DeleteServiceAccountHandler") + } if o.AdminAPIGetUserInfoHandler == nil { unregistered = append(unregistered, "admin_api.GetUserInfoHandler") } @@ -453,6 +466,9 @@ func (o *McsAPI) Validate() error { if o.AdminAPIListPoliciesHandler == nil { unregistered = append(unregistered, "admin_api.ListPoliciesHandler") } + if o.UserAPIListUserServiceAccountsHandler == nil { + unregistered = append(unregistered, "user_api.ListUserServiceAccountsHandler") + } if o.AdminAPIListUsersHandler == nil { unregistered = append(unregistered, "admin_api.ListUsersHandler") } @@ -669,6 +685,10 @@ func (o *McsAPI) initHandlerCache() { o.handlers["DELETE"] = make(map[string]http.Handler) } o.handlers["DELETE"]["/buckets/{bucket_name}/events/{arn}"] = user_api.NewDeleteBucketEvent(o.context, o.UserAPIDeleteBucketEventHandler) + if o.handlers["DELETE"] == nil { + o.handlers["DELETE"] = make(map[string]http.Handler) + } + o.handlers["DELETE"]["/service-accounts/{access_key}"] = user_api.NewDeleteServiceAccount(o.context, o.UserAPIDeleteServiceAccountHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } @@ -700,6 +720,10 @@ func (o *McsAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/service-accounts"] = user_api.NewListUserServiceAccounts(o.context, o.UserAPIListUserServiceAccountsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/users"] = admin_api.NewListUsers(o.context, o.AdminAPIListUsersHandler) if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) diff --git a/restapi/operations/user_api/create_service_account_parameters.go b/restapi/operations/user_api/create_service_account_parameters.go index d082bcb788..67d424fdde 100644 --- a/restapi/operations/user_api/create_service_account_parameters.go +++ b/restapi/operations/user_api/create_service_account_parameters.go @@ -53,7 +53,7 @@ type CreateServiceAccountParams struct { Required: true In: body */ - Body *models.ServiceAccount + Body *models.ServiceAccountRequest } // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface @@ -67,7 +67,7 @@ func (o *CreateServiceAccountParams) BindRequest(r *http.Request, route *middlew if runtime.HasBody(r) { defer r.Body.Close() - var body models.ServiceAccount + var body models.ServiceAccountRequest if err := route.Consumer.Consume(r.Body, &body); err != nil { if err == io.EOF { res = append(res, errors.Required("body", "body")) diff --git a/restapi/operations/user_api/delete_service_account.go b/restapi/operations/user_api/delete_service_account.go new file mode 100644 index 0000000000..42227df0b4 --- /dev/null +++ b/restapi/operations/user_api/delete_service_account.go @@ -0,0 +1,90 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 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 . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" + + "github.com/minio/mcs/models" +) + +// DeleteServiceAccountHandlerFunc turns a function with the right signature into a delete service account handler +type DeleteServiceAccountHandlerFunc func(DeleteServiceAccountParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn DeleteServiceAccountHandlerFunc) Handle(params DeleteServiceAccountParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// DeleteServiceAccountHandler interface for that can handle valid delete service account params +type DeleteServiceAccountHandler interface { + Handle(DeleteServiceAccountParams, *models.Principal) middleware.Responder +} + +// NewDeleteServiceAccount creates a new http.Handler for the delete service account operation +func NewDeleteServiceAccount(ctx *middleware.Context, handler DeleteServiceAccountHandler) *DeleteServiceAccount { + return &DeleteServiceAccount{Context: ctx, Handler: handler} +} + +/*DeleteServiceAccount swagger:route DELETE /service-accounts/{access_key} UserAPI deleteServiceAccount + +Delete Service Account + +*/ +type DeleteServiceAccount struct { + Context *middleware.Context + Handler DeleteServiceAccountHandler +} + +func (o *DeleteServiceAccount) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + r = rCtx + } + var Params = NewDeleteServiceAccountParams() + + uprinc, aCtx, err := o.Context.Authorize(r, route) + if err != nil { + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + if aCtx != nil { + r = aCtx + } + var principal *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise + } + + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params, principal) // actually handle the request + + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/restapi/operations/user_api/delete_service_account_parameters.go b/restapi/operations/user_api/delete_service_account_parameters.go new file mode 100644 index 0000000000..d05cc9368a --- /dev/null +++ b/restapi/operations/user_api/delete_service_account_parameters.go @@ -0,0 +1,89 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 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 . +// + +package user_api + +// 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" + "github.com/go-openapi/strfmt" +) + +// NewDeleteServiceAccountParams creates a new DeleteServiceAccountParams object +// no default values defined in spec. +func NewDeleteServiceAccountParams() DeleteServiceAccountParams { + + return DeleteServiceAccountParams{} +} + +// DeleteServiceAccountParams contains all the bound params for the delete service account operation +// typically these are obtained from a http.Request +// +// swagger:parameters DeleteServiceAccount +type DeleteServiceAccountParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + AccessKey string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewDeleteServiceAccountParams() beforehand. +func (o *DeleteServiceAccountParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rAccessKey, rhkAccessKey, _ := route.Params.GetOK("access_key") + if err := o.bindAccessKey(rAccessKey, rhkAccessKey, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindAccessKey binds and validates parameter AccessKey from path. +func (o *DeleteServiceAccountParams) bindAccessKey(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + o.AccessKey = raw + + return nil +} diff --git a/restapi/operations/user_api/delete_service_account_responses.go b/restapi/operations/user_api/delete_service_account_responses.go new file mode 100644 index 0000000000..3049d36e68 --- /dev/null +++ b/restapi/operations/user_api/delete_service_account_responses.go @@ -0,0 +1,113 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 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 . +// + +package user_api + +// 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/mcs/models" +) + +// DeleteServiceAccountNoContentCode is the HTTP code returned for type DeleteServiceAccountNoContent +const DeleteServiceAccountNoContentCode int = 204 + +/*DeleteServiceAccountNoContent A successful response. + +swagger:response deleteServiceAccountNoContent +*/ +type DeleteServiceAccountNoContent struct { +} + +// NewDeleteServiceAccountNoContent creates DeleteServiceAccountNoContent with default headers values +func NewDeleteServiceAccountNoContent() *DeleteServiceAccountNoContent { + + return &DeleteServiceAccountNoContent{} +} + +// WriteResponse to the client +func (o *DeleteServiceAccountNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(204) +} + +/*DeleteServiceAccountDefault Generic error response. + +swagger:response deleteServiceAccountDefault +*/ +type DeleteServiceAccountDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewDeleteServiceAccountDefault creates DeleteServiceAccountDefault with default headers values +func NewDeleteServiceAccountDefault(code int) *DeleteServiceAccountDefault { + if code <= 0 { + code = 500 + } + + return &DeleteServiceAccountDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the delete service account default response +func (o *DeleteServiceAccountDefault) WithStatusCode(code int) *DeleteServiceAccountDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the delete service account default response +func (o *DeleteServiceAccountDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the delete service account default response +func (o *DeleteServiceAccountDefault) WithPayload(payload *models.Error) *DeleteServiceAccountDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the delete service account default response +func (o *DeleteServiceAccountDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *DeleteServiceAccountDefault) 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 + } + } +} diff --git a/restapi/operations/user_api/delete_service_account_urlbuilder.go b/restapi/operations/user_api/delete_service_account_urlbuilder.go new file mode 100644 index 0000000000..a112d60609 --- /dev/null +++ b/restapi/operations/user_api/delete_service_account_urlbuilder.go @@ -0,0 +1,116 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 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 . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" +) + +// DeleteServiceAccountURL generates an URL for the delete service account operation +type DeleteServiceAccountURL struct { + AccessKey string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteServiceAccountURL) WithBasePath(bp string) *DeleteServiceAccountURL { + 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 *DeleteServiceAccountURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *DeleteServiceAccountURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/service-accounts/{access_key}" + + accessKey := o.AccessKey + if accessKey != "" { + _path = strings.Replace(_path, "{access_key}", accessKey, -1) + } else { + return nil, errors.New("accessKey is required on DeleteServiceAccountURL") + } + + _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 *DeleteServiceAccountURL) 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 *DeleteServiceAccountURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *DeleteServiceAccountURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on DeleteServiceAccountURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on DeleteServiceAccountURL") + } + + 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 *DeleteServiceAccountURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/operations/user_api/list_user_service_accounts.go b/restapi/operations/user_api/list_user_service_accounts.go new file mode 100644 index 0000000000..7d86709a7c --- /dev/null +++ b/restapi/operations/user_api/list_user_service_accounts.go @@ -0,0 +1,90 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 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 . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" + + "github.com/minio/mcs/models" +) + +// ListUserServiceAccountsHandlerFunc turns a function with the right signature into a list user service accounts handler +type ListUserServiceAccountsHandlerFunc func(ListUserServiceAccountsParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn ListUserServiceAccountsHandlerFunc) Handle(params ListUserServiceAccountsParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// ListUserServiceAccountsHandler interface for that can handle valid list user service accounts params +type ListUserServiceAccountsHandler interface { + Handle(ListUserServiceAccountsParams, *models.Principal) middleware.Responder +} + +// NewListUserServiceAccounts creates a new http.Handler for the list user service accounts operation +func NewListUserServiceAccounts(ctx *middleware.Context, handler ListUserServiceAccountsHandler) *ListUserServiceAccounts { + return &ListUserServiceAccounts{Context: ctx, Handler: handler} +} + +/*ListUserServiceAccounts swagger:route GET /service-accounts UserAPI listUserServiceAccounts + +List User's Service Accounts + +*/ +type ListUserServiceAccounts struct { + Context *middleware.Context + Handler ListUserServiceAccountsHandler +} + +func (o *ListUserServiceAccounts) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + r = rCtx + } + var Params = NewListUserServiceAccountsParams() + + uprinc, aCtx, err := o.Context.Authorize(r, route) + if err != nil { + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + if aCtx != nil { + r = aCtx + } + var principal *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise + } + + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params, principal) // actually handle the request + + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/restapi/operations/user_api/list_user_service_accounts_parameters.go b/restapi/operations/user_api/list_user_service_accounts_parameters.go new file mode 100644 index 0000000000..d930490928 --- /dev/null +++ b/restapi/operations/user_api/list_user_service_accounts_parameters.go @@ -0,0 +1,130 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 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 . +// + +package user_api + +// 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" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewListUserServiceAccountsParams creates a new ListUserServiceAccountsParams object +// no default values defined in spec. +func NewListUserServiceAccountsParams() ListUserServiceAccountsParams { + + return ListUserServiceAccountsParams{} +} + +// ListUserServiceAccountsParams contains all the bound params for the list user service accounts operation +// typically these are obtained from a http.Request +// +// swagger:parameters ListUserServiceAccounts +type ListUserServiceAccountsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + In: query + */ + Limit *int32 + /* + In: query + */ + Offset *int32 +} + +// 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 NewListUserServiceAccountsParams() beforehand. +func (o *ListUserServiceAccountsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qLimit, qhkLimit, _ := qs.GetOK("limit") + if err := o.bindLimit(qLimit, qhkLimit, route.Formats); err != nil { + res = append(res, err) + } + + qOffset, qhkOffset, _ := qs.GetOK("offset") + if err := o.bindOffset(qOffset, qhkOffset, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindLimit binds and validates parameter Limit from query. +func (o *ListUserServiceAccountsParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt32(raw) + if err != nil { + return errors.InvalidType("limit", "query", "int32", raw) + } + o.Limit = &value + + return nil +} + +// bindOffset binds and validates parameter Offset from query. +func (o *ListUserServiceAccountsParams) bindOffset(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt32(raw) + if err != nil { + return errors.InvalidType("offset", "query", "int32", raw) + } + o.Offset = &value + + return nil +} diff --git a/restapi/operations/user_api/list_user_service_accounts_responses.go b/restapi/operations/user_api/list_user_service_accounts_responses.go new file mode 100644 index 0000000000..8e2b8ebd35 --- /dev/null +++ b/restapi/operations/user_api/list_user_service_accounts_responses.go @@ -0,0 +1,136 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 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 . +// + +package user_api + +// 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/mcs/models" +) + +// ListUserServiceAccountsOKCode is the HTTP code returned for type ListUserServiceAccountsOK +const ListUserServiceAccountsOKCode int = 200 + +/*ListUserServiceAccountsOK A successful response. + +swagger:response listUserServiceAccountsOK +*/ +type ListUserServiceAccountsOK struct { + + /* + In: Body + */ + Payload models.ServiceAccounts `json:"body,omitempty"` +} + +// NewListUserServiceAccountsOK creates ListUserServiceAccountsOK with default headers values +func NewListUserServiceAccountsOK() *ListUserServiceAccountsOK { + + return &ListUserServiceAccountsOK{} +} + +// WithPayload adds the payload to the list user service accounts o k response +func (o *ListUserServiceAccountsOK) WithPayload(payload models.ServiceAccounts) *ListUserServiceAccountsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the list user service accounts o k response +func (o *ListUserServiceAccountsOK) SetPayload(payload models.ServiceAccounts) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ListUserServiceAccountsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if payload == nil { + // return empty array + payload = models.ServiceAccounts{} + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +/*ListUserServiceAccountsDefault Generic error response. + +swagger:response listUserServiceAccountsDefault +*/ +type ListUserServiceAccountsDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewListUserServiceAccountsDefault creates ListUserServiceAccountsDefault with default headers values +func NewListUserServiceAccountsDefault(code int) *ListUserServiceAccountsDefault { + if code <= 0 { + code = 500 + } + + return &ListUserServiceAccountsDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the list user service accounts default response +func (o *ListUserServiceAccountsDefault) WithStatusCode(code int) *ListUserServiceAccountsDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the list user service accounts default response +func (o *ListUserServiceAccountsDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the list user service accounts default response +func (o *ListUserServiceAccountsDefault) WithPayload(payload *models.Error) *ListUserServiceAccountsDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the list user service accounts default response +func (o *ListUserServiceAccountsDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ListUserServiceAccountsDefault) 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 + } + } +} diff --git a/restapi/operations/user_api/list_user_service_accounts_urlbuilder.go b/restapi/operations/user_api/list_user_service_accounts_urlbuilder.go new file mode 100644 index 0000000000..0ca2063729 --- /dev/null +++ b/restapi/operations/user_api/list_user_service_accounts_urlbuilder.go @@ -0,0 +1,131 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 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 . +// + +package user_api + +// 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" + + "github.com/go-openapi/swag" +) + +// ListUserServiceAccountsURL generates an URL for the list user service accounts operation +type ListUserServiceAccountsURL struct { + Limit *int32 + Offset *int32 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *ListUserServiceAccountsURL) WithBasePath(bp string) *ListUserServiceAccountsURL { + 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 *ListUserServiceAccountsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *ListUserServiceAccountsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/service-accounts" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var limitQ string + if o.Limit != nil { + limitQ = swag.FormatInt32(*o.Limit) + } + if limitQ != "" { + qs.Set("limit", limitQ) + } + + var offsetQ string + if o.Offset != nil { + offsetQ = swag.FormatInt32(*o.Offset) + } + if offsetQ != "" { + qs.Set("offset", offsetQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *ListUserServiceAccountsURL) 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 *ListUserServiceAccountsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *ListUserServiceAccountsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on ListUserServiceAccountsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on ListUserServiceAccountsURL") + } + + 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 *ListUserServiceAccountsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/user_service_accounts.go b/restapi/user_service_accounts.go index 3dba3016fa..0122fa4f53 100644 --- a/restapi/user_service_accounts.go +++ b/restapi/user_service_accounts.go @@ -41,6 +41,24 @@ func registerServiceAccountsHandlers(api *operations.McsAPI) { } return user_api.NewCreateServiceAccountCreated().WithPayload(creds) }) + // List Service Accounts for User + api.UserAPIListUserServiceAccountsHandler = user_api.ListUserServiceAccountsHandlerFunc(func(params user_api.ListUserServiceAccountsParams, principal *models.Principal) middleware.Responder { + sessionID := string(*principal) + serviceAccounts, err := getUserServiceAccountsResponse(sessionID) + if err != nil { + return user_api.NewListUserServiceAccountsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())}) + } + return user_api.NewListUserServiceAccountsOK().WithPayload(serviceAccounts) + }) + + // Delete a User's service account + api.UserAPIDeleteServiceAccountHandler = user_api.DeleteServiceAccountHandlerFunc(func(params user_api.DeleteServiceAccountParams, principal *models.Principal) middleware.Responder { + sessionID := string(*principal) + if err := getDeleteServiceAccountResponse(sessionID, params.AccessKey); err != nil { + return user_api.NewDeleteServiceAccountDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())}) + } + return user_api.NewDeleteServiceAccountNoContent() + }) } // createServiceAccount adds a service account to the userClient and assigns a policy to him if defined. @@ -64,7 +82,7 @@ func createServiceAccount(ctx context.Context, userClient MinioAdmin, policy str // getCreateServiceAccountResponse creates a service account with the defined policy for the user that // is requestingit ,it first gets the credentials of the user and creates a client which is going to // make the call to create the Service Account -func getCreateServiceAccountResponse(userSessionID string, serviceAccount *models.ServiceAccount) (*models.ServiceAccountCreds, error) { +func getCreateServiceAccountResponse(userSessionID string, serviceAccount *models.ServiceAccountRequest) (*models.ServiceAccountCreds, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) defer cancel() @@ -84,3 +102,66 @@ func getCreateServiceAccountResponse(userSessionID string, serviceAccount *model } return saCreds, nil } + +// getUserServiceAccount gets list of the user's service accounts +func getUserServiceAccounts(ctx context.Context, userClient MinioAdmin) (models.ServiceAccounts, error) { + listServAccs, err := userClient.listServiceAccounts(ctx) + if err != nil { + return nil, err + } + serviceAccounts := models.ServiceAccounts{} + for _, acc := range listServAccs.Accounts { + serviceAccounts = append(serviceAccounts, acc) + } + return serviceAccounts, nil +} + +// getUserServiceAccountsResponse authenticates the user and calls +// getUserServiceAccounts to list the user's service accounts +func getUserServiceAccountsResponse(userSessionID string) (models.ServiceAccounts, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + defer cancel() + + userAdmin, err := newMAdminClient(userSessionID) + if err != nil { + log.Println("error creating user Client:", err) + return nil, err + } + // create a MinIO user Admin Client interface implementation + // defining the client to be used + userAdminClient := adminClient{client: userAdmin} + + serviceAccounts, err := getUserServiceAccounts(ctx, userAdminClient) + if err != nil { + log.Println("error listing user's service account:", err) + return nil, err + } + return serviceAccounts, nil + +} + +// deleteServiceAccount calls delete service account api +func deleteServiceAccount(ctx context.Context, userClient MinioAdmin, accessKey string) error { + return userClient.deleteServiceAccount(ctx, accessKey) +} + +// getDeleteServiceAccountResponse authenticates the user and calls deleteServiceAccount +func getDeleteServiceAccountResponse(userSessionID, accessKey string) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + defer cancel() + + userAdmin, err := newMAdminClient(userSessionID) + if err != nil { + log.Println("error creating user Client:", err) + return err + } + // create a MinIO user Admin Client interface implementation + // defining the client to be used + userAdminClient := adminClient{client: userAdmin} + + if err := deleteServiceAccount(ctx, userAdminClient, accessKey); err != nil { + log.Println("error deleting user's service account:", err) + return err + } + return nil +} diff --git a/restapi/user_service_accounts_test.go b/restapi/user_service_accounts_test.go index 6755b0c2ed..23bd62193f 100644 --- a/restapi/user_service_accounts_test.go +++ b/restapi/user_service_accounts_test.go @@ -25,17 +25,30 @@ import ( "github.com/minio/minio/pkg/auth" iampolicy "github.com/minio/minio/pkg/iam/policy" + "github.com/minio/minio/pkg/madmin" "github.com/stretchr/testify/assert" ) // assigning mock at runtime instead of compile time var minioAddServiceAccountMock func(ctx context.Context, policy *iampolicy.Policy) (auth.Credentials, error) +var minioListServiceAccountsMock func(ctx context.Context) (madmin.ListServiceAccountsResp, error) +var minioDeleteServiceAccountMock func(ctx context.Context, serviceAccount string) error -// mock function of listUsers() +// mock function of AddServiceAccount() func (ac adminClientMock) addServiceAccount(ctx context.Context, policy *iampolicy.Policy) (auth.Credentials, error) { return minioAddServiceAccountMock(ctx, policy) } +// mock function of ListServiceAccounts() +func (ac adminClientMock) listServiceAccounts(ctx context.Context) (madmin.ListServiceAccountsResp, error) { + return minioListServiceAccountsMock(ctx) +} + +// mock function of DeleteServiceAccount() +func (ac adminClientMock) deleteServiceAccount(ctx context.Context, serviceAccount string) error { + return minioDeleteServiceAccountMock(ctx, serviceAccount) +} + func TestAddServiceAccount(t *testing.T) { assert := assert.New(t) // mock minIO client @@ -84,3 +97,61 @@ func TestAddServiceAccount(t *testing.T) { assert.Equal("error", err.Error()) } } + +func TestListServiceAccounts(t *testing.T) { + assert := assert.New(t) + // mock minIO client + client := adminClientMock{} + function := "getUserServiceAccounts()" + + // Test-1: getUserServiceAccounts list serviceaccounts for a user + ctx := context.Background() + mockResponse := madmin.ListServiceAccountsResp{ + Accounts: []string{"accesskey1", "accesskey2"}, + } + minioListServiceAccountsMock = func(ctx context.Context) (madmin.ListServiceAccountsResp, error) { + return mockResponse, nil + } + serviceAccounts, err := getUserServiceAccounts(ctx, client) + if err != nil { + t.Errorf("Failed on %s:, error occurred: %s", function, err.Error()) + } + for i, sa := range serviceAccounts { + assert.Equal(mockResponse.Accounts[i], sa) + } + + // Test-2: getUserServiceAccounts returns an error, handle it properly + minioListServiceAccountsMock = func(ctx context.Context) (madmin.ListServiceAccountsResp, error) { + return madmin.ListServiceAccountsResp{}, errors.New("error") + } + _, err = getUserServiceAccounts(ctx, client) + if assert.Error(err) { + assert.Equal("error", err.Error()) + } +} + +func TestDeleteServiceAccount(t *testing.T) { + assert := assert.New(t) + // mock minIO client + client := adminClientMock{} + function := "deleteServiceAccount()" + ctx := context.Background() + + // Test-1: deleteServiceAccount receive a service account to delete + testServiceAccount := "accesskeytest" + minioDeleteServiceAccountMock = func(ctx context.Context, serviceAccount string) error { + return nil + } + if err := deleteServiceAccount(ctx, client, testServiceAccount); err != nil { + t.Errorf("Failed on %s:, error occurred: %s", function, err.Error()) + } + + // Test-2: if an invalid policy is assigned to the service account, this will raise an error + minioDeleteServiceAccountMock = func(ctx context.Context, serviceAccount string) error { + return errors.New("error") + } + + if err := deleteServiceAccount(ctx, client, testServiceAccount); assert.Error(err) { + assert.Equal("error", err.Error()) + } +} diff --git a/swagger.yml b/swagger.yml index 4d28a79249..0081d27231 100644 --- a/swagger.yml +++ b/swagger.yml @@ -309,6 +309,31 @@ paths: - UserAPI /service-accounts: + get: + summary: List User's Service Accounts + operationId: ListUserServiceAccounts + parameters: + - name: offset + in: query + required: false + type: integer + format: int32 + - name: limit + in: query + required: false + type: integer + format: int32 + responses: + 200: + description: A successful response. + schema: + $ref: "#/definitions/serviceAccounts" + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - UserAPI post: summary: Create Service Account operationId: CreateServiceAccount @@ -317,7 +342,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/serviceAccount' + $ref: '#/definitions/serviceAccountRequest' responses: 201: description: A successful response. @@ -329,6 +354,25 @@ paths: $ref: "#/definitions/error" tags: - UserAPI + + /service-accounts/{access_key}: + delete: + summary: Delete Service Account + operationId: DeleteServiceAccount + parameters: + - name: access_key + in: path + required: true + type: string + responses: + 204: + description: A successful response. + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - UserAPI /users: get: @@ -1367,7 +1411,11 @@ definitions: type: array items: type: string - serviceAccount: + serviceAccounts: + type: array + items: + type: string + serviceAccountRequest: type: object properties: policy: