Have simpler JWT authentication. (#3501)
parent
69559aa101
commit
ee0172dfe4
@ -0,0 +1,116 @@ |
|||||||
|
/* |
||||||
|
* Minio Cloud Storage, (C) 2016 Minio, Inc. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"net/http" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
jwtgo "github.com/dgrijalva/jwt-go" |
||||||
|
jwtreq "github.com/dgrijalva/jwt-go/request" |
||||||
|
"golang.org/x/crypto/bcrypt" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
jwtAlgorithm = "Bearer" |
||||||
|
|
||||||
|
// Default JWT token for web handlers is one day.
|
||||||
|
defaultJWTExpiry = 24 * time.Hour |
||||||
|
|
||||||
|
// Inter-node JWT token expiry is 100 years approx.
|
||||||
|
defaultInterNodeJWTExpiry = 100 * 365 * 24 * time.Hour |
||||||
|
) |
||||||
|
|
||||||
|
var errInvalidAccessKeyLength = errors.New("Invalid access key, access key should be 5 to 20 characters in length") |
||||||
|
var errInvalidSecretKeyLength = errors.New("Invalid secret key, secret key should be 8 to 40 characters in length") |
||||||
|
|
||||||
|
var errInvalidAccessKeyID = errors.New("The access key ID you provided does not exist in our records") |
||||||
|
var errAuthentication = errors.New("Authentication failed, check your access credentials") |
||||||
|
|
||||||
|
func authenticateJWT(accessKey, secretKey string, expiry time.Duration) (string, error) { |
||||||
|
// Trim spaces.
|
||||||
|
accessKey = strings.TrimSpace(accessKey) |
||||||
|
|
||||||
|
if !isAccessKeyValid(accessKey) { |
||||||
|
return "", errInvalidAccessKeyLength |
||||||
|
} |
||||||
|
if !isSecretKeyValid(secretKey) { |
||||||
|
return "", errInvalidSecretKeyLength |
||||||
|
} |
||||||
|
|
||||||
|
serverCred := serverConfig.GetCredential() |
||||||
|
|
||||||
|
// Validate access key.
|
||||||
|
if accessKey != serverCred.AccessKey { |
||||||
|
return "", errInvalidAccessKeyID |
||||||
|
} |
||||||
|
|
||||||
|
// Validate secret key.
|
||||||
|
// Using bcrypt to avoid timing attacks.
|
||||||
|
hashedSecretKey, _ := bcrypt.GenerateFromPassword([]byte(serverCred.SecretKey), bcrypt.DefaultCost) |
||||||
|
if bcrypt.CompareHashAndPassword(hashedSecretKey, []byte(secretKey)) != nil { |
||||||
|
return "", errAuthentication |
||||||
|
} |
||||||
|
|
||||||
|
utcNow := time.Now().UTC() |
||||||
|
token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims{ |
||||||
|
"exp": utcNow.Add(expiry).Unix(), |
||||||
|
"iat": utcNow.Unix(), |
||||||
|
"sub": accessKey, |
||||||
|
}) |
||||||
|
|
||||||
|
return token.SignedString([]byte(serverCred.SecretKey)) |
||||||
|
} |
||||||
|
|
||||||
|
func authenticateNode(accessKey, secretKey string) (string, error) { |
||||||
|
return authenticateJWT(accessKey, secretKey, defaultInterNodeJWTExpiry) |
||||||
|
} |
||||||
|
|
||||||
|
func authenticateWeb(accessKey, secretKey string) (string, error) { |
||||||
|
return authenticateJWT(accessKey, secretKey, defaultJWTExpiry) |
||||||
|
} |
||||||
|
|
||||||
|
func keyFuncCallback(jwtToken *jwtgo.Token) (interface{}, error) { |
||||||
|
if _, ok := jwtToken.Method.(*jwtgo.SigningMethodHMAC); !ok { |
||||||
|
return nil, fmt.Errorf("Unexpected signing method: %v", jwtToken.Header["alg"]) |
||||||
|
} |
||||||
|
|
||||||
|
return []byte(serverConfig.GetCredential().SecretKey), nil |
||||||
|
} |
||||||
|
|
||||||
|
func isAuthTokenValid(tokenString string) bool { |
||||||
|
jwtToken, err := jwtgo.Parse(tokenString, keyFuncCallback) |
||||||
|
if err != nil { |
||||||
|
errorIf(err, "Unable to parse JWT token string") |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
return jwtToken.Valid |
||||||
|
} |
||||||
|
|
||||||
|
func isHTTPRequestValid(req *http.Request) bool { |
||||||
|
jwtToken, err := jwtreq.ParseFromRequest(req, jwtreq.AuthorizationHeaderExtractor, keyFuncCallback) |
||||||
|
if err != nil { |
||||||
|
errorIf(err, "Unable to parse JWT token string") |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
return jwtToken.Valid |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
/* |
||||||
|
* Minio Cloud Storage, (C) 2016 Minio, Inc. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package cmd |
||||||
|
|
||||||
|
import "testing" |
||||||
|
|
||||||
|
func testAuthenticate(authType string, t *testing.T) { |
||||||
|
testPath, err := newTestConfig("us-east-1") |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("unable initialize config file, %s", err) |
||||||
|
} |
||||||
|
defer removeAll(testPath) |
||||||
|
|
||||||
|
serverCred := serverConfig.GetCredential() |
||||||
|
|
||||||
|
// Define test cases.
|
||||||
|
testCases := []struct { |
||||||
|
accessKey string |
||||||
|
secretKey string |
||||||
|
expectedErr error |
||||||
|
}{ |
||||||
|
// Access key too small.
|
||||||
|
{"user", "pass", errInvalidAccessKeyLength}, |
||||||
|
// Access key too long.
|
||||||
|
{"user12345678901234567", "pass", errInvalidAccessKeyLength}, |
||||||
|
// Access key contains unsuppported characters.
|
||||||
|
{"!@#$", "pass", errInvalidAccessKeyLength}, |
||||||
|
// Secret key too small.
|
||||||
|
{"myuser", "pass", errInvalidSecretKeyLength}, |
||||||
|
// Secret key too long.
|
||||||
|
{"myuser", "pass1234567890123456789012345678901234567", errInvalidSecretKeyLength}, |
||||||
|
// Authentication error.
|
||||||
|
{"myuser", "mypassword", errInvalidAccessKeyID}, |
||||||
|
// Authentication error.
|
||||||
|
{serverCred.AccessKey, "mypassword", errAuthentication}, |
||||||
|
// Success.
|
||||||
|
{serverCred.AccessKey, serverCred.SecretKey, nil}, |
||||||
|
// Success when access key contains leading/trailing spaces.
|
||||||
|
{" " + serverCred.AccessKey + " ", serverCred.SecretKey, nil}, |
||||||
|
} |
||||||
|
|
||||||
|
// Run tests.
|
||||||
|
for _, testCase := range testCases { |
||||||
|
var err error |
||||||
|
if authType == "node" { |
||||||
|
_, err = authenticateNode(testCase.accessKey, testCase.secretKey) |
||||||
|
} else if authType == "web" { |
||||||
|
_, err = authenticateWeb(testCase.accessKey, testCase.secretKey) |
||||||
|
} |
||||||
|
|
||||||
|
if testCase.expectedErr != nil { |
||||||
|
if err == nil { |
||||||
|
t.Fatalf("%+v: expected: %s, got: <nil>", testCase, testCase.expectedErr) |
||||||
|
} |
||||||
|
if testCase.expectedErr.Error() != err.Error() { |
||||||
|
t.Fatalf("%+v: expected: %s, got: %s", testCase, testCase.expectedErr, err) |
||||||
|
} |
||||||
|
} else if err != nil { |
||||||
|
t.Fatalf("%+v: expected: <nil>, got: %s", testCase, err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestNodeAuthenticate(t *testing.T) { |
||||||
|
testAuthenticate("node", t) |
||||||
|
} |
||||||
|
|
||||||
|
func TestWebAuthenticate(t *testing.T) { |
||||||
|
testAuthenticate("web", t) |
||||||
|
} |
@ -1,103 +0,0 @@ |
|||||||
/* |
|
||||||
* Minio Cloud Storage, (C) 2016 Minio, Inc. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
package cmd |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"strings" |
|
||||||
"time" |
|
||||||
|
|
||||||
jwtgo "github.com/dgrijalva/jwt-go" |
|
||||||
"golang.org/x/crypto/bcrypt" |
|
||||||
) |
|
||||||
|
|
||||||
const jwtAlgorithm = "Bearer" |
|
||||||
|
|
||||||
// JWT - jwt auth backend
|
|
||||||
type JWT struct { |
|
||||||
credential |
|
||||||
expiry time.Duration |
|
||||||
} |
|
||||||
|
|
||||||
const ( |
|
||||||
// Default JWT token for web handlers is one day.
|
|
||||||
defaultJWTExpiry time.Duration = time.Hour * 24 |
|
||||||
|
|
||||||
// Inter-node JWT token expiry is 100 years.
|
|
||||||
defaultInterNodeJWTExpiry time.Duration = time.Hour * 24 * 365 * 100 |
|
||||||
) |
|
||||||
|
|
||||||
// newJWT - returns new JWT object.
|
|
||||||
func newJWT(expiry time.Duration, cred credential) (*JWT, error) { |
|
||||||
if !isAccessKeyValid(cred.AccessKey) { |
|
||||||
return nil, errInvalidAccessKeyLength |
|
||||||
} |
|
||||||
if !isSecretKeyValid(cred.SecretKey) { |
|
||||||
return nil, errInvalidSecretKeyLength |
|
||||||
} |
|
||||||
return &JWT{cred, expiry}, nil |
|
||||||
} |
|
||||||
|
|
||||||
var errInvalidAccessKeyLength = errors.New("Invalid access key, access key should be 5 to 20 characters in length") |
|
||||||
var errInvalidSecretKeyLength = errors.New("Invalid secret key, secret key should be 8 to 40 characters in length") |
|
||||||
|
|
||||||
// GenerateToken - generates a new Json Web Token based on the incoming access key.
|
|
||||||
func (jwt *JWT) GenerateToken(accessKey string) (string, error) { |
|
||||||
// Trim spaces.
|
|
||||||
accessKey = strings.TrimSpace(accessKey) |
|
||||||
|
|
||||||
if !isAccessKeyValid(accessKey) { |
|
||||||
return "", errInvalidAccessKeyLength |
|
||||||
} |
|
||||||
|
|
||||||
tUTCNow := time.Now().UTC() |
|
||||||
token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims{ |
|
||||||
// Token expires in 10hrs.
|
|
||||||
"exp": tUTCNow.Add(jwt.expiry).Unix(), |
|
||||||
"iat": tUTCNow.Unix(), |
|
||||||
"sub": accessKey, |
|
||||||
}) |
|
||||||
return token.SignedString([]byte(jwt.SecretKey)) |
|
||||||
} |
|
||||||
|
|
||||||
var errInvalidAccessKeyID = errors.New("The access key ID you provided does not exist in our records") |
|
||||||
var errAuthentication = errors.New("Authentication failed, check your access credentials") |
|
||||||
|
|
||||||
// Authenticate - authenticates incoming access key and secret key.
|
|
||||||
func (jwt *JWT) Authenticate(accessKey, secretKey string) error { |
|
||||||
// Trim spaces.
|
|
||||||
accessKey = strings.TrimSpace(accessKey) |
|
||||||
|
|
||||||
if !isAccessKeyValid(accessKey) { |
|
||||||
return errInvalidAccessKeyLength |
|
||||||
} |
|
||||||
if !isSecretKeyValid(secretKey) { |
|
||||||
return errInvalidSecretKeyLength |
|
||||||
} |
|
||||||
|
|
||||||
if accessKey != jwt.AccessKey { |
|
||||||
return errInvalidAccessKeyID |
|
||||||
} |
|
||||||
|
|
||||||
hashedSecretKey, _ := bcrypt.GenerateFromPassword([]byte(jwt.SecretKey), bcrypt.DefaultCost) |
|
||||||
if bcrypt.CompareHashAndPassword(hashedSecretKey, []byte(secretKey)) != nil { |
|
||||||
return errAuthentication |
|
||||||
} |
|
||||||
|
|
||||||
// Success.
|
|
||||||
return nil |
|
||||||
} |
|
@ -1,214 +0,0 @@ |
|||||||
/* |
|
||||||
* Minio Cloud Storage, (C) 2016 Minio, Inc. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
package cmd |
|
||||||
|
|
||||||
import ( |
|
||||||
"io/ioutil" |
|
||||||
"os" |
|
||||||
"path" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
// Tests newJWT()
|
|
||||||
func TestNewJWT(t *testing.T) { |
|
||||||
savedServerConfig := serverConfig |
|
||||||
defer func() { |
|
||||||
serverConfig = savedServerConfig |
|
||||||
}() |
|
||||||
serverConfig = nil |
|
||||||
|
|
||||||
// Test non-existent config directory.
|
|
||||||
path1, err := ioutil.TempDir(globalTestTmpDir, "minio-") |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("Unable to create a temporary directory, %s", err) |
|
||||||
} |
|
||||||
defer removeAll(path1) |
|
||||||
|
|
||||||
// Test empty config directory.
|
|
||||||
path2, err := ioutil.TempDir(globalTestTmpDir, "minio-") |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("Unable to create a temporary directory, %s", err) |
|
||||||
} |
|
||||||
defer removeAll(path2) |
|
||||||
|
|
||||||
// Test empty config file.
|
|
||||||
path3, err := ioutil.TempDir(globalTestTmpDir, "minio-") |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("Unable to create a temporary directory, %s", err) |
|
||||||
} |
|
||||||
defer removeAll(path3) |
|
||||||
|
|
||||||
if err = ioutil.WriteFile(path.Join(path3, "config.json"), []byte{}, os.ModePerm); err != nil { |
|
||||||
t.Fatalf("unable to create config file, %s", err) |
|
||||||
} |
|
||||||
|
|
||||||
// Test initialized config file.
|
|
||||||
path4, err := ioutil.TempDir(globalTestTmpDir, "minio-") |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("Unable to create a temporary directory, %s", err) |
|
||||||
} |
|
||||||
defer removeAll(path4) |
|
||||||
|
|
||||||
// Define test cases.
|
|
||||||
testCases := []struct { |
|
||||||
dirPath string |
|
||||||
init bool |
|
||||||
cred *credential |
|
||||||
expectedErr error |
|
||||||
}{ |
|
||||||
// Test initialized config file.
|
|
||||||
{path4, true, nil, nil}, |
|
||||||
// Test to read already created config file.
|
|
||||||
{path4, true, nil, nil}, |
|
||||||
// Access key is too small.
|
|
||||||
{path4, false, &credential{"user", "pass"}, errInvalidAccessKeyLength}, |
|
||||||
// Access key is too long.
|
|
||||||
{path4, false, &credential{"user12345678901234567", "pass"}, errInvalidAccessKeyLength}, |
|
||||||
// Secret key is too small.
|
|
||||||
{path4, false, &credential{"myuser", "pass"}, errInvalidSecretKeyLength}, |
|
||||||
// Secret key is too long.
|
|
||||||
{path4, false, &credential{"myuser", "pass1234567890123456789012345678901234567"}, errInvalidSecretKeyLength}, |
|
||||||
// Valid access/secret keys.
|
|
||||||
{path4, false, &credential{"myuser", "mypassword"}, nil}, |
|
||||||
} |
|
||||||
|
|
||||||
// Run tests.
|
|
||||||
for _, testCase := range testCases { |
|
||||||
setGlobalConfigPath(testCase.dirPath) |
|
||||||
if testCase.init { |
|
||||||
if _, err := initConfig(); err != nil { |
|
||||||
t.Fatalf("unable initialize config file, %s", err) |
|
||||||
} |
|
||||||
} |
|
||||||
if testCase.cred != nil { |
|
||||||
serverConfig.SetCredential(*testCase.cred) |
|
||||||
} |
|
||||||
_, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential()) |
|
||||||
if testCase.expectedErr != nil { |
|
||||||
if err == nil { |
|
||||||
t.Fatalf("%+v: expected: %s, got: <nil>", testCase, testCase.expectedErr) |
|
||||||
} |
|
||||||
if testCase.expectedErr.Error() != err.Error() { |
|
||||||
t.Fatalf("%+v: expected: %s, got: %s", testCase, testCase.expectedErr, err) |
|
||||||
} |
|
||||||
} else if err != nil { |
|
||||||
t.Fatalf("%+v: expected: <nil>, got: %s", testCase, err) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Tests JWT.GenerateToken()
|
|
||||||
func TestGenerateToken(t *testing.T) { |
|
||||||
testPath, err := newTestConfig("us-east-1") |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("unable initialize config file, %s", err) |
|
||||||
} |
|
||||||
defer removeAll(testPath) |
|
||||||
|
|
||||||
jwt, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential()) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("unable get new JWT, %s", err) |
|
||||||
} |
|
||||||
|
|
||||||
// Define test cases.
|
|
||||||
testCases := []struct { |
|
||||||
accessKey string |
|
||||||
expectedErr error |
|
||||||
}{ |
|
||||||
// Access key is too small.
|
|
||||||
{"user", errInvalidAccessKeyLength}, |
|
||||||
// Access key is too long.
|
|
||||||
{"user12345678901234567", errInvalidAccessKeyLength}, |
|
||||||
// Access key contains unsupported characters.
|
|
||||||
{"!@#$", errInvalidAccessKeyLength}, |
|
||||||
// Valid access key.
|
|
||||||
{"myuser", nil}, |
|
||||||
// Valid access key with leading/trailing spaces.
|
|
||||||
{" myuser ", nil}, |
|
||||||
} |
|
||||||
|
|
||||||
// Run tests.
|
|
||||||
for _, testCase := range testCases { |
|
||||||
_, err := jwt.GenerateToken(testCase.accessKey) |
|
||||||
if testCase.expectedErr != nil { |
|
||||||
if err == nil { |
|
||||||
t.Fatalf("%+v: expected: %s, got: <nil>", testCase, testCase.expectedErr) |
|
||||||
} |
|
||||||
|
|
||||||
if testCase.expectedErr.Error() != err.Error() { |
|
||||||
t.Fatalf("%+v: expected: %s, got: %s", testCase, testCase.expectedErr, err) |
|
||||||
} |
|
||||||
} else if err != nil { |
|
||||||
t.Fatalf("%+v: expected: <nil>, got: %s", testCase, err) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Tests JWT.Authenticate()
|
|
||||||
func TestAuthenticate(t *testing.T) { |
|
||||||
testPath, err := newTestConfig("us-east-1") |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("unable initialize config file, %s", err) |
|
||||||
} |
|
||||||
defer removeAll(testPath) |
|
||||||
|
|
||||||
jwt, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential()) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("unable get new JWT, %s", err) |
|
||||||
} |
|
||||||
|
|
||||||
// Define test cases.
|
|
||||||
testCases := []struct { |
|
||||||
accessKey string |
|
||||||
secretKey string |
|
||||||
expectedErr error |
|
||||||
}{ |
|
||||||
// Access key too small.
|
|
||||||
{"user", "pass", errInvalidAccessKeyLength}, |
|
||||||
// Access key too long.
|
|
||||||
{"user12345678901234567", "pass", errInvalidAccessKeyLength}, |
|
||||||
// Access key contains unsuppported characters.
|
|
||||||
{"!@#$", "pass", errInvalidAccessKeyLength}, |
|
||||||
// Secret key too small.
|
|
||||||
{"myuser", "pass", errInvalidSecretKeyLength}, |
|
||||||
// Secret key too long.
|
|
||||||
{"myuser", "pass1234567890123456789012345678901234567", errInvalidSecretKeyLength}, |
|
||||||
// Authentication error.
|
|
||||||
{"myuser", "mypassword", errInvalidAccessKeyID}, |
|
||||||
// Authentication error.
|
|
||||||
{serverConfig.GetCredential().AccessKey, "mypassword", errAuthentication}, |
|
||||||
// Success.
|
|
||||||
{serverConfig.GetCredential().AccessKey, serverConfig.GetCredential().SecretKey, nil}, |
|
||||||
// Success when access key contains leading/trailing spaces.
|
|
||||||
{" " + serverConfig.GetCredential().AccessKey + " ", serverConfig.GetCredential().SecretKey, nil}, |
|
||||||
} |
|
||||||
|
|
||||||
// Run tests.
|
|
||||||
for _, testCase := range testCases { |
|
||||||
err := jwt.Authenticate(testCase.accessKey, testCase.secretKey) |
|
||||||
if testCase.expectedErr != nil { |
|
||||||
if err == nil { |
|
||||||
t.Fatalf("%+v: expected: %s, got: <nil>", testCase, testCase.expectedErr) |
|
||||||
} |
|
||||||
if testCase.expectedErr.Error() != err.Error() { |
|
||||||
t.Fatalf("%+v: expected: %s, got: %s", testCase, testCase.expectedErr, err) |
|
||||||
} |
|
||||||
} else if err != nil { |
|
||||||
t.Fatalf("%+v: expected: <nil>, got: %s", testCase, err) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue