4a110b266e
This change adds endpoints for supporting OpenID Connect (OIDC) and OAuth requests. This allows application developers to easily develop apps with Storj using common mechanisms for authentication and authorization. Change-Id: I2a76d48bd1241367aa2d1e3309f6f65d6d6ea4dc
181 lines
5.0 KiB
Go
181 lines
5.0 KiB
Go
// Copyright (C) 2022 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package oidc_test
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-oauth2/oauth2/v4"
|
|
"github.com/go-oauth2/oauth2/v4/models"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"storj.io/common/macaroon"
|
|
"storj.io/common/uuid"
|
|
"storj.io/storj/satellite/console"
|
|
"storj.io/storj/satellite/oidc"
|
|
)
|
|
|
|
type mockGenerateService struct {
|
|
GetAPIKeyInfoFunc func(ctx context.Context, uuid uuid.UUID, name string) (*console.APIKeyInfo, error)
|
|
CreateAPIKeyFunc func(ctx context.Context, uuid uuid.UUID, name string) (*console.APIKeyInfo, *macaroon.APIKey, error)
|
|
GetUserFunc func(ctx context.Context, uuid uuid.UUID) (*console.User, error)
|
|
}
|
|
|
|
func (m *mockGenerateService) GetAPIKeyInfoByName(ctx context.Context, projectID uuid.UUID, name string) (*console.APIKeyInfo, error) {
|
|
if m.GetAPIKeyInfoFunc == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return m.GetAPIKeyInfoFunc(ctx, projectID, name)
|
|
}
|
|
|
|
func (m *mockGenerateService) GetUser(ctx context.Context, id uuid.UUID) (u *console.User, err error) {
|
|
if m.GetUserFunc == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return m.GetUserFunc(ctx, id)
|
|
}
|
|
|
|
func (m *mockGenerateService) CreateAPIKey(ctx context.Context, id uuid.UUID, name string) (*console.APIKeyInfo, *macaroon.APIKey, error) {
|
|
if m.CreateAPIKeyFunc == nil {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
return m.CreateAPIKeyFunc(ctx, id, name)
|
|
}
|
|
|
|
var _ oidc.GenerateService = &mockGenerateService{}
|
|
|
|
func TestUUIDGenerate(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
generate := oidc.UUIDAuthorizeGenerate{}
|
|
uuid, err := generate.Token(ctx, nil)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, "", uuid)
|
|
}
|
|
|
|
func TestMacaroonGenerate(t *testing.T) {
|
|
secret, err := macaroon.NewSecret()
|
|
require.NoError(t, err)
|
|
|
|
apiKey, err := macaroon.NewAPIKey(secret)
|
|
require.NoError(t, err)
|
|
|
|
getSuccess := func(ctx context.Context, uuid uuid.UUID, name string) (*console.APIKeyInfo, error) {
|
|
return &console.APIKeyInfo{
|
|
ID: uuid,
|
|
ProjectID: uuid,
|
|
Name: name,
|
|
Head: apiKey.Head(),
|
|
Secret: secret,
|
|
}, nil
|
|
}
|
|
|
|
getFailure := func(ctx context.Context, uuid uuid.UUID, name string) (*console.APIKeyInfo, error) {
|
|
return nil, sql.ErrNoRows
|
|
}
|
|
|
|
createSuccess := func(ctx context.Context, uuid uuid.UUID, name string) (*console.APIKeyInfo, *macaroon.APIKey, error) {
|
|
return &console.APIKeyInfo{
|
|
ID: uuid,
|
|
ProjectID: uuid,
|
|
Name: name,
|
|
Head: apiKey.Head(),
|
|
Secret: secret,
|
|
}, apiKey, nil
|
|
}
|
|
|
|
user, err := uuid.New()
|
|
require.NoError(t, err)
|
|
|
|
project, err := uuid.New()
|
|
require.NoError(t, err)
|
|
|
|
missingProjectScope := `object:list object:read object:write object:delete`
|
|
fullScope := "project:" + project.String() + " bucket:test cubbyhole:plaintext " + missingProjectScope
|
|
multipleProjectScopes := "project:" + project.String() + " " + fullScope
|
|
|
|
testCases := []struct {
|
|
name string
|
|
scope string
|
|
get func(ctx context.Context, uuid uuid.UUID, name string) (*console.APIKeyInfo, error)
|
|
create func(ctx context.Context, uuid uuid.UUID, name string) (*console.APIKeyInfo, *macaroon.APIKey, error)
|
|
refresh bool
|
|
err string
|
|
}{
|
|
{"missing project", missingProjectScope, getSuccess, nil, false, "missing project"},
|
|
{"multiple projects", multipleProjectScopes, getSuccess, nil, false, "multiple project scopes provided"},
|
|
{"create secret - access", fullScope, getFailure, createSuccess, false, ""},
|
|
{"create secret - access and refresh", fullScope, getFailure, createSuccess, true, ""},
|
|
{"existing secret - access", fullScope, getSuccess, nil, false, ""},
|
|
{"existing secret - access and refresh", fullScope, getSuccess, nil, true, ""},
|
|
}
|
|
|
|
ctx := context.Background()
|
|
mock := &mockGenerateService{
|
|
GetUserFunc: func(ctx context.Context, uuid uuid.UUID) (*console.User, error) {
|
|
return &console.User{
|
|
ID: user,
|
|
}, nil
|
|
},
|
|
}
|
|
generate := &oidc.MacaroonAccessGenerate{Service: mock}
|
|
|
|
token := &models.Token{
|
|
AccessCreateAt: time.Now(),
|
|
AccessExpiresIn: time.Minute,
|
|
RefreshCreateAt: time.Now(),
|
|
RefreshExpiresIn: time.Minute,
|
|
}
|
|
|
|
request := &oauth2.GenerateBasic{
|
|
Client: oidc.OAuthClient{},
|
|
UserID: user.String(),
|
|
TokenInfo: token,
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Log(testCase.name)
|
|
|
|
token.Refresh = ""
|
|
token.Scope = testCase.scope
|
|
|
|
mock.GetAPIKeyInfoFunc = testCase.get
|
|
mock.CreateAPIKeyFunc = testCase.create
|
|
|
|
// initial generation
|
|
access, refresh, err := generate.Token(ctx, request, testCase.refresh)
|
|
if testCase.err != "" {
|
|
require.Error(t, err)
|
|
require.Equal(t, testCase.err, err.Error())
|
|
continue
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, "", access)
|
|
|
|
if !testCase.refresh {
|
|
require.Equal(t, "", refresh)
|
|
continue
|
|
}
|
|
|
|
require.NotEqual(t, "", refresh)
|
|
|
|
// test regeneration
|
|
token.Refresh = refresh
|
|
refreshed, refresh, err := generate.Token(ctx, request, testCase.refresh)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, token.Refresh, refresh)
|
|
|
|
// ensure the refreshed token isn't the same as the original
|
|
require.NotEqual(t, access, refreshed)
|
|
}
|
|
}
|