feat(auth): complete sso subsystem integration
This commit is contained in:
@@ -53,8 +53,12 @@ func (s *WayenService) Login(email, username string) (*WayenLoginResult, error)
|
||||
if email == "" {
|
||||
return nil, ErrWayenEmailMissing
|
||||
}
|
||||
if strings.TrimSpace(s.cfg.OAuthRedirectURI) != "" && strings.TrimSpace(s.cfg.WayenTargetURL) != "" {
|
||||
target, err := s.oauthLoginURL(s.cfg.OAuthRedirectURI, s.cfg.WayenTargetURL)
|
||||
oauthLoginURL := strings.TrimSpace(s.cfg.WayenOAuthLoginURL)
|
||||
if oauthLoginURL == "" {
|
||||
oauthLoginURL = strings.TrimSpace(s.cfg.OAuthRedirectURI)
|
||||
}
|
||||
if oauthLoginURL != "" && strings.TrimSpace(s.cfg.WayenTargetURL) != "" {
|
||||
target, err := s.oauthLoginURL(oauthLoginURL, s.cfg.WayenTargetURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/1024XEngineer/xinfra/server/internal/config"
|
||||
)
|
||||
|
||||
func TestWayenLoginUsesDedicatedOAuthLoginURL(t *testing.T) {
|
||||
service := NewWayenService(config.Config{
|
||||
WayenOAuthLoginURL: "http://218.11.5.223:32000/login/oauth2/oauth2",
|
||||
OAuthRedirectURI: "http://218.11.5.223:30008/login/oauth2/oauth2",
|
||||
WayenTargetURL: "http://218.11.5.223:32000/",
|
||||
WayenOAuthRef: "/portal/namespace/1/app",
|
||||
}, nil)
|
||||
|
||||
result, err := service.Login("eastsales@qiniu.com", "eastsales@qiniu.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Login() error = %v", err)
|
||||
}
|
||||
parsed, err := url.Parse(result.TargetURL)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid target url: %v", err)
|
||||
}
|
||||
if parsed.Host != "218.11.5.223:32000" {
|
||||
t.Fatalf("target host = %q, want Wayne frontend 32000", parsed.Host)
|
||||
}
|
||||
if parsed.Path != "/login/oauth2/oauth2" {
|
||||
t.Fatalf("target path = %q", parsed.Path)
|
||||
}
|
||||
|
||||
next := parsed.Query().Get("next")
|
||||
if next == "" {
|
||||
t.Fatal("next is empty")
|
||||
}
|
||||
parsedNext, err := url.Parse(next)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid next url: %v", err)
|
||||
}
|
||||
if parsedNext.Host != "218.11.5.223:32000" {
|
||||
t.Fatalf("next host = %q, want Wayne frontend 32000", parsedNext.Host)
|
||||
}
|
||||
if parsedNext.Path != "/sign-in" {
|
||||
t.Fatalf("next path = %q, want /sign-in", parsedNext.Path)
|
||||
}
|
||||
if parsedNext.Query().Get("ref") != "/portal/namespace/1/app" {
|
||||
t.Fatalf("next ref = %q", parsedNext.Query().Get("ref"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWayenLoginFallsBackToOAuthRedirectURI(t *testing.T) {
|
||||
service := NewWayenService(config.Config{
|
||||
OAuthRedirectURI: "http://218.11.5.223:32000/login/oauth2/oauth2",
|
||||
WayenTargetURL: "http://218.11.5.223:32000/",
|
||||
}, nil)
|
||||
|
||||
result, err := service.Login("eastsales@qiniu.com", "eastsales@qiniu.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Login() error = %v", err)
|
||||
}
|
||||
parsed, err := url.Parse(result.TargetURL)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid target url: %v", err)
|
||||
}
|
||||
if parsed.Host != "218.11.5.223:32000" {
|
||||
t.Fatalf("target host = %q, want fallback redirect host", parsed.Host)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1024XEngineer/xinfra/server/internal/config"
|
||||
"github.com/1024XEngineer/xinfra/server/internal/wayne"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWayneRoleBindingNotConfigured = errors.New("wayne internal role binding api is not configured")
|
||||
ErrWayneRoleBindingRequestFailed = errors.New("wayne internal role binding request failed")
|
||||
)
|
||||
|
||||
type WayneRoleBindingRequest struct {
|
||||
GroupIDs []uint64 `json:"groupIds,omitempty"`
|
||||
OperatorUserID *uint64 `json:"operatorUserId,omitempty"`
|
||||
OperatorName string `json:"operatorName,omitempty"`
|
||||
Replace *bool `json:"replace,omitempty"`
|
||||
RequestID string `json:"requestId,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
DryRun bool `json:"dryRun,omitempty"`
|
||||
}
|
||||
|
||||
type WayneRoleBindingResult struct {
|
||||
StatusCode int
|
||||
ContentType string
|
||||
Body []byte
|
||||
}
|
||||
|
||||
type WayneRoleBindingHTTPError struct {
|
||||
StatusCode int
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (e *WayneRoleBindingHTTPError) Error() string {
|
||||
body := strings.TrimSpace(string(e.Body))
|
||||
if body == "" {
|
||||
return fmt.Sprintf("%s: status %d", ErrWayneRoleBindingRequestFailed, e.StatusCode)
|
||||
}
|
||||
if len(body) > 512 {
|
||||
body = body[:512]
|
||||
}
|
||||
return fmt.Sprintf("%s: status %d: %s", ErrWayneRoleBindingRequestFailed, e.StatusCode, body)
|
||||
}
|
||||
|
||||
type WayneRoleBindingService struct {
|
||||
cfg config.Config
|
||||
client *http.Client
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
func NewWayneRoleBindingService(cfg config.Config) *WayneRoleBindingService {
|
||||
return &WayneRoleBindingService{
|
||||
cfg: cfg,
|
||||
client: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
now: time.Now,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) BindNamespace(ctx context.Context, namespaceID uint64, username string, req WayneRoleBindingRequest) (*WayneRoleBindingResult, error) {
|
||||
return s.call(ctx, http.MethodPut, fmt.Sprintf("/api/v1/internal/namespaces/%d/users/%s/roles", namespaceID, url.PathEscape(username)), username, req)
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) UnbindNamespace(ctx context.Context, namespaceID uint64, username string, req WayneRoleBindingRequest) (*WayneRoleBindingResult, error) {
|
||||
return s.call(ctx, http.MethodDelete, fmt.Sprintf("/api/v1/internal/namespaces/%d/users/%s/roles", namespaceID, url.PathEscape(username)), username, req)
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) BindApp(ctx context.Context, appID uint64, username string, req WayneRoleBindingRequest) (*WayneRoleBindingResult, error) {
|
||||
return s.call(ctx, http.MethodPut, fmt.Sprintf("/api/v1/internal/apps/%d/users/%s/roles", appID, url.PathEscape(username)), username, req)
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) UnbindApp(ctx context.Context, appID uint64, username string, req WayneRoleBindingRequest) (*WayneRoleBindingResult, error) {
|
||||
return s.call(ctx, http.MethodDelete, fmt.Sprintf("/api/v1/internal/apps/%d/users/%s/roles", appID, url.PathEscape(username)), username, req)
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) ListNamespaces(ctx context.Context) (*WayneRoleBindingResult, error) {
|
||||
return s.callRaw(ctx, http.MethodGet, "/api/v1/internal/namespaces", nil)
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) ListGroups(ctx context.Context, groupType *int) (*WayneRoleBindingResult, error) {
|
||||
internalPath := "/api/v1/internal/groups"
|
||||
if groupType != nil {
|
||||
values := url.Values{}
|
||||
values.Set("type", strconv.Itoa(*groupType))
|
||||
internalPath += "?" + values.Encode()
|
||||
}
|
||||
return s.callRaw(ctx, http.MethodGet, internalPath, nil)
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) GetUserRoles(ctx context.Context, username string) (*WayneRoleBindingResult, error) {
|
||||
username = strings.TrimSpace(username)
|
||||
if username == "" {
|
||||
return nil, ErrWayenEmailMissing
|
||||
}
|
||||
return s.callRaw(ctx, http.MethodGet, fmt.Sprintf("/api/v1/internal/users/%s/roles", url.PathEscape(username)), nil)
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) NamespaceOperatorPermissions(ctx context.Context, namespaceID uint64, operatorEmail string) (*WayneRoleBindingResult, error) {
|
||||
return s.operatorPermissions(ctx, fmt.Sprintf("/api/v1/internal/namespaces/%d/operator-permissions", namespaceID), operatorEmail)
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) AppOperatorPermissions(ctx context.Context, appID uint64, operatorEmail string) (*WayneRoleBindingResult, error) {
|
||||
return s.operatorPermissions(ctx, fmt.Sprintf("/api/v1/internal/apps/%d/operator-permissions", appID), operatorEmail)
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) call(ctx context.Context, method, internalPath, operatorEmail string, req WayneRoleBindingRequest) (*WayneRoleBindingResult, error) {
|
||||
if err := s.validateConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
operatorEmail = strings.TrimSpace(operatorEmail)
|
||||
if operatorEmail == "" {
|
||||
return nil, ErrWayenEmailMissing
|
||||
}
|
||||
|
||||
req.OperatorUserID = nil
|
||||
req.OperatorName = operatorEmail
|
||||
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.callRaw(ctx, method, internalPath, body)
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) operatorPermissions(ctx context.Context, internalPath, operatorEmail string) (*WayneRoleBindingResult, error) {
|
||||
operatorEmail = strings.TrimSpace(operatorEmail)
|
||||
if operatorEmail == "" {
|
||||
return nil, ErrWayenEmailMissing
|
||||
}
|
||||
values := url.Values{}
|
||||
values.Set("operatorName", operatorEmail)
|
||||
return s.callRaw(ctx, http.MethodGet, internalPath+"?"+values.Encode(), nil)
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) callRaw(ctx context.Context, method, internalPath string, body []byte) (*WayneRoleBindingResult, error) {
|
||||
if err := s.validateConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if body == nil {
|
||||
body = []byte{}
|
||||
}
|
||||
target, signingURI, err := s.requestURL(internalPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf(
|
||||
"wayne role binding request: method=%s target=%s signing_uri=%s body_bytes=%d service_name=%s secret_configured=%t",
|
||||
method,
|
||||
target,
|
||||
signingURI,
|
||||
len(body),
|
||||
s.cfg.WayneServiceName,
|
||||
strings.TrimSpace(s.cfg.WayneServiceAPISecretKey) != "",
|
||||
)
|
||||
httpReq, err := http.NewRequestWithContext(ctx, method, target, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(body) > 0 {
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
httpReq.Header.Set("Accept", "application/json")
|
||||
|
||||
headers, err := wayne.BuildSignedHeaders(s.cfg.WayneServiceName, s.cfg.WayneServiceAPISecretKey, method, signingURI, body, s.now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headers.Apply(httpReq)
|
||||
|
||||
resp, err := s.client.Do(httpReq)
|
||||
if err != nil {
|
||||
log.Printf("wayne role binding request failed: method=%s target=%s error=%v", method, target, err)
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &WayneRoleBindingResult{
|
||||
StatusCode: resp.StatusCode,
|
||||
ContentType: resp.Header.Get("Content-Type"),
|
||||
Body: respBody,
|
||||
}
|
||||
log.Printf(
|
||||
"wayne role binding response: method=%s target=%s status=%d content_type=%q body=%q",
|
||||
method,
|
||||
target,
|
||||
resp.StatusCode,
|
||||
result.ContentType,
|
||||
truncateForDebugLog(string(respBody), 512),
|
||||
)
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest {
|
||||
return result, &WayneRoleBindingHTTPError{StatusCode: resp.StatusCode, Body: respBody}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) validateConfig() error {
|
||||
baseConfigured := strings.TrimSpace(s.cfg.WayneInternalAPIBaseURL) != ""
|
||||
serviceConfigured := strings.TrimSpace(s.cfg.WayneServiceName) != ""
|
||||
secretConfigured := strings.TrimSpace(s.cfg.WayneServiceAPISecretKey) != ""
|
||||
if !baseConfigured || !serviceConfigured || !secretConfigured {
|
||||
log.Printf(
|
||||
"wayne role binding config invalid: base_url_configured=%t service_name_configured=%t secret_configured=%t",
|
||||
baseConfigured,
|
||||
serviceConfigured,
|
||||
secretConfigured,
|
||||
)
|
||||
return ErrWayneRoleBindingNotConfigured
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) requestURL(internalPath string) (string, string, error) {
|
||||
base, err := url.Parse(strings.TrimRight(strings.TrimSpace(s.cfg.WayneInternalAPIBaseURL), "/"))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if base.Scheme == "" || base.Host == "" {
|
||||
return "", "", fmt.Errorf("invalid wayne internal api base url: %s", s.cfg.WayneInternalAPIBaseURL)
|
||||
}
|
||||
path, rawQuery, _ := strings.Cut(internalPath, "?")
|
||||
base.Path = strings.TrimRight(base.Path, "/") + path
|
||||
base.RawQuery = rawQuery
|
||||
return base.String(), base.RequestURI(), nil
|
||||
}
|
||||
|
||||
func truncateForDebugLog(value string, limit int) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if limit <= 0 || len(value) <= limit {
|
||||
return value
|
||||
}
|
||||
return value[:limit] + "...(truncated)"
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/1024XEngineer/xinfra/server/internal/config"
|
||||
"github.com/1024XEngineer/xinfra/server/internal/wayne"
|
||||
)
|
||||
|
||||
func TestWayneRoleBindingServiceBindNamespaceSignsAndOverridesOperator(t *testing.T) {
|
||||
var requestPath string
|
||||
var payload WayneRoleBindingRequest
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requestPath = r.URL.RequestURI()
|
||||
body := readTestBody(t, r)
|
||||
if !wayne.Verify("service-secret", r.Header.Get(wayne.HeaderSignature), r.Method, r.URL.RequestURI(), r.Header.Get(wayne.HeaderTimestamp), r.Header.Get(wayne.HeaderNonce), body) {
|
||||
t.Fatalf("invalid signature headers: %#v body=%s", r.Header, string(body))
|
||||
}
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
t.Fatalf("invalid request body: %v", err)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"data":{"changed":true}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
svc := NewWayneRoleBindingService(config.Config{
|
||||
WayneInternalAPIBaseURL: server.URL,
|
||||
WayneServiceName: "xinfra",
|
||||
WayneServiceAPISecretKey: "service-secret",
|
||||
})
|
||||
svc.now = func() time.Time { return time.Unix(1721000000, 0) }
|
||||
|
||||
operatorUserID := uint64(123)
|
||||
replace := false
|
||||
result, err := svc.BindNamespace(context.Background(), 1, "eastsales@qiniu.com", WayneRoleBindingRequest{
|
||||
GroupIDs: []uint64{10, 11},
|
||||
OperatorUserID: &operatorUserID,
|
||||
OperatorName: "attacker@example.com",
|
||||
Replace: &replace,
|
||||
RequestID: "req-001",
|
||||
Reason: "grant",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BindNamespace() error = %v", err)
|
||||
}
|
||||
if result.StatusCode != http.StatusOK {
|
||||
t.Fatalf("StatusCode = %d, want 200", result.StatusCode)
|
||||
}
|
||||
if requestPath != "/api/v1/internal/namespaces/1/users/eastsales@qiniu.com/roles" {
|
||||
t.Fatalf("requestPath = %q", requestPath)
|
||||
}
|
||||
if payload.OperatorName != "eastsales@qiniu.com" {
|
||||
t.Fatalf("OperatorName = %q, want token email", payload.OperatorName)
|
||||
}
|
||||
if payload.OperatorUserID != nil {
|
||||
t.Fatalf("OperatorUserID should be omitted, got %v", *payload.OperatorUserID)
|
||||
}
|
||||
if payload.Replace == nil || *payload.Replace {
|
||||
t.Fatalf("Replace = %v, want false", payload.Replace)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWayneRoleBindingServiceCallsAllDocumentedEndpoints(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
call func(*WayneRoleBindingService) (*WayneRoleBindingResult, error)
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "unbind namespace",
|
||||
call: func(s *WayneRoleBindingService) (*WayneRoleBindingResult, error) {
|
||||
return s.UnbindNamespace(context.Background(), 1, "eastsales@qiniu.com", WayneRoleBindingRequest{GroupIDs: []uint64{10}})
|
||||
},
|
||||
want: "DELETE /api/v1/internal/namespaces/1/users/eastsales@qiniu.com/roles",
|
||||
},
|
||||
{
|
||||
name: "bind app",
|
||||
call: func(s *WayneRoleBindingService) (*WayneRoleBindingResult, error) {
|
||||
return s.BindApp(context.Background(), 3, "eastsales@qiniu.com", WayneRoleBindingRequest{GroupIDs: []uint64{20}})
|
||||
},
|
||||
want: "PUT /api/v1/internal/apps/3/users/eastsales@qiniu.com/roles",
|
||||
},
|
||||
{
|
||||
name: "unbind app",
|
||||
call: func(s *WayneRoleBindingService) (*WayneRoleBindingResult, error) {
|
||||
return s.UnbindApp(context.Background(), 3, "eastsales@qiniu.com", WayneRoleBindingRequest{GroupIDs: []uint64{20}})
|
||||
},
|
||||
want: "DELETE /api/v1/internal/apps/3/users/eastsales@qiniu.com/roles",
|
||||
},
|
||||
{
|
||||
name: "list namespaces",
|
||||
call: func(s *WayneRoleBindingService) (*WayneRoleBindingResult, error) {
|
||||
return s.ListNamespaces(context.Background())
|
||||
},
|
||||
want: "GET /api/v1/internal/namespaces",
|
||||
},
|
||||
{
|
||||
name: "list namespace groups",
|
||||
call: func(s *WayneRoleBindingService) (*WayneRoleBindingResult, error) {
|
||||
groupType := 1
|
||||
return s.ListGroups(context.Background(), &groupType)
|
||||
},
|
||||
want: "GET /api/v1/internal/groups?type=1",
|
||||
},
|
||||
{
|
||||
name: "list all groups",
|
||||
call: func(s *WayneRoleBindingService) (*WayneRoleBindingResult, error) {
|
||||
return s.ListGroups(context.Background(), nil)
|
||||
},
|
||||
want: "GET /api/v1/internal/groups",
|
||||
},
|
||||
{
|
||||
name: "get user roles",
|
||||
call: func(s *WayneRoleBindingService) (*WayneRoleBindingResult, error) {
|
||||
return s.GetUserRoles(context.Background(), "eastsales@qiniu.com")
|
||||
},
|
||||
want: "GET /api/v1/internal/users/eastsales@qiniu.com/roles",
|
||||
},
|
||||
{
|
||||
name: "namespace operator permissions",
|
||||
call: func(s *WayneRoleBindingService) (*WayneRoleBindingResult, error) {
|
||||
return s.NamespaceOperatorPermissions(context.Background(), 1, "eastsales@qiniu.com")
|
||||
},
|
||||
want: "GET /api/v1/internal/namespaces/1/operator-permissions?operatorName=eastsales%40qiniu.com",
|
||||
},
|
||||
{
|
||||
name: "app operator permissions",
|
||||
call: func(s *WayneRoleBindingService) (*WayneRoleBindingResult, error) {
|
||||
return s.AppOperatorPermissions(context.Background(), 3, "eastsales@qiniu.com")
|
||||
},
|
||||
want: "GET /api/v1/internal/apps/3/operator-permissions?operatorName=eastsales%40qiniu.com",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
got = r.Method + " " + r.URL.RequestURI()
|
||||
_, _ = w.Write([]byte(`{"data":{"changed":true}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
svc := NewWayneRoleBindingService(config.Config{
|
||||
WayneInternalAPIBaseURL: server.URL,
|
||||
WayneServiceName: "xinfra",
|
||||
WayneServiceAPISecretKey: "service-secret",
|
||||
})
|
||||
if _, err := tt.call(svc); err != nil {
|
||||
t.Fatalf("call error = %v", err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Fatalf("got endpoint %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWayneRoleBindingServiceQuerySignsEmptyBodyAndQueryURI(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body := readTestBody(t, r)
|
||||
if len(body) != 0 {
|
||||
t.Fatalf("GET body length = %d, want 0", len(body))
|
||||
}
|
||||
if r.URL.RequestURI() != "/api/v1/internal/groups?type=1" {
|
||||
t.Fatalf("RequestURI = %q", r.URL.RequestURI())
|
||||
}
|
||||
if !wayne.Verify("service-secret", r.Header.Get(wayne.HeaderSignature), r.Method, r.URL.RequestURI(), r.Header.Get(wayne.HeaderTimestamp), r.Header.Get(wayne.HeaderNonce), body) {
|
||||
t.Fatalf("invalid GET signature headers: %#v", r.Header)
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"data":[]}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
svc := NewWayneRoleBindingService(config.Config{
|
||||
WayneInternalAPIBaseURL: server.URL,
|
||||
WayneServiceName: "xinfra",
|
||||
WayneServiceAPISecretKey: "service-secret",
|
||||
})
|
||||
groupType := 1
|
||||
if _, err := svc.ListGroups(context.Background(), &groupType); err != nil {
|
||||
t.Fatalf("ListGroups() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWayneRoleBindingServiceOperatorPermissionsSignsQueryURI(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body := readTestBody(t, r)
|
||||
if r.URL.RequestURI() != "/api/v1/internal/namespaces/1/operator-permissions?operatorName=eastsales%40qiniu.com" {
|
||||
t.Fatalf("RequestURI = %q", r.URL.RequestURI())
|
||||
}
|
||||
if !wayne.Verify("service-secret", r.Header.Get(wayne.HeaderSignature), r.Method, r.URL.RequestURI(), r.Header.Get(wayne.HeaderTimestamp), r.Header.Get(wayne.HeaderNonce), body) {
|
||||
t.Fatalf("invalid operator permissions signature headers: %#v", r.Header)
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"data":{"permissions":{"create":true,"update":true,"delete":false}}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
svc := NewWayneRoleBindingService(config.Config{
|
||||
WayneInternalAPIBaseURL: server.URL,
|
||||
WayneServiceName: "xinfra",
|
||||
WayneServiceAPISecretKey: "service-secret",
|
||||
})
|
||||
if _, err := svc.NamespaceOperatorPermissions(context.Background(), 1, "eastsales@qiniu.com"); err != nil {
|
||||
t.Fatalf("NamespaceOperatorPermissions() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWayneRoleBindingServiceHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
_, _ = w.Write([]byte(`{"code":403,"msg":"denied"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
svc := NewWayneRoleBindingService(config.Config{
|
||||
WayneInternalAPIBaseURL: server.URL,
|
||||
WayneServiceName: "xinfra",
|
||||
WayneServiceAPISecretKey: "service-secret",
|
||||
})
|
||||
result, err := svc.BindApp(context.Background(), 3, "eastsales@qiniu.com", WayneRoleBindingRequest{GroupIDs: []uint64{20}})
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if result == nil || result.StatusCode != http.StatusForbidden {
|
||||
t.Fatalf("result = %#v, want 403", result)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "denied") {
|
||||
t.Fatalf("error = %q, want denied body", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func readTestBody(t *testing.T, r *http.Request) []byte {
|
||||
t.Helper()
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read body: %v", err)
|
||||
}
|
||||
return body
|
||||
}
|
||||
Reference in New Issue
Block a user