STS integration, JWT auth and Stateless MCS (#70)
This commit changes the authentication mechanism between mcs and minio to an sts
(security token service) schema using the user provided credentials, previously
mcs was using master credentials. With that said in order for you to
login to MCS as an admin your user must exists first on minio and have enough
privileges to do administrative operations.
```
./mc admin user add myminio alevsk alevsk12345
```
```
cat admin.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"admin:*",
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}
./mc admin policy add myminio admin admin.json
```
```
./mc admin policy set myminio admin user=alevsk
```
This commit is contained in:
1
Makefile
1
Makefile
@@ -22,6 +22,7 @@ assets:
|
||||
|
||||
test:
|
||||
@(go test -race -v github.com/minio/mcs/restapi/...)
|
||||
@(go test -race -v github.com/minio/mcs/pkg/auth)
|
||||
|
||||
coverage:
|
||||
@(go test -v -coverprofile=coverage.out github.com/minio/mcs/restapi/... && go tool cover -html=coverage.out && open coverage.html)
|
||||
|
||||
@@ -54,6 +54,14 @@ $ mc admin policy set myminio mcsAdmin user=mcs
|
||||
To run the server:
|
||||
|
||||
```
|
||||
export MCS_HMAC_JWT_SECRET=YOURJWTSIGNINGSECRET
|
||||
|
||||
#required to encrypt jwet payload
|
||||
export MCS_PBKDF_PASSPHRASE=SECRET
|
||||
|
||||
#required to encrypt jwet payload
|
||||
export MCS_PBKDF_SALT=SECRET
|
||||
|
||||
export MCS_ACCESS_KEY=mcs
|
||||
export MCS_SECRET_KEY=YOURMCSSECRET
|
||||
export MCS_MINIO_SERVER=http://localhost:9000
|
||||
|
||||
4
go.mod
4
go.mod
@@ -3,6 +3,7 @@ module github.com/minio/mcs
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0
|
||||
github.com/go-openapi/errors v0.19.4
|
||||
github.com/go-openapi/loads v0.19.5
|
||||
@@ -12,11 +13,14 @@ require (
|
||||
github.com/go-openapi/swag v0.19.8
|
||||
github.com/go-openapi/validate v0.19.7
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
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-20200415191640-bde0f444dbab
|
||||
github.com/minio/minio-go/v6 v6.0.53
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/unrolled/secure v1.0.7
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
|
||||
)
|
||||
|
||||
2
go.sum
2
go.sum
@@ -390,6 +390,7 @@ github.com/minio/minio v0.0.0-20200415191640-bde0f444dbab h1:9hlqghJl3e3HorXa6AD
|
||||
github.com/minio/minio v0.0.0-20200415191640-bde0f444dbab/go.mod h1:v8oQPMMaTkjDwp5cOz1WCElA4Ik+X+0y4On+VMk0fis=
|
||||
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/parquet-go v0.0.0-20200414234858-838cfa8aae61 h1:pUSI/WKPdd77gcuoJkSzhJ4wdS8OMDOsOu99MtpXEQA=
|
||||
github.com/minio/parquet-go v0.0.0-20200414234858-838cfa8aae61/go.mod h1:4trzEJ7N1nBTd5Tt7OCZT5SEin+WiAXpdJ/WgPkESA8=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
@@ -494,6 +495,7 @@ github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/secure-io/sio-go v0.3.0 h1:QKGb6rGJeiExac9wSWxnWPYo8O8OFN7lxXQvHshX6vo=
|
||||
github.com/secure-io/sio-go v0.3.0/go.mod h1:D3KmXgKETffyYxBdFRN+Hpd2WzhzqS0EQwT3XWsAcBU=
|
||||
|
||||
180
pkg/auth/jwt.go
Normal file
180
pkg/auth/jwt.go
Normal file
@@ -0,0 +1,180 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
jwtgo "github.com/dgrijalva/jwt-go"
|
||||
xjwt "github.com/minio/mcs/pkg/auth/jwt"
|
||||
"github.com/minio/minio-go/v6/pkg/credentials"
|
||||
"github.com/minio/minio/cmd"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
var (
|
||||
errAuthentication = errors.New("Authentication failed, check your access credentials")
|
||||
errNoAuthToken = errors.New("JWT token missing")
|
||||
errReadingToken = errors.New("JWT internal data is malformed")
|
||||
errClaimsFormat = errors.New("encrypted jwt claims not in the right format")
|
||||
)
|
||||
|
||||
// derivedKey is the key used to encrypt the JWT claims, its derived using pbkdf on MCS_PBKDF_PASSPHRASE with MCS_PBKDF_SALT
|
||||
var derivedKey = pbkdf2.Key([]byte(xjwt.GetPBKDFPassphrase()), []byte(xjwt.GetPBKDFSalt()), 4096, 32, sha1.New)
|
||||
|
||||
// IsJWTValid returns true or false depending if the provided jwt is valid or not
|
||||
func IsJWTValid(token string) bool {
|
||||
_, err := JWTAuthenticate(token)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type DecryptedClaims struct {
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
SessionToken string
|
||||
}
|
||||
|
||||
// JWTAuthenticate takes a jwt, decode it, extract claims and validate the signature
|
||||
// if the jwt claims.Data is valid we proceed to decrypt the information inside
|
||||
//
|
||||
// returns claims after validation in the following format:
|
||||
//
|
||||
// type DecryptedClaims struct {
|
||||
// AccessKeyID
|
||||
// SecretAccessKey
|
||||
// SessionToken
|
||||
// }
|
||||
func JWTAuthenticate(token string) (*DecryptedClaims, error) {
|
||||
if token == "" {
|
||||
return nil, errNoAuthToken
|
||||
}
|
||||
// initialize claims object
|
||||
claims := xjwt.NewMapClaims()
|
||||
// populate the claims object
|
||||
if err := xjwt.ParseWithClaims(token, claims); err != nil {
|
||||
return nil, errAuthentication
|
||||
}
|
||||
// decrypt the claims.Data field
|
||||
claimTokens, err := decryptClaims(claims.Data)
|
||||
if err != nil {
|
||||
// we print decryption token error information for debugging purposes
|
||||
log.Println(err)
|
||||
// we return a generic error that doesn't give any information to attackers
|
||||
return nil, errReadingToken
|
||||
}
|
||||
// claimsTokens contains the decrypted STS claims
|
||||
return claimTokens, nil
|
||||
}
|
||||
|
||||
// NewJWTWithClaimsForClient generates a new jwt with claims based on the provided STS credentials, first
|
||||
// encrypts the claims and the sign them
|
||||
func NewJWTWithClaimsForClient(credentials *credentials.Value, audience string) (string, error) {
|
||||
if credentials != nil {
|
||||
encryptedClaims, err := encryptClaims(credentials.AccessKeyID, credentials.SecretAccessKey, credentials.SessionToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
claims := xjwt.NewStandardClaims()
|
||||
claims.SetExpiry(cmd.UTCNow().Add(xjwt.GetMcsSTSAndJWTDurationTime()))
|
||||
claims.SetSubject(uuid.NewV4().String())
|
||||
claims.SetData(encryptedClaims)
|
||||
claims.SetAudience(audience)
|
||||
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
|
||||
return jwt.SignedString([]byte(xjwt.GetHmacJWTSecret()))
|
||||
}
|
||||
return "", errors.New("provided credentials are empty")
|
||||
}
|
||||
|
||||
// encryptClaims() receives the 3 STS claims, concatenate them and encrypt them using AES-GCM
|
||||
// returns a base64 encoded ciphertext
|
||||
func encryptClaims(accessKeyID, secretAccessKey, sessionToken string) (string, error) {
|
||||
payload := []byte(fmt.Sprintf("%s:%s:%s", accessKeyID, secretAccessKey, sessionToken))
|
||||
ciphertext, err := encrypt(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
// decryptClaims() receives base64 encoded ciphertext, decode it, decrypt it (AES-GCM) and produces a *DecryptedClaims object
|
||||
func decryptClaims(ciphertext string) (*DecryptedClaims, error) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(ciphertext)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, errClaimsFormat
|
||||
}
|
||||
plaintext, err := decrypt(decoded)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, errClaimsFormat
|
||||
}
|
||||
s := strings.Split(string(plaintext), ":")
|
||||
// Validate that the decrypted string has the right format "accessKeyID:secretAccessKey:sessionToken"
|
||||
if len(s) != 3 {
|
||||
return nil, errClaimsFormat
|
||||
}
|
||||
accessKeyID, secretAccessKey, sessionToken := s[0], s[1], s[2]
|
||||
return &DecryptedClaims{
|
||||
AccessKeyID: accessKeyID,
|
||||
SecretAccessKey: secretAccessKey,
|
||||
SessionToken: sessionToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Encrypt a blob of data using AEAD (AES-GCM) with a pbkdf2 derived key
|
||||
func encrypt(plaintext []byte) ([]byte, error) {
|
||||
block, _ := aes.NewCipher(derivedKey)
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cipherText := gcm.Seal(nonce, nonce, plaintext, nil)
|
||||
return cipherText, nil
|
||||
}
|
||||
|
||||
// Decrypts a blob of data using AEAD (AES-GCM) with a pbkdf2 derived key
|
||||
func decrypt(data []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(derivedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nonceSize := gcm.NonceSize()
|
||||
nonce, cipherText := data[:nonceSize], data[nonceSize:]
|
||||
plaintext, err := gcm.Open(nil, nonce, cipherText, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
@@ -14,58 +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 sessions
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
mcCmd "github.com/minio/mc/cmd"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
)
|
||||
|
||||
type Singleton struct {
|
||||
sessions map[string]*mcCmd.Config
|
||||
}
|
||||
|
||||
var instance *Singleton
|
||||
var once sync.Once
|
||||
|
||||
// Returns a Singleton instance that keeps the sessions
|
||||
func GetInstance() *Singleton {
|
||||
once.Do(func() {
|
||||
//build sessions hash
|
||||
sessions := make(map[string]*mcCmd.Config)
|
||||
|
||||
instance = &Singleton{
|
||||
sessions: sessions,
|
||||
}
|
||||
})
|
||||
return instance
|
||||
}
|
||||
|
||||
// The delete built-in function deletes the element with the specified key (m[key]) from the map.
|
||||
// If m is nil or there is no such element, delete is a no-op. https://golang.org/pkg/builtin/#delete
|
||||
func (s *Singleton) DeleteSession(sessionID string) {
|
||||
delete(s.sessions, sessionID)
|
||||
}
|
||||
|
||||
func (s *Singleton) NewSession(cfg *mcCmd.Config) string {
|
||||
// genereate random session id
|
||||
sessionID := RandomCharString(64)
|
||||
// store the cfg under that session id
|
||||
s.sessions[sessionID] = cfg
|
||||
return sessionID
|
||||
}
|
||||
|
||||
func (s *Singleton) ValidSession(sessionID string) bool {
|
||||
if _, ok := s.sessions[sessionID]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Do not use:
|
||||
// https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
|
||||
// It relies on math/rand and therefore not on a cryptographically secure RNG => It must not be used
|
||||
@@ -93,3 +53,42 @@ func RandomCharString(n int) string {
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// defaultHmacJWTPassphrase will be used by default if application is not configured with a custom MCS_HMAC_JWT_SECRET secret
|
||||
var defaultHmacJWTPassphrase = RandomCharString(64)
|
||||
|
||||
// GetHmacJWTSecret returns the 64 bytes secret used for signing the generated JWT for the application
|
||||
func GetHmacJWTSecret() string {
|
||||
return env.Get(McsHmacJWTSecret, defaultHmacJWTPassphrase)
|
||||
}
|
||||
|
||||
// McsSTSAndJWTDurationSeconds returns the default session duration for the STS requested tokens and the generated JWTs.
|
||||
// Ideally both values should match so jwt and Minio sts sessions expires at the same time.
|
||||
func GetMcsSTSAndJWTDurationInSeconds() int {
|
||||
duration, err := strconv.Atoi(env.Get(McsSTSAndJWTDurationSeconds, "3600"))
|
||||
if err != nil {
|
||||
duration = 3600
|
||||
}
|
||||
return duration
|
||||
}
|
||||
|
||||
// GetMcsSTSAndJWTDurationTime returns GetMcsSTSAndJWTDurationInSeconds in duration format
|
||||
func GetMcsSTSAndJWTDurationTime() time.Duration {
|
||||
duration := GetMcsSTSAndJWTDurationInSeconds()
|
||||
return time.Duration(duration) * time.Second
|
||||
}
|
||||
|
||||
// defaultPBKDFPassphrase
|
||||
var defaultPBKDFPassphrase = RandomCharString(64)
|
||||
|
||||
// GetPBKDFPassphrase returns passphrase for the pbkdf2 function used to encrypt JWT payload
|
||||
func GetPBKDFPassphrase() string {
|
||||
return env.Get(McsPBKDFPassphrase, defaultPBKDFPassphrase)
|
||||
}
|
||||
|
||||
var defaultPBKDFSalt = RandomCharString(64)
|
||||
|
||||
// GetPBKDFSalt returns salt for the pbkdf2 function used to encrypt JWT payload
|
||||
func GetPBKDFSalt() string {
|
||||
return env.Get(McsPBKDFSalt, defaultPBKDFSalt)
|
||||
}
|
||||
24
pkg/auth/jwt/const.go
Normal file
24
pkg/auth/jwt/const.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package jwt
|
||||
|
||||
const (
|
||||
McsHmacJWTSecret = "MCS_HMAC_JWT_SECRET"
|
||||
McsSTSAndJWTDurationSeconds = "MCS_STS_AND_JWT_DURATION_SECONDS"
|
||||
McsPBKDFPassphrase = "MCS_PBKDF_PASSPHRASE"
|
||||
McsPBKDFSalt = "MCS_PBKDF_SALT"
|
||||
)
|
||||
281
pkg/auth/jwt/parser.go
Normal file
281
pkg/auth/jwt/parser.go
Normal file
@@ -0,0 +1,281 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package jwt
|
||||
|
||||
// This file is a re-implementation of the original code here with some
|
||||
// additional allocation tweaks reproduced using GODEBUG=allocfreetrace=1
|
||||
// original file https://github.com/dgrijalva/jwt-go/blob/master/parser.go
|
||||
// borrowed under MIT License https://github.com/dgrijalva/jwt-go/blob/master/LICENSE
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
jwtgo "github.com/dgrijalva/jwt-go"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
const (
|
||||
claimData = "data"
|
||||
claimSub = "sub"
|
||||
)
|
||||
|
||||
// SigningMethodHMAC - Implements the HMAC-SHA family of signing methods signing methods
|
||||
// Expects key type of []byte for both signing and validation
|
||||
type SigningMethodHMAC struct {
|
||||
Name string
|
||||
Hash crypto.Hash
|
||||
}
|
||||
|
||||
// Specific instances for HS256, HS384, HS512
|
||||
var (
|
||||
SigningMethodHS256 *SigningMethodHMAC
|
||||
SigningMethodHS384 *SigningMethodHMAC
|
||||
SigningMethodHS512 *SigningMethodHMAC
|
||||
)
|
||||
|
||||
var (
|
||||
base64BufPool sync.Pool
|
||||
hmacSigners []*SigningMethodHMAC
|
||||
)
|
||||
|
||||
func init() {
|
||||
base64BufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
buf := make([]byte, 8192)
|
||||
return &buf
|
||||
},
|
||||
}
|
||||
|
||||
hmacSigners = []*SigningMethodHMAC{
|
||||
{"HS256", crypto.SHA256},
|
||||
{"HS384", crypto.SHA384},
|
||||
{"HS512", crypto.SHA512},
|
||||
}
|
||||
}
|
||||
|
||||
// StandardClaims are basically standard claims with "Data"
|
||||
type StandardClaims struct {
|
||||
Data string `json:"data,omitempty"`
|
||||
jwtgo.StandardClaims
|
||||
}
|
||||
|
||||
// MapClaims - implements custom unmarshaller
|
||||
type MapClaims struct {
|
||||
Data string `json:"data,omitempty"`
|
||||
Subject string `json:"sub,omitempty"`
|
||||
jwtgo.MapClaims
|
||||
}
|
||||
|
||||
// NewStandardClaims - initializes standard claims
|
||||
func NewStandardClaims() *StandardClaims {
|
||||
return &StandardClaims{}
|
||||
}
|
||||
|
||||
// SetIssuer sets issuer for these claims
|
||||
func (c *StandardClaims) SetIssuer(issuer string) {
|
||||
c.Issuer = issuer
|
||||
}
|
||||
|
||||
// SetAudience sets audience for these claims
|
||||
func (c *StandardClaims) SetAudience(aud string) {
|
||||
c.Audience = aud
|
||||
}
|
||||
|
||||
// SetExpiry sets expiry in unix epoch secs
|
||||
func (c *StandardClaims) SetExpiry(t time.Time) {
|
||||
c.ExpiresAt = t.Unix()
|
||||
}
|
||||
|
||||
// SetSubject sets unique identifier for the jwt
|
||||
func (c *StandardClaims) SetSubject(subject string) {
|
||||
c.Subject = subject
|
||||
}
|
||||
|
||||
// SetData sets the "Data" custom field.
|
||||
func (c *StandardClaims) SetData(data string) {
|
||||
c.Data = data
|
||||
}
|
||||
|
||||
// Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible
|
||||
// claims interface, additionally validates "Data" field.
|
||||
func (c *StandardClaims) Valid() error {
|
||||
if err := c.StandardClaims.Valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Data == "" || c.Subject == "" {
|
||||
return jwtgo.NewValidationError("data/sub",
|
||||
jwtgo.ValidationErrorClaimsInvalid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewMapClaims - Initializes a new map claims
|
||||
func NewMapClaims() *MapClaims {
|
||||
return &MapClaims{MapClaims: jwtgo.MapClaims{}}
|
||||
}
|
||||
|
||||
// Lookup returns the value and if the key is found.
|
||||
func (c *MapClaims) Lookup(key string) (value string, ok bool) {
|
||||
var vinterface interface{}
|
||||
vinterface, ok = c.MapClaims[key]
|
||||
if ok {
|
||||
value, ok = vinterface.(string)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetExpiry sets expiry in unix epoch secs
|
||||
func (c *MapClaims) SetExpiry(t time.Time) {
|
||||
c.MapClaims["exp"] = t.Unix()
|
||||
}
|
||||
|
||||
// SetData sets the "Data" custom field.
|
||||
func (c *MapClaims) SetData(data string) {
|
||||
c.MapClaims[claimData] = data
|
||||
}
|
||||
|
||||
// Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible
|
||||
// claims interface, additionally validates "Data" field.
|
||||
func (c *MapClaims) Valid() error {
|
||||
if err := c.MapClaims.Valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Data == "" || c.Subject == "" {
|
||||
return jwtgo.NewValidationError("data/subject",
|
||||
jwtgo.ValidationErrorClaimsInvalid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map returns underlying low-level map claims.
|
||||
func (c *MapClaims) Map() map[string]interface{} {
|
||||
return c.MapClaims
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the MapClaims struct
|
||||
func (c *MapClaims) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(c.MapClaims)
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7519#page-11
|
||||
type jwtHeader struct {
|
||||
Algorithm string `json:"alg"`
|
||||
Type string `json:"typ"`
|
||||
}
|
||||
|
||||
// ParseWithClaims - parse the token string, valid methods.
|
||||
func ParseWithClaims(tokenStr string, claims *MapClaims) error {
|
||||
bufp := base64BufPool.Get().(*[]byte)
|
||||
defer base64BufPool.Put(bufp)
|
||||
|
||||
signer, err := parseUnverifiedMapClaims(tokenStr, claims, *bufp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i := strings.LastIndex(tokenStr, ".")
|
||||
if i < 0 {
|
||||
return jwtgo.ErrSignatureInvalid
|
||||
}
|
||||
|
||||
n, err := base64Decode(tokenStr[i+1:], *bufp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
|
||||
claims.Data, ok = claims.Lookup(claimData)
|
||||
if !ok {
|
||||
return jwtgo.NewValidationError("data missing",
|
||||
jwtgo.ValidationErrorClaimsInvalid)
|
||||
}
|
||||
|
||||
claims.Subject, ok = claims.Lookup(claimSub)
|
||||
if !ok {
|
||||
return jwtgo.NewValidationError("sub missing",
|
||||
jwtgo.ValidationErrorClaimsInvalid)
|
||||
}
|
||||
|
||||
hasher := hmac.New(signer.Hash.New, []byte(GetHmacJWTSecret()))
|
||||
hasher.Write([]byte(tokenStr[:i]))
|
||||
if !hmac.Equal((*bufp)[:n], hasher.Sum(nil)) {
|
||||
return jwtgo.ErrSignatureInvalid
|
||||
}
|
||||
|
||||
// Signature is valid, lets validate the claims for
|
||||
// other fields such as expiry etc.
|
||||
return claims.Valid()
|
||||
}
|
||||
|
||||
// base64Decode returns the bytes represented by the base64 string s.
|
||||
func base64Decode(s string, buf []byte) (int, error) {
|
||||
return base64.RawURLEncoding.Decode(buf, []byte(s))
|
||||
}
|
||||
|
||||
// ParseUnverifiedMapClaims - WARNING: Don't use this method unless you know what you're doing
|
||||
//
|
||||
// This method parses the token but doesn't validate the signature. It's only
|
||||
// ever useful in cases where you know the signature is valid (because it has
|
||||
// been checked previously in the stack) and you want to extract values from
|
||||
// it.
|
||||
func parseUnverifiedMapClaims(tokenString string, claims *MapClaims, buf []byte) (*SigningMethodHMAC, error) {
|
||||
if strings.Count(tokenString, ".") != 2 {
|
||||
return nil, jwtgo.ErrSignatureInvalid
|
||||
}
|
||||
|
||||
i := strings.Index(tokenString, ".")
|
||||
j := strings.LastIndex(tokenString, ".")
|
||||
|
||||
n, err := base64Decode(tokenString[:i], buf)
|
||||
if err != nil {
|
||||
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
var header = jwtHeader{}
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
if err = json.Unmarshal(buf[:n], &header); err != nil {
|
||||
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
n, err = base64Decode(tokenString[i+1:j], buf)
|
||||
if err != nil {
|
||||
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(buf[:n], &claims.MapClaims); err != nil {
|
||||
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
for _, signer := range hmacSigners {
|
||||
if header.Algorithm == signer.Name {
|
||||
return signer, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, jwtgo.NewValidationError(fmt.Sprintf("signing method (%s) is unavailable.", header.Algorithm),
|
||||
jwtgo.ValidationErrorUnverifiable)
|
||||
}
|
||||
80
pkg/auth/jwt_test.go
Normal file
80
pkg/auth/jwt_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/credentials"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var audience = ""
|
||||
var creds = &credentials.Value{
|
||||
AccessKeyID: "fakeAccessKeyID",
|
||||
SecretAccessKey: "fakeSecretAccessKey",
|
||||
SessionToken: "fakeSessionToken",
|
||||
SignerType: 0,
|
||||
}
|
||||
var goodToken = ""
|
||||
var badToken = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiRDMwYWE0ekQ1bWtFaFRyWm5yOWM3NWh0Yko0MkROOWNDZVQ5RHVHUkg1U25SR3RyTXZNOXBMdnlFSVJAAAE5eWxxekhYMXllck8xUXpzMlZzRVFKeUF2ZmpOaDkrTVdoUURWZ2FhK2R5emxzSjNpK0k1dUdoeW5DNWswUW83WEY0UWszY0RtUTdUQUVROVFEbWRKdjBkdVB5L25hQk5vM3dIdlRDZHFNRDJZN3kycktJbmVUbUlFNmVveW9EWmprcW5tckVoYmMrTlhTRU81WjZqa1kwZ1E2eXZLaWhUZGxBRS9zS1lBNlc4Q1R1cm1MU0E0b0dIcGtldFZWU0VXMHEzNU9TU1VaczRXNkxHdGMxSTFWVFZLWUo3ZTlHR2REQ3hMWGtiZHQwcjl0RDNMWUhWRndra0dSZit5ZHBzS1Y3L1Jtbkp3SHNqNVVGV0w5WGVHUkZVUjJQclJTN2plVzFXeGZuYitVeXoxNVpOMzZsZ01GNnBlWFd1LzJGcEtrb2Z2QzNpY2x5Rmp0SE45ZkxYTVpVSFhnV2lsQWVSa3oiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJleHAiOjE1ODc1MTY1NzEsInN1YiI6ImZmYmY4YzljLTJlMjYtNGMwYS1iMmI0LTYyMmVhM2I1YjZhYiJ9.P392RUwzsrBeJOO3fS1xMZcF-lWiDvWZ5hM7LZOyFMmoG5QLccDU5eAPSm8obzPoznX1b7eCFLeEmKK-vKgjiQ"
|
||||
|
||||
func TestNewJWTWithClaimsForClient(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : NewJWTWithClaimsForClient() is generated correctly without errors
|
||||
function := "NewJWTWithClaimsForClient()"
|
||||
jwt, err := NewJWTWithClaimsForClient(creds, audience)
|
||||
if err != nil || jwt == "" {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err)
|
||||
}
|
||||
// saving jwt for future tests
|
||||
goodToken = jwt
|
||||
// Test-2 : NewJWTWithClaimsForClient() throws error because of empty credentials
|
||||
if _, err = NewJWTWithClaimsForClient(nil, audience); err != nil {
|
||||
funcAssert.Equal("provided credentials are empty", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWTAuthenticate(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : JWTAuthenticate() should correctly return the claims
|
||||
function := "JWTAuthenticate()"
|
||||
claims, err := JWTAuthenticate(goodToken)
|
||||
if err != nil || claims == nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err)
|
||||
} else {
|
||||
funcAssert.Equal(claims.AccessKeyID, creds.AccessKeyID)
|
||||
funcAssert.Equal(claims.SecretAccessKey, creds.SecretAccessKey)
|
||||
funcAssert.Equal(claims.SessionToken, creds.SessionToken)
|
||||
}
|
||||
// Test-2 : JWTAuthenticate() return an error because of a tampered jwt
|
||||
if _, err := JWTAuthenticate(badToken); err != nil {
|
||||
funcAssert.Equal("Authentication failed, check your access credentials", err.Error())
|
||||
}
|
||||
// Test-3 : JWTAuthenticate() return an error because of an empty jwt
|
||||
if _, err := JWTAuthenticate(""); err != nil {
|
||||
funcAssert.Equal("JWT token missing", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJWTValid(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : JWTAuthenticate() provided token is valid
|
||||
funcAssert.Equal(true, IsJWTValid(goodToken))
|
||||
// Test-2 : JWTAuthenticate() provided token is invalid
|
||||
funcAssert.Equal(false, IsJWTValid(badToken))
|
||||
}
|
||||
@@ -31,7 +31,8 @@ import (
|
||||
func registerAdminArnsHandlers(api *operations.McsAPI) {
|
||||
// return a list of arns
|
||||
api.AdminAPIArnListHandler = admin_api.ArnListHandlerFunc(func(params admin_api.ArnListParams, principal *models.Principal) middleware.Responder {
|
||||
arnsResp, err := getArnsResponse()
|
||||
sessionID := string(*principal)
|
||||
arnsResp, err := getArnsResponse(sessionID)
|
||||
if err != nil {
|
||||
return admin_api.NewArnListDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -53,8 +54,8 @@ func getArns(ctx context.Context, client MinioAdmin) (*models.ArnsResponse, erro
|
||||
}
|
||||
|
||||
// getArnsResponse returns a list of active arns in the instance
|
||||
func getArnsResponse() (*models.ArnsResponse, error) {
|
||||
mAdmin, err := newMAdminClient()
|
||||
func getArnsResponse(sessionID string) (*models.ArnsResponse, error) {
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
|
||||
@@ -33,7 +33,8 @@ import (
|
||||
func registerConfigHandlers(api *operations.McsAPI) {
|
||||
// List Configurations
|
||||
api.AdminAPIListConfigHandler = admin_api.ListConfigHandlerFunc(func(params admin_api.ListConfigParams, principal *models.Principal) middleware.Responder {
|
||||
configListResp, err := getListConfigResponse()
|
||||
sessionID := string(*principal)
|
||||
configListResp, err := getListConfigResponse(sessionID)
|
||||
if err != nil {
|
||||
return admin_api.NewListConfigDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -41,7 +42,8 @@ func registerConfigHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// Configuration Info
|
||||
api.AdminAPIConfigInfoHandler = admin_api.ConfigInfoHandlerFunc(func(params admin_api.ConfigInfoParams, principal *models.Principal) middleware.Responder {
|
||||
config, err := getConfigResponse(params)
|
||||
sessionID := string(*principal)
|
||||
config, err := getConfigResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return admin_api.NewConfigInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -49,7 +51,8 @@ func registerConfigHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// Set Configuration
|
||||
api.AdminAPISetConfigHandler = admin_api.SetConfigHandlerFunc(func(params admin_api.SetConfigParams, principal *models.Principal) middleware.Responder {
|
||||
if err := setConfigResponse(params.Name, params.Body); err != nil {
|
||||
sessionID := string(*principal)
|
||||
if err := setConfigResponse(sessionID, params.Name, params.Body); err != nil {
|
||||
return admin_api.NewSetConfigDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
return admin_api.NewSetConfigNoContent()
|
||||
@@ -75,8 +78,8 @@ func listConfig(client MinioAdmin) ([]*models.ConfigDescription, error) {
|
||||
}
|
||||
|
||||
// getListConfigResponse performs listConfig() and serializes it to the handler's output
|
||||
func getListConfigResponse() (*models.ListConfigResponse, error) {
|
||||
mAdmin, err := newMAdminClient()
|
||||
func getListConfigResponse(sessionID string) (*models.ListConfigResponse, error) {
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -122,8 +125,8 @@ func getConfig(client MinioAdmin, name string) ([]*models.ConfigurationKV, error
|
||||
}
|
||||
|
||||
// getConfigResponse performs getConfig() and serializes it to the handler's output
|
||||
func getConfigResponse(params admin_api.ConfigInfoParams) (*models.Configuration, error) {
|
||||
mAdmin, err := newMAdminClient()
|
||||
func getConfigResponse(sessionID string, params admin_api.ConfigInfoParams) (*models.Configuration, error) {
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -175,8 +178,8 @@ func buildConfig(configName *string, kvs []*models.ConfigurationKV) *string {
|
||||
}
|
||||
|
||||
// setConfigResponse implements setConfig() to be used by handler
|
||||
func setConfigResponse(name string, configRequest *models.SetConfigRequest) error {
|
||||
mAdmin, err := newMAdminClient()
|
||||
func setConfigResponse(sessionID string, name string, configRequest *models.SetConfigRequest) error {
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return err
|
||||
|
||||
@@ -34,7 +34,8 @@ import (
|
||||
func registerGroupsHandlers(api *operations.McsAPI) {
|
||||
// List Groups
|
||||
api.AdminAPIListGroupsHandler = admin_api.ListGroupsHandlerFunc(func(params admin_api.ListGroupsParams, principal *models.Principal) middleware.Responder {
|
||||
listGroupsResponse, err := getListGroupsResponse()
|
||||
sessionID := string(*principal)
|
||||
listGroupsResponse, err := getListGroupsResponse(sessionID)
|
||||
if err != nil {
|
||||
return admin_api.NewListGroupsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -42,7 +43,8 @@ func registerGroupsHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// Group Info
|
||||
api.AdminAPIGroupInfoHandler = admin_api.GroupInfoHandlerFunc(func(params admin_api.GroupInfoParams, principal *models.Principal) middleware.Responder {
|
||||
groupInfo, err := getGroupInfoResponse(params)
|
||||
sessionID := string(*principal)
|
||||
groupInfo, err := getGroupInfoResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return admin_api.NewGroupInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -50,21 +52,24 @@ func registerGroupsHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// Add Group
|
||||
api.AdminAPIAddGroupHandler = admin_api.AddGroupHandlerFunc(func(params admin_api.AddGroupParams, principal *models.Principal) middleware.Responder {
|
||||
if err := getAddGroupResponse(params.Body); err != nil {
|
||||
sessionID := string(*principal)
|
||||
if err := getAddGroupResponse(sessionID, params.Body); err != nil {
|
||||
return admin_api.NewAddGroupDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
return admin_api.NewAddGroupCreated()
|
||||
})
|
||||
// Remove Group
|
||||
api.AdminAPIRemoveGroupHandler = admin_api.RemoveGroupHandlerFunc(func(params admin_api.RemoveGroupParams, principal *models.Principal) middleware.Responder {
|
||||
if err := getRemoveGroupResponse(params); err != nil {
|
||||
sessionID := string(*principal)
|
||||
if err := getRemoveGroupResponse(sessionID, params); err != nil {
|
||||
return admin_api.NewRemoveGroupDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
return admin_api.NewRemoveGroupNoContent()
|
||||
})
|
||||
// Update Group
|
||||
api.AdminAPIUpdateGroupHandler = admin_api.UpdateGroupHandlerFunc(func(params admin_api.UpdateGroupParams, principal *models.Principal) middleware.Responder {
|
||||
groupUpdateResp, err := getUpdateGroupResponse(params)
|
||||
sessionID := string(*principal)
|
||||
groupUpdateResp, err := getUpdateGroupResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return admin_api.NewUpdateGroupDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -82,9 +87,9 @@ func listGroups(ctx context.Context, client MinioAdmin) (*[]string, error) {
|
||||
}
|
||||
|
||||
// getListGroupsResponse performs listGroups() and serializes it to the handler's output
|
||||
func getListGroupsResponse() (*models.ListGroupsResponse, error) {
|
||||
func getListGroupsResponse(sessionID string) (*models.ListGroupsResponse, error) {
|
||||
ctx := context.Background()
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -116,9 +121,9 @@ func groupInfo(ctx context.Context, client MinioAdmin, group string) (*madmin.Gr
|
||||
}
|
||||
|
||||
// getGroupInfoResponse performs groupInfo() and serializes it to the handler's output
|
||||
func getGroupInfoResponse(params admin_api.GroupInfoParams) (*models.Group, error) {
|
||||
func getGroupInfoResponse(sessionID string, params admin_api.GroupInfoParams) (*models.Group, error) {
|
||||
ctx := context.Background()
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -157,7 +162,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(params *models.AddGroupRequest) error {
|
||||
func getAddGroupResponse(sessionID string, params *models.AddGroupRequest) error {
|
||||
ctx := context.Background()
|
||||
// AddGroup request needed to proceed
|
||||
if params == nil {
|
||||
@@ -165,7 +170,7 @@ func getAddGroupResponse(params *models.AddGroupRequest) error {
|
||||
return errors.New(500, "error AddGroup body not in request")
|
||||
}
|
||||
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return err
|
||||
@@ -196,14 +201,14 @@ func removeGroup(ctx context.Context, client MinioAdmin, group string) error {
|
||||
}
|
||||
|
||||
// getRemoveGroupResponse performs removeGroup() and serializes it to the handler's output
|
||||
func getRemoveGroupResponse(params admin_api.RemoveGroupParams) error {
|
||||
func getRemoveGroupResponse(sessionID string, params admin_api.RemoveGroupParams) error {
|
||||
ctx := context.Background()
|
||||
|
||||
if params.Name == "" {
|
||||
log.Println("error group name not in request")
|
||||
return errors.New(500, "error group name not in request")
|
||||
}
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return err
|
||||
@@ -276,7 +281,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(params admin_api.UpdateGroupParams) (*models.Group, error) {
|
||||
func getUpdateGroupResponse(sessionID string, params admin_api.UpdateGroupParams) (*models.Group, error) {
|
||||
ctx := context.Background()
|
||||
if params.Name == "" {
|
||||
log.Println("error group name not in request")
|
||||
@@ -289,7 +294,7 @@ func getUpdateGroupResponse(params admin_api.UpdateGroupParams) (*models.Group,
|
||||
expectedGroupUpdate := params.Body
|
||||
groupName := params.Name
|
||||
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
|
||||
@@ -31,7 +31,8 @@ import (
|
||||
func registerAdminInfoHandlers(api *operations.McsAPI) {
|
||||
// return usage stats
|
||||
api.AdminAPIAdminInfoHandler = admin_api.AdminInfoHandlerFunc(func(params admin_api.AdminInfoParams, principal *models.Principal) middleware.Responder {
|
||||
infoResp, err := getAdminInfoResponse()
|
||||
sessionID := string(*principal)
|
||||
infoResp, err := getAdminInfoResponse(sessionID)
|
||||
if err != nil {
|
||||
return admin_api.NewAdminInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -62,8 +63,8 @@ func getAdminInfo(ctx context.Context, client MinioAdmin) (*UsageInfo, error) {
|
||||
}
|
||||
|
||||
// getAdminInfoResponse returns the response containing total buckets, objects and usage.
|
||||
func getAdminInfoResponse() (*models.AdminInfoResponse, error) {
|
||||
mAdmin, err := newMAdminClient()
|
||||
func getAdminInfoResponse(sessionID string) (*models.AdminInfoResponse, error) {
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
|
||||
@@ -32,7 +32,8 @@ import (
|
||||
func registerAdminNotificationEndpointsHandlers(api *operations.McsAPI) {
|
||||
// return a list of notification endpoints
|
||||
api.AdminAPINotificationEndpointListHandler = admin_api.NotificationEndpointListHandlerFunc(func(params admin_api.NotificationEndpointListParams, principal *models.Principal) middleware.Responder {
|
||||
notifEndpoints, err := getNotificationEndpointsResponse()
|
||||
sessionID := string(*principal)
|
||||
notifEndpoints, err := getNotificationEndpointsResponse(sessionID)
|
||||
if err != nil {
|
||||
return admin_api.NewNotificationEndpointListDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -40,7 +41,8 @@ func registerAdminNotificationEndpointsHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// add a new notification endpoints
|
||||
api.AdminAPIAddNotificationEndpointHandler = admin_api.AddNotificationEndpointHandlerFunc(func(params admin_api.AddNotificationEndpointParams, principal *models.Principal) middleware.Responder {
|
||||
notifEndpoints, err := getAddNotificationEndpointResponse(¶ms)
|
||||
sessionID := string(*principal)
|
||||
notifEndpoints, err := getAddNotificationEndpointResponse(sessionID, ¶ms)
|
||||
if err != nil {
|
||||
return admin_api.NewAddNotificationEndpointDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -78,8 +80,8 @@ func getNotificationEndpoints(ctx context.Context, client MinioAdmin) (*models.N
|
||||
}
|
||||
|
||||
// getNotificationEndpointsResponse returns a list of notification endpoints in the instance
|
||||
func getNotificationEndpointsResponse() (*models.NotifEndpointResponse, error) {
|
||||
mAdmin, err := newMAdminClient()
|
||||
func getNotificationEndpointsResponse(sessionID string) (*models.NotifEndpointResponse, error) {
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -151,8 +153,8 @@ func addNotificationEndpoint(ctx context.Context, client MinioAdmin, params *adm
|
||||
}
|
||||
|
||||
// getNotificationEndpointsResponse returns a list of notification endpoints in the instance
|
||||
func getAddNotificationEndpointResponse(params *admin_api.AddNotificationEndpointParams) (*models.NotificationEndpoint, error) {
|
||||
mAdmin, err := newMAdminClient()
|
||||
func getAddNotificationEndpointResponse(sessionID string, params *admin_api.AddNotificationEndpointParams) (*models.NotificationEndpoint, error) {
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
|
||||
@@ -35,7 +35,8 @@ import (
|
||||
func registersPoliciesHandler(api *operations.McsAPI) {
|
||||
// List Policies
|
||||
api.AdminAPIListPoliciesHandler = admin_api.ListPoliciesHandlerFunc(func(params admin_api.ListPoliciesParams, principal *models.Principal) middleware.Responder {
|
||||
listPoliciesResponse, err := getListPoliciesResponse()
|
||||
sessionID := string(*principal)
|
||||
listPoliciesResponse, err := getListPoliciesResponse(sessionID)
|
||||
if err != nil {
|
||||
return admin_api.NewListPoliciesDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -43,7 +44,8 @@ func registersPoliciesHandler(api *operations.McsAPI) {
|
||||
})
|
||||
// Policy Info
|
||||
api.AdminAPIPolicyInfoHandler = admin_api.PolicyInfoHandlerFunc(func(params admin_api.PolicyInfoParams, principal *models.Principal) middleware.Responder {
|
||||
policyInfo, err := getPolicyInfoResponse(params)
|
||||
sessionID := string(*principal)
|
||||
policyInfo, err := getPolicyInfoResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return admin_api.NewPolicyInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -51,7 +53,8 @@ func registersPoliciesHandler(api *operations.McsAPI) {
|
||||
})
|
||||
// Add Policy
|
||||
api.AdminAPIAddPolicyHandler = admin_api.AddPolicyHandlerFunc(func(params admin_api.AddPolicyParams, principal *models.Principal) middleware.Responder {
|
||||
policyResponse, err := getAddPolicyResponse(params.Body)
|
||||
sessionID := string(*principal)
|
||||
policyResponse, err := getAddPolicyResponse(sessionID, params.Body)
|
||||
if err != nil {
|
||||
return admin_api.NewAddPolicyDefault(500).WithPayload(&models.Error{
|
||||
Code: 500,
|
||||
@@ -62,14 +65,16 @@ func registersPoliciesHandler(api *operations.McsAPI) {
|
||||
})
|
||||
// Remove Policy
|
||||
api.AdminAPIRemovePolicyHandler = admin_api.RemovePolicyHandlerFunc(func(params admin_api.RemovePolicyParams, principal *models.Principal) middleware.Responder {
|
||||
if err := getRemovePolicyResponse(params); err != nil {
|
||||
sessionID := string(*principal)
|
||||
if err := getRemovePolicyResponse(sessionID, params); err != nil {
|
||||
return admin_api.NewRemovePolicyDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
return admin_api.NewRemovePolicyNoContent()
|
||||
})
|
||||
// Set Policy
|
||||
api.AdminAPISetPolicyHandler = admin_api.SetPolicyHandlerFunc(func(params admin_api.SetPolicyParams, principal *models.Principal) middleware.Responder {
|
||||
if err := getSetPolicyResponse(params.Name, params.Body); err != nil {
|
||||
sessionID := string(*principal)
|
||||
if err := getSetPolicyResponse(sessionID, params.Name, params.Body); err != nil {
|
||||
return admin_api.NewSetPolicyDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
return admin_api.NewSetPolicyNoContent()
|
||||
@@ -97,9 +102,9 @@ func listPolicies(ctx context.Context, client MinioAdmin) ([]*models.Policy, err
|
||||
}
|
||||
|
||||
// getListPoliciesResponse performs listPolicies() and serializes it to the handler's output
|
||||
func getListPoliciesResponse() (*models.ListPoliciesResponse, error) {
|
||||
func getListPoliciesResponse(sessionID string) (*models.ListPoliciesResponse, error) {
|
||||
ctx := context.Background()
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -131,13 +136,13 @@ func removePolicy(ctx context.Context, client MinioAdmin, name string) error {
|
||||
}
|
||||
|
||||
// getRemovePolicyResponse() performs removePolicy() and serializes it to the handler's output
|
||||
func getRemovePolicyResponse(params admin_api.RemovePolicyParams) error {
|
||||
func getRemovePolicyResponse(sessionID string, params admin_api.RemovePolicyParams) error {
|
||||
ctx := context.Background()
|
||||
if params.Name == "" {
|
||||
log.Println("error policy name not in request")
|
||||
return errors.New(500, "error policy name not in request")
|
||||
}
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return err
|
||||
@@ -173,14 +178,14 @@ func addPolicy(ctx context.Context, client MinioAdmin, name, policy string) (*mo
|
||||
}
|
||||
|
||||
// getAddPolicyResponse performs addPolicy() and serializes it to the handler's output
|
||||
func getAddPolicyResponse(params *models.AddPolicyRequest) (*models.Policy, error) {
|
||||
func getAddPolicyResponse(sessionID string, params *models.AddPolicyRequest) (*models.Policy, error) {
|
||||
ctx := context.Background()
|
||||
if params == nil {
|
||||
log.Println("error AddPolicy body not in request")
|
||||
return nil, errors.New(500, "error AddPolicy body not in request")
|
||||
}
|
||||
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -213,9 +218,9 @@ func policyInfo(ctx context.Context, client MinioAdmin, name string) (*models.Po
|
||||
}
|
||||
|
||||
// getPolicyInfoResponse performs policyInfo() and serializes it to the handler's output
|
||||
func getPolicyInfoResponse(params admin_api.PolicyInfoParams) (*models.Policy, error) {
|
||||
func getPolicyInfoResponse(sessionID string, params admin_api.PolicyInfoParams) (*models.Policy, error) {
|
||||
ctx := context.Background()
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -244,13 +249,13 @@ func setPolicy(ctx context.Context, client MinioAdmin, name, entityName string,
|
||||
}
|
||||
|
||||
// getSetPolicyResponse() performs setPolicy() and serializes it to the handler's output
|
||||
func getSetPolicyResponse(name string, params *models.SetPolicyRequest) error {
|
||||
func getSetPolicyResponse(sessionID string, name string, params *models.SetPolicyRequest) error {
|
||||
ctx := context.Background()
|
||||
if name == "" {
|
||||
log.Println("error policy name not in request")
|
||||
return errors.New(500, "error policy name not in request")
|
||||
}
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return err
|
||||
|
||||
@@ -35,7 +35,8 @@ import (
|
||||
func registerProfilingHandler(api *operations.McsAPI) {
|
||||
// Start Profiling
|
||||
api.AdminAPIProfilingStartHandler = admin_api.ProfilingStartHandlerFunc(func(params admin_api.ProfilingStartParams, principal *models.Principal) middleware.Responder {
|
||||
profilingStartResponse, err := getProfilingStartResponse(params.Body)
|
||||
sessionID := string(*principal)
|
||||
profilingStartResponse, err := getProfilingStartResponse(sessionID, params.Body)
|
||||
if err != nil {
|
||||
return admin_api.NewProfilingStartDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -43,7 +44,8 @@ func registerProfilingHandler(api *operations.McsAPI) {
|
||||
})
|
||||
// Stop and download profiling data
|
||||
api.AdminAPIProfilingStopHandler = admin_api.ProfilingStopHandlerFunc(func(params admin_api.ProfilingStopParams, principal *models.Principal) middleware.Responder {
|
||||
profilingStopResponse, err := getProfilingStopResponse()
|
||||
sessionID := string(*principal)
|
||||
profilingStopResponse, err := getProfilingStopResponse(sessionID)
|
||||
if err != nil {
|
||||
return admin_api.NewProfilingStopDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -90,13 +92,13 @@ func startProfiling(ctx context.Context, client MinioAdmin, profilerType models.
|
||||
}
|
||||
|
||||
// getProfilingStartResponse performs startProfiling() and serializes it to the handler's output
|
||||
func getProfilingStartResponse(params *models.ProfilingStartRequest) (*models.StartProfilingList, error) {
|
||||
func getProfilingStartResponse(sessionID string, params *models.ProfilingStartRequest) (*models.StartProfilingList, error) {
|
||||
ctx := context.Background()
|
||||
if params == nil {
|
||||
log.Println("error profiling type not in body request")
|
||||
return nil, errors.New(500, "error AddPolicy body not in request")
|
||||
}
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -127,9 +129,9 @@ func stopProfiling(ctx context.Context, client MinioAdmin) (io.ReadCloser, error
|
||||
}
|
||||
|
||||
// getProfilingStopResponse() performs setPolicy() and serializes it to the handler's output
|
||||
func getProfilingStopResponse() (io.ReadCloser, error) {
|
||||
func getProfilingStopResponse(sessionID string) (io.ReadCloser, error) {
|
||||
ctx := context.Background()
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
|
||||
@@ -32,7 +32,8 @@ import (
|
||||
func registerServiceHandlers(api *operations.McsAPI) {
|
||||
// Restart Service
|
||||
api.AdminAPIRestartServiceHandler = admin_api.RestartServiceHandlerFunc(func(params admin_api.RestartServiceParams, principal *models.Principal) middleware.Responder {
|
||||
if err := getRestartServiceResponse(); err != nil {
|
||||
sessionID := string(*principal)
|
||||
if err := getRestartServiceResponse(sessionID); err != nil {
|
||||
return admin_api.NewRestartServiceDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
return admin_api.NewRestartServiceNoContent()
|
||||
@@ -61,9 +62,9 @@ func serviceRestart(ctx context.Context, client MinioAdmin) error {
|
||||
}
|
||||
|
||||
// getRestartServiceResponse performs serviceRestart()
|
||||
func getRestartServiceResponse() error {
|
||||
func getRestartServiceResponse(sessionID string) error {
|
||||
ctx := context.Background()
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return err
|
||||
|
||||
@@ -34,7 +34,8 @@ import (
|
||||
func registerUsersHandlers(api *operations.McsAPI) {
|
||||
// List Users
|
||||
api.AdminAPIListUsersHandler = admin_api.ListUsersHandlerFunc(func(params admin_api.ListUsersParams, principal *models.Principal) middleware.Responder {
|
||||
listUsersResponse, err := getListUsersResponse()
|
||||
sessionID := string(*principal)
|
||||
listUsersResponse, err := getListUsersResponse(sessionID)
|
||||
if err != nil {
|
||||
return admin_api.NewListUsersDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -42,7 +43,8 @@ func registerUsersHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// Add User
|
||||
api.AdminAPIAddUserHandler = admin_api.AddUserHandlerFunc(func(params admin_api.AddUserParams, principal *models.Principal) middleware.Responder {
|
||||
userResponse, err := getUserAddResponse(params)
|
||||
sessionID := string(*principal)
|
||||
userResponse, err := getUserAddResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return admin_api.NewAddUserDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -50,7 +52,8 @@ func registerUsersHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// Remove User
|
||||
api.AdminAPIRemoveUserHandler = admin_api.RemoveUserHandlerFunc(func(params admin_api.RemoveUserParams, principal *models.Principal) middleware.Responder {
|
||||
err := getRemoveUserResponse(params)
|
||||
sessionID := string(*principal)
|
||||
err := getRemoveUserResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return admin_api.NewRemoveUserDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -58,7 +61,8 @@ func registerUsersHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// Update User-Groups
|
||||
api.AdminAPIUpdateUserGroupsHandler = admin_api.UpdateUserGroupsHandlerFunc(func(params admin_api.UpdateUserGroupsParams, principal *models.Principal) middleware.Responder {
|
||||
userUpdateResponse, err := getUpdateUserGroupsResponse(params)
|
||||
sessionID := string(*principal)
|
||||
userUpdateResponse, err := getUpdateUserGroupsResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return admin_api.NewUpdateUserGroupsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -67,7 +71,8 @@ func registerUsersHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// Get User
|
||||
api.AdminAPIGetUserInfoHandler = admin_api.GetUserInfoHandlerFunc(func(params admin_api.GetUserInfoParams, principal *models.Principal) middleware.Responder {
|
||||
userInfoResponse, err := getUserInfoResponse(params)
|
||||
sessionID := string(*principal)
|
||||
userInfoResponse, err := getUserInfoResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return admin_api.NewGetUserDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -76,7 +81,8 @@ func registerUsersHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// Update User
|
||||
api.AdminAPIUpdateUserInfoHandler = admin_api.UpdateUserInfoHandlerFunc(func(params admin_api.UpdateUserInfoParams, principal *models.Principal) middleware.Responder {
|
||||
userUpdateResponse, err := getUpdateUserResponse(params)
|
||||
sessionID := string(*principal)
|
||||
userUpdateResponse, err := getUpdateUserResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return admin_api.NewUpdateUserInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -85,9 +91,10 @@ func registerUsersHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// Update User-Groups Bulk
|
||||
api.AdminAPIBulkUpdateUsersGroupsHandler = admin_api.BulkUpdateUsersGroupsHandlerFunc(func(params admin_api.BulkUpdateUsersGroupsParams, principal *models.Principal) middleware.Responder {
|
||||
error := getAddUsersListToGroupsResponse(params)
|
||||
if error != nil {
|
||||
return admin_api.NewBulkUpdateUsersGroupsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(error.Error())})
|
||||
sessionID := string(*principal)
|
||||
err := getAddUsersListToGroupsResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return admin_api.NewBulkUpdateUsersGroupsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
|
||||
return admin_api.NewBulkUpdateUsersGroupsOK()
|
||||
@@ -119,9 +126,9 @@ func listUsers(ctx context.Context, client MinioAdmin) ([]*models.User, error) {
|
||||
}
|
||||
|
||||
// getListUsersResponse performs listUsers() and serializes it to the handler's output
|
||||
func getListUsersResponse() (*models.ListUsersResponse, error) {
|
||||
func getListUsersResponse(sessionID string) (*models.ListUsersResponse, error) {
|
||||
ctx := context.Background()
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -167,9 +174,9 @@ func addUser(ctx context.Context, client MinioAdmin, accessKey, secretKey *strin
|
||||
return userRet, nil
|
||||
}
|
||||
|
||||
func getUserAddResponse(params admin_api.AddUserParams) (*models.User, error) {
|
||||
func getUserAddResponse(sessionID string, params admin_api.AddUserParams) (*models.User, error) {
|
||||
ctx := context.Background()
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -194,10 +201,10 @@ func removeUser(ctx context.Context, client MinioAdmin, accessKey string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRemoveUserResponse(params admin_api.RemoveUserParams) error {
|
||||
func getRemoveUserResponse(sessionID string, params admin_api.RemoveUserParams) error {
|
||||
ctx := context.Background()
|
||||
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return err
|
||||
@@ -226,10 +233,10 @@ func getUserInfo(ctx context.Context, client MinioAdmin, accessKey string) (*mad
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func getUserInfoResponse(params admin_api.GetUserInfoParams) (*models.User, error) {
|
||||
func getUserInfoResponse(sessionID string, params admin_api.GetUserInfoParams) (*models.User, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -341,10 +348,10 @@ func updateUserGroups(ctx context.Context, client MinioAdmin, user string, group
|
||||
return userReturn, nil
|
||||
}
|
||||
|
||||
func getUpdateUserGroupsResponse(params admin_api.UpdateUserGroupsParams) (*models.User, error) {
|
||||
func getUpdateUserGroupsResponse(sessionID string, params admin_api.UpdateUserGroupsParams) (*models.User, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -382,10 +389,10 @@ func setUserStatus(ctx context.Context, client MinioAdmin, user string, status s
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUpdateUserResponse(params admin_api.UpdateUserInfoParams) (*models.User, error) {
|
||||
func getUpdateUserResponse(sessionID string, params admin_api.UpdateUserInfoParams) (*models.User, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return nil, err
|
||||
@@ -455,10 +462,10 @@ func addUsersListToGroups(ctx context.Context, client MinioAdmin, usersToUpdate
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAddUsersListToGroupsResponse(params admin_api.BulkUpdateUsersGroupsParams) error {
|
||||
func getAddUsersListToGroupsResponse(sessionID string, params admin_api.BulkUpdateUsersGroupsParams) error {
|
||||
ctx := context.Background()
|
||||
|
||||
mAdmin, err := newMAdminClient()
|
||||
mAdmin, err := newMAdminClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating Madmin Client:", err)
|
||||
return err
|
||||
|
||||
@@ -24,6 +24,8 @@ import (
|
||||
|
||||
mcCmd "github.com/minio/mc/cmd"
|
||||
"github.com/minio/mc/pkg/probe"
|
||||
"github.com/minio/mcs/pkg/auth"
|
||||
"github.com/minio/minio-go/v6/pkg/credentials"
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
@@ -192,14 +194,17 @@ func (ac adminClient) stopProfiling(ctx context.Context) (io.ReadCloser, error)
|
||||
return ac.client.DownloadProfilingData(ctx)
|
||||
}
|
||||
|
||||
func newMAdminClient() (*madmin.AdminClient, error) {
|
||||
endpoint := getMinIOServer()
|
||||
accessKeyID := getAccessKey()
|
||||
secretAccessKey := getSecretKey()
|
||||
|
||||
adminClient, pErr := NewAdminClient(endpoint, accessKeyID, secretAccessKey)
|
||||
if pErr != nil {
|
||||
return nil, pErr.Cause
|
||||
func newMAdminClient(jwt string) (*madmin.AdminClient, error) {
|
||||
claims, err := auth.JWTAuthenticate(jwt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
adminClient, err := madmin.NewWithOptions(getMinIOEndpoint(), &madmin.Options{
|
||||
Creds: credentials.NewStaticV4(claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken),
|
||||
Secure: getMinIOEndpointIsSecure(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return adminClient, nil
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ import (
|
||||
|
||||
mc "github.com/minio/mc/cmd"
|
||||
"github.com/minio/mc/pkg/probe"
|
||||
"github.com/minio/mcs/pkg/auth"
|
||||
xjwt "github.com/minio/mcs/pkg/auth/jwt"
|
||||
"github.com/minio/minio-go/v6"
|
||||
"github.com/minio/minio-go/v6/pkg/credentials"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -107,20 +110,67 @@ func (c mcS3Client) removeNotificationConfig(arn string, event string, prefix st
|
||||
return c.client.RemoveNotificationConfig(arn, event, prefix, suffix)
|
||||
}
|
||||
|
||||
// newMinioClient creates a new MinIO client to talk to the server
|
||||
func newMinioClient() (*minio.Client, error) {
|
||||
endpoint := getMinIOEndpoint()
|
||||
accessKeyID := getAccessKey()
|
||||
secretAccessKey := getSecretKey()
|
||||
useSSL := getMinIOEndpointIsSecure()
|
||||
// Define MCSCredentials interface with all functions to be implemented
|
||||
// by mock when testing, it should include all needed minioCredentials.Credentials api calls
|
||||
// that are used within this project.
|
||||
type MCSCredentials interface {
|
||||
Get() (credentials.Value, error)
|
||||
Expire()
|
||||
}
|
||||
|
||||
// Initialize minio client object.
|
||||
minioClient, err := minio.NewV4(endpoint, accessKeyID, secretAccessKey, useSSL)
|
||||
// Interface implementation
|
||||
//
|
||||
// Define the structure of a mc S3Client and define the functions that are actually used
|
||||
// from mcsCredentials api.
|
||||
type mcsCredentials struct {
|
||||
minioCredentials *credentials.Credentials
|
||||
}
|
||||
|
||||
// implements *Credentials.Get()
|
||||
func (c mcsCredentials) Get() (credentials.Value, error) {
|
||||
return c.minioCredentials.Get()
|
||||
}
|
||||
|
||||
// implements *Credentials.Expire()
|
||||
func (c mcsCredentials) Expire() {
|
||||
c.minioCredentials.Expire()
|
||||
}
|
||||
|
||||
func newMcsCredentials(accessKey, secretKey, location string) (*credentials.Credentials, error) {
|
||||
return credentials.NewSTSAssumeRole(getMinIOServer(), credentials.STSAssumeRoleOptions{
|
||||
AccessKey: accessKey,
|
||||
SecretKey: secretKey,
|
||||
Location: location,
|
||||
DurationSeconds: xjwt.GetMcsSTSAndJWTDurationInSeconds(),
|
||||
})
|
||||
}
|
||||
|
||||
// getMcsCredentialsFromJWT returns the *minioCredentials.Credentials associated to the
|
||||
// provided jwt, this is useful for running the Expire() or IsExpired() operations
|
||||
func getMcsCredentialsFromJWT(jwt string) (*credentials.Credentials, error) {
|
||||
claims, err := auth.JWTAuthenticate(jwt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creds := credentials.NewStaticV4(claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken)
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
return minioClient, nil
|
||||
// newMinioClient creates a new MinIO client based on the minioCredentials extracted
|
||||
// from the provided jwt
|
||||
func newMinioClient(jwt string) (*minio.Client, error) {
|
||||
creds, err := getMcsCredentialsFromJWT(jwt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
adminClient, err := minio.NewWithOptions(getMinIOEndpoint(), &minio.Options{
|
||||
Creds: creds,
|
||||
Secure: getMinIOEndpointIsSecure(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return adminClient, nil
|
||||
}
|
||||
|
||||
// newS3BucketClient creates a new mc S3Client to talk to the server based on a bucket
|
||||
@@ -150,7 +200,7 @@ func newS3BucketClient(bucketName *string) (*mc.S3Client, error) {
|
||||
// parameters.
|
||||
func newS3Config(endpoint, accessKey, secretKey string, isSecure bool) *mc.Config {
|
||||
// We have a valid alias and hostConfig. We populate the
|
||||
// credentials from the match found in the config file.
|
||||
// minioCredentials from the match found in the config file.
|
||||
s3Config := new(mc.Config)
|
||||
|
||||
s3Config.AppName = "mcs" // TODO: make this a constant
|
||||
|
||||
@@ -198,7 +198,6 @@ func getSecureFeaturePolicy() string {
|
||||
return env.Get(McsSecureFeaturePolicy, "")
|
||||
}
|
||||
|
||||
// FeaturePolicy allows the Feature-Policy header with the value to be set with a custom value. Default is "".
|
||||
func getSecureExpectCTHeader() string {
|
||||
return env.Get(McsSecureExpectCTHeader, "")
|
||||
}
|
||||
|
||||
@@ -24,9 +24,8 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/mcs/restapi/sessions"
|
||||
|
||||
"github.com/minio/mcs/models"
|
||||
"github.com/minio/mcs/pkg/auth"
|
||||
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
|
||||
@@ -60,7 +59,7 @@ func configureAPI(api *operations.McsAPI) http.Handler {
|
||||
// Applies when the "x-token" header is set
|
||||
|
||||
api.KeyAuth = func(token string, scopes []string) (*models.Principal, error) {
|
||||
if sessions.GetInstance().ValidSession(token) {
|
||||
if auth.IsJWTValid(token) {
|
||||
prin := models.Principal(token)
|
||||
return &prin, nil
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
mcCmd "github.com/minio/mc/cmd"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestNewSession tests the creation of a new sesison for a valid cfg object
|
||||
func TestNewSession(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
cfg := mcCmd.Config{}
|
||||
// Test Case 1: No collision
|
||||
sessionID := GetInstance().NewSession(&cfg)
|
||||
assert.NotEmpty(sessionID, "Session ID was returned empty")
|
||||
}
|
||||
|
||||
// TestValidateSession tests a valid sessionId on the sessions object
|
||||
func TestValidateSession(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
cfg := mcCmd.Config{}
|
||||
// Test Case 1: Valid session
|
||||
sessionID := GetInstance().NewSession(&cfg)
|
||||
isValid := GetInstance().ValidSession(sessionID)
|
||||
assert.Equal(isValid, true, "Session was not found valid")
|
||||
// Test Case 2: Invalid session
|
||||
isInvalid := GetInstance().ValidSession("random")
|
||||
assert.Equal(isInvalid, false, "Session was found valid")
|
||||
}
|
||||
@@ -37,7 +37,8 @@ import (
|
||||
func registerBucketsHandlers(api *operations.McsAPI) {
|
||||
// list buckets
|
||||
api.UserAPIListBucketsHandler = user_api.ListBucketsHandlerFunc(func(params user_api.ListBucketsParams, principal *models.Principal) middleware.Responder {
|
||||
listBucketsResponse, err := getListBucketsResponse()
|
||||
sessionID := string(*principal)
|
||||
listBucketsResponse, err := getListBucketsResponse(sessionID)
|
||||
if err != nil {
|
||||
return user_api.NewListBucketsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -45,14 +46,16 @@ func registerBucketsHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// make bucket
|
||||
api.UserAPIMakeBucketHandler = user_api.MakeBucketHandlerFunc(func(params user_api.MakeBucketParams, principal *models.Principal) middleware.Responder {
|
||||
if err := getMakeBucketResponse(params.Body); err != nil {
|
||||
sessionID := string(*principal)
|
||||
if err := getMakeBucketResponse(sessionID, params.Body); err != nil {
|
||||
return user_api.NewMakeBucketDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
return user_api.NewMakeBucketCreated()
|
||||
})
|
||||
// delete bucket
|
||||
api.UserAPIDeleteBucketHandler = user_api.DeleteBucketHandlerFunc(func(params user_api.DeleteBucketParams, principal *models.Principal) middleware.Responder {
|
||||
if err := getDeleteBucketResponse(params); err != nil {
|
||||
sessionID := string(*principal)
|
||||
if err := getDeleteBucketResponse(sessionID, params); err != nil {
|
||||
return user_api.NewMakeBucketDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
|
||||
}
|
||||
@@ -60,7 +63,8 @@ func registerBucketsHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// get bucket info
|
||||
api.UserAPIBucketInfoHandler = user_api.BucketInfoHandlerFunc(func(params user_api.BucketInfoParams, principal *models.Principal) middleware.Responder {
|
||||
bucketInfoResp, err := getBucketInfoResponse(params)
|
||||
sessionID := string(*principal)
|
||||
bucketInfoResp, err := getBucketInfoResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return user_api.NewBucketInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -69,7 +73,8 @@ func registerBucketsHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
// set bucket policy
|
||||
api.UserAPIBucketSetPolicyHandler = user_api.BucketSetPolicyHandlerFunc(func(params user_api.BucketSetPolicyParams, principal *models.Principal) middleware.Responder {
|
||||
bucketSetPolicyResp, err := getBucketSetPolicyResponse(params.Name, params.Body)
|
||||
sessionID := string(*principal)
|
||||
bucketSetPolicyResp, err := getBucketSetPolicyResponse(sessionID, params.Name, params.Body)
|
||||
if err != nil {
|
||||
return user_api.NewBucketSetPolicyDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -97,11 +102,10 @@ func listBuckets(ctx context.Context, client MinioClient) ([]*models.Bucket, err
|
||||
}
|
||||
|
||||
// getListBucketsResponse performs listBuckets() and serializes it to the handler's output
|
||||
func getListBucketsResponse() (*models.ListBucketsResponse, error) {
|
||||
func getListBucketsResponse(sessionID string) (*models.ListBucketsResponse, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
|
||||
mClient, err := newMinioClient()
|
||||
mClient, err := newMinioClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating MinIO Client:", err)
|
||||
return nil, err
|
||||
@@ -133,7 +137,7 @@ func makeBucket(ctx context.Context, client MinioClient, bucketName string) erro
|
||||
}
|
||||
|
||||
// getMakeBucketResponse performs makeBucket() to create a bucket with its access policy
|
||||
func getMakeBucketResponse(br *models.MakeBucketRequest) error {
|
||||
func getMakeBucketResponse(sessionID string, br *models.MakeBucketRequest) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
// bucket request needed to proceed
|
||||
@@ -141,7 +145,7 @@ func getMakeBucketResponse(br *models.MakeBucketRequest) error {
|
||||
log.Println("error bucket body not in request")
|
||||
return errors.New(500, "error bucket body not in request")
|
||||
}
|
||||
mClient, err := newMinioClient()
|
||||
mClient, err := newMinioClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating MinIO Client:", err)
|
||||
return err
|
||||
@@ -187,11 +191,11 @@ func setBucketAccessPolicy(ctx context.Context, client MinioClient, bucketName s
|
||||
|
||||
// getBucketSetPolicyResponse calls setBucketAccessPolicy() to set a access policy to a bucket
|
||||
// and returns the serialized output.
|
||||
func getBucketSetPolicyResponse(bucketName string, req *models.SetBucketPolicyRequest) (*models.Bucket, error) {
|
||||
func getBucketSetPolicyResponse(sessionID string, bucketName string, req *models.SetBucketPolicyRequest) (*models.Bucket, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
|
||||
mClient, err := newMinioClient()
|
||||
mClient, err := newMinioClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating MinIO Client:", err)
|
||||
return nil, err
|
||||
@@ -223,14 +227,14 @@ func removeBucket(client MinioClient, bucketName string) error {
|
||||
}
|
||||
|
||||
// getDeleteBucketResponse performs removeBucket() to delete a bucket
|
||||
func getDeleteBucketResponse(params user_api.DeleteBucketParams) error {
|
||||
func getDeleteBucketResponse(sessionID string, params user_api.DeleteBucketParams) error {
|
||||
if params.Name == "" {
|
||||
log.Println("error bucket name not in request")
|
||||
return errors.New(500, "error bucket name not in request")
|
||||
}
|
||||
bucketName := params.Name
|
||||
|
||||
mClient, err := newMinioClient()
|
||||
mClient, err := newMinioClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating MinIO Client:", err)
|
||||
return err
|
||||
@@ -272,8 +276,8 @@ func getBucketInfo(client MinioClient, bucketName string) (*models.Bucket, error
|
||||
}
|
||||
|
||||
// getBucketInfoResponse calls getBucketInfo() to get the bucket's info
|
||||
func getBucketInfoResponse(params user_api.BucketInfoParams) (*models.Bucket, error) {
|
||||
mClient, err := newMinioClient()
|
||||
func getBucketInfoResponse(sessionID string, params user_api.BucketInfoParams) (*models.Bucket, error) {
|
||||
mClient, err := newMinioClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating MinIO Client:", err)
|
||||
return nil, err
|
||||
|
||||
@@ -31,7 +31,8 @@ import (
|
||||
func registerBucketEventsHandlers(api *operations.McsAPI) {
|
||||
// list bucket events
|
||||
api.UserAPIListBucketEventsHandler = user_api.ListBucketEventsHandlerFunc(func(params user_api.ListBucketEventsParams, principal *models.Principal) middleware.Responder {
|
||||
listBucketEventsResponse, err := getListBucketEventsResponse(params)
|
||||
sessionID := string(*principal)
|
||||
listBucketEventsResponse, err := getListBucketEventsResponse(sessionID, params)
|
||||
if err != nil {
|
||||
return user_api.NewListBucketEventsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
|
||||
}
|
||||
@@ -124,8 +125,8 @@ func listBucketEvents(client MinioClient, bucketName string) ([]*models.Notifica
|
||||
}
|
||||
|
||||
// getListBucketsResponse performs listBucketEvents() and serializes it to the handler's output
|
||||
func getListBucketEventsResponse(params user_api.ListBucketEventsParams) (*models.ListBucketEventsResponse, error) {
|
||||
mClient, err := newMinioClient()
|
||||
func getListBucketEventsResponse(sessionID string, params user_api.ListBucketEventsParams) (*models.ListBucketEventsResponse, error) {
|
||||
mClient, err := newMinioClient(sessionID)
|
||||
if err != nil {
|
||||
log.Println("error creating MinIO Client:", err)
|
||||
return nil, err
|
||||
|
||||
@@ -20,31 +20,14 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/minio/mc/pkg/probe"
|
||||
|
||||
"github.com/minio/mcs/restapi/sessions"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/go-openapi/swag"
|
||||
mcCmd "github.com/minio/mc/cmd"
|
||||
"github.com/minio/mcs/models"
|
||||
"github.com/minio/mcs/pkg/auth"
|
||||
"github.com/minio/mcs/restapi/operations"
|
||||
"github.com/minio/mcs/restapi/operations/user_api"
|
||||
)
|
||||
|
||||
// Wraps the code at mc/cmd
|
||||
type McCmd interface {
|
||||
BuildS3Config(url, accessKey, secretKey, api, lookup string) (*mcCmd.Config, *probe.Error)
|
||||
}
|
||||
|
||||
// Implementation of McCmd
|
||||
type mcCmdWrapper struct {
|
||||
}
|
||||
|
||||
func (mc mcCmdWrapper) BuildS3Config(url, accessKey, secretKey, api, lookup string) (*mcCmd.Config, *probe.Error) {
|
||||
return mcCmd.BuildS3Config(url, accessKey, secretKey, api, lookup)
|
||||
}
|
||||
|
||||
func registerLoginHandlers(api *operations.McsAPI) {
|
||||
// get login strategy
|
||||
api.UserAPILoginDetailHandler = user_api.LoginDetailHandlerFunc(func(params user_api.LoginDetailParams) middleware.Responder {
|
||||
@@ -59,28 +42,34 @@ func registerLoginHandlers(api *operations.McsAPI) {
|
||||
}
|
||||
return user_api.NewLoginCreated().WithPayload(loginResponse)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
var ErrInvalidCredentials = errors.New("invalid credentials")
|
||||
var ErrInvalidCredentials = errors.New("invalid minioCredentials")
|
||||
|
||||
// login performs a check of credentials against MinIO
|
||||
func login(mc McCmd, accessKey, secretKey *string) (*string, error) {
|
||||
// Probe the credentials
|
||||
cfg, pErr := mc.BuildS3Config(getMinIOServer(), *accessKey, *secretKey, "", "auto")
|
||||
if pErr != nil {
|
||||
// login performs a check of minioCredentials against MinIO
|
||||
func login(credentials MCSCredentials) (*string, error) {
|
||||
// try to obtain minioCredentials,
|
||||
tokens, err := credentials.Get()
|
||||
if err != nil {
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
// if we made it here, the credentials work, generate a session
|
||||
sessionID := sessions.GetInstance().NewSession(cfg)
|
||||
|
||||
return &sessionID, nil
|
||||
// if we made it here, the minioCredentials work, generate a jwt with claims
|
||||
jwt, err := auth.NewJWTWithClaimsForClient(&tokens, getMinIOServer())
|
||||
if err != nil {
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
return &jwt, nil
|
||||
}
|
||||
|
||||
// getLoginResponse performs login() and serializes it to the handler's output
|
||||
func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, error) {
|
||||
mc := mcCmdWrapper{}
|
||||
sessionID, err := login(&mc, lr.AccessKey, lr.SecretKey)
|
||||
creds, err := newMcsCredentials(*lr.AccessKey, *lr.SecretKey, "")
|
||||
if err != nil {
|
||||
log.Println("error login:", err)
|
||||
return nil, err
|
||||
}
|
||||
credentials := mcsCredentials{minioCredentials: creds}
|
||||
sessionID, err := login(credentials)
|
||||
if err != nil {
|
||||
log.Println("error login:", err)
|
||||
return nil, err
|
||||
|
||||
@@ -20,43 +20,41 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
mcCmd "github.com/minio/mc/cmd"
|
||||
"github.com/minio/mc/pkg/probe"
|
||||
"github.com/minio/minio-go/v6/pkg/credentials"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var mcBuildS3ConfigMock func(url, accessKey, secretKey, api, lookup string) (*mcCmd.Config, *probe.Error)
|
||||
// Define a mock struct of MCSCredentials interface implementation
|
||||
type mcsCredentialsMock struct{}
|
||||
|
||||
type mcCmdMock struct{}
|
||||
// Common mocks
|
||||
var mcsCredentialsGetMock func() (credentials.Value, error)
|
||||
|
||||
func (mc mcCmdMock) BuildS3Config(url, accessKey, secretKey, api, lookup string) (*mcCmd.Config, *probe.Error) {
|
||||
return mcBuildS3ConfigMock(url, accessKey, secretKey, api, lookup)
|
||||
// mock function of Get()
|
||||
func (ac mcsCredentialsMock) Get() (credentials.Value, error) {
|
||||
return mcsCredentialsGetMock()
|
||||
}
|
||||
|
||||
// TestLogin tests the case of passing a valid and an invalid access/secret pair
|
||||
func TestLogin(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
// We will write a test against play
|
||||
// Probe the credentials
|
||||
mcx := mcCmdMock{}
|
||||
access := "ABCDEFHIJK"
|
||||
secret := "ABCDEFHIJKABCDEFHIJK"
|
||||
|
||||
// Test Case 1: Valid credentials
|
||||
mcBuildS3ConfigMock = func(url, accessKey, secretKey, api, lookup string) (config *mcCmd.Config, p *probe.Error) {
|
||||
return &mcCmd.Config{}, nil
|
||||
funcAssert := assert.New(t)
|
||||
mcsCredentials := mcsCredentialsMock{}
|
||||
// Test Case 1: Valid mcsCredentials
|
||||
mcsCredentialsGetMock = func() (credentials.Value, error) {
|
||||
return credentials.Value{
|
||||
AccessKeyID: "fakeAccessKeyID",
|
||||
SecretAccessKey: "fakeSecretAccessKey",
|
||||
SessionToken: "fakeSessionToken",
|
||||
SignerType: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
sessionID, err := login(mcx, &access, &secret)
|
||||
assert.NotEmpty(sessionID, "Session ID was returned empty")
|
||||
assert.Nil(err, "error creating a session")
|
||||
jwt, err := login(mcsCredentials)
|
||||
funcAssert.NotEmpty(jwt, "JWT was returned empty")
|
||||
funcAssert.Nil(err, "error creating a session")
|
||||
|
||||
// Test Case 2: Invalid credentials
|
||||
mcBuildS3ConfigMock = func(url, accessKey, secretKey, api, lookup string) (config *mcCmd.Config, p *probe.Error) {
|
||||
return nil, probe.NewError(errors.New("Bad credentials"))
|
||||
mcsCredentialsGetMock = func() (credentials.Value, error) {
|
||||
return credentials.Value{}, errors.New("")
|
||||
}
|
||||
|
||||
sessionID, err = login(mcx, &access, &secret)
|
||||
assert.Empty(sessionID, "Session ID was not returned empty")
|
||||
assert.NotNil(err, "not error returned creating a session")
|
||||
_, err = login(mcsCredentials)
|
||||
funcAssert.NotNil(err, "not error returned creating a session")
|
||||
}
|
||||
|
||||
@@ -17,14 +17,13 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/minio/mcs/models"
|
||||
"github.com/minio/mcs/restapi/operations"
|
||||
"github.com/minio/mcs/restapi/operations/user_api"
|
||||
"github.com/minio/mcs/restapi/sessions"
|
||||
)
|
||||
|
||||
func registerLogoutHandlers(api *operations.McsAPI) {
|
||||
@@ -38,21 +37,23 @@ func registerLogoutHandlers(api *operations.McsAPI) {
|
||||
})
|
||||
}
|
||||
|
||||
// logout() deletes provided bearer token from in memory sessions map
|
||||
// then checks that the session actually got removed
|
||||
func logout(sessionID string) error {
|
||||
sessionsMap := sessions.GetInstance()
|
||||
sessionsMap.DeleteSession(sessionID)
|
||||
if sessionsMap.ValidSession(sessionID) {
|
||||
return errors.New("something went wrong deleting your session, please try again")
|
||||
}
|
||||
return nil
|
||||
// logout() call Expire() on the provided minioCredentials
|
||||
func logout(credentials MCSCredentials) {
|
||||
credentials.Expire()
|
||||
}
|
||||
|
||||
// getLogoutResponse performs logout() and returns nil or error
|
||||
func getLogoutResponse(sessionID string) error {
|
||||
if err := logout(sessionID); err != nil {
|
||||
func getLogoutResponse(jwt string) error {
|
||||
creds, err := getMcsCredentialsFromJWT(jwt)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
credentials := mcsCredentials{minioCredentials: creds}
|
||||
if err != nil {
|
||||
log.Println("error creating MinIO Client:", err)
|
||||
return err
|
||||
}
|
||||
logout(credentials)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
import "testing"
|
||||
|
||||
mcCmd "github.com/minio/mc/cmd"
|
||||
"github.com/minio/mcs/restapi/sessions"
|
||||
)
|
||||
|
||||
// TestLogout tests the case of deleting a valid session id
|
||||
func TestLogout(t *testing.T) {
|
||||
cfg := mcCmd.Config{}
|
||||
// Creating a new session
|
||||
sessionID := sessions.GetInstance().NewSession(&cfg)
|
||||
// Test Case 1: Delete a session Valid sessionID
|
||||
function := "logout()"
|
||||
err := logout(sessionID)
|
||||
if err != nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
|
||||
}
|
||||
// mock function of Get()
|
||||
func (ac mcsCredentialsMock) Expire() {
|
||||
// Do nothing
|
||||
// Implementing this method for the mcsCredentials interface
|
||||
}
|
||||
|
||||
func TestLogout(t *testing.T) {
|
||||
// There's nothing to test right now
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user