mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-03 04:16:03 +02:00
563 lines
15 KiB
Go
563 lines
15 KiB
Go
package auth0
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/stretchr/testify/assert"
|
|
"gopkg.in/auth0.v5/management"
|
|
|
|
"github.com/pomerium/pomerium/internal/directory/auth0/mock_auth0"
|
|
"github.com/pomerium/pomerium/internal/testutil"
|
|
"github.com/pomerium/pomerium/pkg/grpc/directory"
|
|
)
|
|
|
|
type M = map[string]interface{}
|
|
|
|
func newMockAPI(t *testing.T, srv *httptest.Server) http.Handler {
|
|
r := chi.NewRouter()
|
|
r.Use(middleware.Logger)
|
|
r.Post("/oauth/token", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(M{
|
|
"access_token": "ACCESSTOKEN",
|
|
"token_type": "Bearer",
|
|
"refresh_token": "REFRESHTOKEN",
|
|
})
|
|
})
|
|
r.Route("/api/v2", func(r chi.Router) {
|
|
r.Route("/users", func(r chi.Router) {
|
|
r.Route("/{user_id}", func(r chi.Router) {
|
|
r.Get("/roles", func(w http.ResponseWriter, r *http.Request) {
|
|
switch chi.URLParam(r, "user_id") {
|
|
case "user1":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(M{
|
|
"total": 2,
|
|
"limit": 2,
|
|
"roles": []M{
|
|
{"id": "role1"},
|
|
{"id": "role2"},
|
|
},
|
|
})
|
|
default:
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
}
|
|
})
|
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
|
switch chi.URLParam(r, "user_id") {
|
|
case "user1":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(M{
|
|
"user_id": "user1",
|
|
"email": "user1@example.com",
|
|
"name": "User 1",
|
|
})
|
|
default:
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
}
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
return r
|
|
}
|
|
|
|
func TestProvider_User(t *testing.T) {
|
|
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Minute)
|
|
defer clearTimeout()
|
|
|
|
orig := http.DefaultTransport
|
|
defer func() {
|
|
http.DefaultTransport = orig
|
|
}()
|
|
http.DefaultTransport = &http.Transport{
|
|
TLSClientConfig: &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
},
|
|
}
|
|
|
|
var mockAPI http.Handler
|
|
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
mockAPI.ServeHTTP(w, r)
|
|
}))
|
|
srv.StartTLS()
|
|
defer srv.Close()
|
|
mockAPI = newMockAPI(t, srv)
|
|
|
|
p := New(
|
|
WithDomain(srv.URL),
|
|
WithServiceAccount(&ServiceAccount{ClientID: "CLIENT_ID", Secret: "SECRET"}),
|
|
)
|
|
du, err := p.User(ctx, "user1", "")
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
testutil.AssertProtoJSONEqual(t, `{
|
|
"id": "user1",
|
|
"displayName": "User 1",
|
|
"email": "user1@example.com",
|
|
"groupIds": ["role1", "role2"]
|
|
}`, du)
|
|
}
|
|
|
|
type mockNewRoleManagerFunc struct {
|
|
CalledWithContext context.Context
|
|
CalledWithDomain string
|
|
CalledWithServiceAccount *ServiceAccount
|
|
|
|
ReturnRoleManager RoleManager
|
|
ReturnUserManager UserManager
|
|
ReturnError error
|
|
}
|
|
|
|
func (m *mockNewRoleManagerFunc) f(ctx context.Context, domain string, serviceAccount *ServiceAccount) (RoleManager, UserManager, error) {
|
|
m.CalledWithContext = ctx
|
|
m.CalledWithDomain = domain
|
|
m.CalledWithServiceAccount = serviceAccount
|
|
|
|
return m.ReturnRoleManager, m.ReturnUserManager, m.ReturnError
|
|
}
|
|
|
|
type listOptionMatcher struct {
|
|
expected management.RequestOption
|
|
}
|
|
|
|
func buildValues(opt management.RequestOption) map[string][]string {
|
|
req, err := (&management.Management{}).NewRequest("GET", "example.com", nil, opt)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return req.URL.Query()
|
|
}
|
|
|
|
func (lom listOptionMatcher) Matches(actual interface{}) bool {
|
|
return gomock.Eq(buildValues(lom.expected)).Matches(buildValues(actual.(management.RequestOption)))
|
|
}
|
|
|
|
func (lom listOptionMatcher) String() string {
|
|
return fmt.Sprintf("is equal to %v", buildValues(lom.expected))
|
|
}
|
|
|
|
func stringPtr(in string) *string {
|
|
return &in
|
|
}
|
|
|
|
func TestProvider_UserGroups(t *testing.T) {
|
|
expectedDomain := "login.example.com"
|
|
expectedServiceAccount := &ServiceAccount{Domain: "login-example.auth0.com", ClientID: "c_id", Secret: "secret"}
|
|
|
|
tests := []struct {
|
|
name string
|
|
setupRoleManagerExpectations func(*mock_auth0.MockRoleManager)
|
|
newRoleManagerError error
|
|
expectedGroups []*directory.Group
|
|
expectedUsers []*directory.User
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "errors if getting the role manager errors",
|
|
newRoleManagerError: errors.New("new role manager error"),
|
|
expectedError: errors.New("auth0: could not get the role manager: new role manager error"),
|
|
},
|
|
{
|
|
name: "errors if listing roles errors",
|
|
setupRoleManagerExpectations: func(mrm *mock_auth0.MockRoleManager) {
|
|
mrm.EXPECT().List(
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(0)},
|
|
).Return(nil, errors.New("list error"))
|
|
},
|
|
expectedError: errors.New("auth0: could not list roles: list error"),
|
|
},
|
|
{
|
|
name: "errors if getting user ids errors",
|
|
setupRoleManagerExpectations: func(mrm *mock_auth0.MockRoleManager) {
|
|
mrm.EXPECT().List(
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(0)},
|
|
).Return(&management.RoleList{
|
|
Roles: []*management.Role{
|
|
{
|
|
ID: stringPtr("i-am-role-id"),
|
|
Name: stringPtr("i-am-role-name"),
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
mrm.EXPECT().Users(
|
|
"i-am-role-id",
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(0)},
|
|
).Return(nil, errors.New("users error"))
|
|
},
|
|
expectedError: errors.New("auth0: could not get users for role \"i-am-role-id\": users error"),
|
|
},
|
|
{
|
|
name: "handles multiple pages of roles",
|
|
setupRoleManagerExpectations: func(mrm *mock_auth0.MockRoleManager) {
|
|
mrm.EXPECT().List(
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(0)},
|
|
).Return(&management.RoleList{
|
|
List: management.List{
|
|
Total: 3,
|
|
Start: 0,
|
|
Limit: 1,
|
|
},
|
|
Roles: []*management.Role{
|
|
{
|
|
ID: stringPtr("i-am-role-id-1"),
|
|
Name: stringPtr("i-am-role-name-1"),
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
mrm.EXPECT().List(
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(1)},
|
|
).Return(&management.RoleList{
|
|
List: management.List{
|
|
Total: 3,
|
|
Start: 1,
|
|
Limit: 1,
|
|
},
|
|
Roles: []*management.Role{
|
|
{
|
|
ID: stringPtr("i-am-role-id-2"),
|
|
Name: stringPtr("i-am-role-name-2"),
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
mrm.EXPECT().List(
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(2)},
|
|
).Return(&management.RoleList{
|
|
List: management.List{
|
|
Total: 3,
|
|
Start: 2,
|
|
Limit: 1,
|
|
},
|
|
Roles: []*management.Role{
|
|
{
|
|
ID: stringPtr("i-am-role-id-3"),
|
|
Name: stringPtr("i-am-role-name-3"),
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
mrm.EXPECT().Users(
|
|
"i-am-role-id-1",
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(0)},
|
|
).Return(&management.UserList{}, nil)
|
|
|
|
mrm.EXPECT().Users(
|
|
"i-am-role-id-2",
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(0)},
|
|
).Return(&management.UserList{}, nil)
|
|
|
|
mrm.EXPECT().Users(
|
|
"i-am-role-id-3",
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(0)},
|
|
).Return(&management.UserList{}, nil)
|
|
},
|
|
expectedGroups: []*directory.Group{
|
|
{
|
|
Id: "i-am-role-id-1",
|
|
Name: "i-am-role-name-1",
|
|
},
|
|
{
|
|
Id: "i-am-role-id-2",
|
|
Name: "i-am-role-name-2",
|
|
},
|
|
{
|
|
Id: "i-am-role-id-3",
|
|
Name: "i-am-role-name-3",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "handles multiple pages of users",
|
|
setupRoleManagerExpectations: func(mrm *mock_auth0.MockRoleManager) {
|
|
mrm.EXPECT().List(
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(0)},
|
|
).Return(&management.RoleList{
|
|
Roles: []*management.Role{
|
|
{
|
|
ID: stringPtr("i-am-role-id-1"),
|
|
Name: stringPtr("i-am-role-name-1"),
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
mrm.EXPECT().Users(
|
|
"i-am-role-id-1",
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(0)},
|
|
).Return(&management.UserList{
|
|
List: management.List{
|
|
Total: 3,
|
|
Start: 0,
|
|
Limit: 1,
|
|
},
|
|
Users: []*management.User{
|
|
{ID: stringPtr("i-am-user-id-1")},
|
|
},
|
|
}, nil)
|
|
|
|
mrm.EXPECT().Users(
|
|
"i-am-role-id-1",
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(1)},
|
|
).Return(&management.UserList{
|
|
List: management.List{
|
|
Total: 3,
|
|
Start: 1,
|
|
Limit: 1,
|
|
},
|
|
Users: []*management.User{
|
|
{ID: stringPtr("i-am-user-id-2")},
|
|
},
|
|
}, nil)
|
|
|
|
mrm.EXPECT().Users(
|
|
"i-am-role-id-1",
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(2)},
|
|
).Return(&management.UserList{
|
|
List: management.List{
|
|
Total: 3,
|
|
Start: 2,
|
|
Limit: 1,
|
|
},
|
|
Users: []*management.User{
|
|
{ID: stringPtr("i-am-user-id-3")},
|
|
},
|
|
}, nil)
|
|
},
|
|
expectedGroups: []*directory.Group{
|
|
{
|
|
Id: "i-am-role-id-1",
|
|
Name: "i-am-role-name-1",
|
|
},
|
|
},
|
|
expectedUsers: []*directory.User{
|
|
{
|
|
Id: "i-am-user-id-1",
|
|
GroupIds: []string{"i-am-role-id-1"},
|
|
},
|
|
{
|
|
Id: "i-am-user-id-2",
|
|
GroupIds: []string{"i-am-role-id-1"},
|
|
},
|
|
{
|
|
Id: "i-am-user-id-3",
|
|
GroupIds: []string{"i-am-role-id-1"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "correctly builds out groups and users",
|
|
setupRoleManagerExpectations: func(mrm *mock_auth0.MockRoleManager) {
|
|
mrm.EXPECT().List(
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(0)},
|
|
).Return(&management.RoleList{
|
|
List: management.List{
|
|
Total: 2,
|
|
Start: 0,
|
|
Limit: 1,
|
|
},
|
|
Roles: []*management.Role{
|
|
{
|
|
ID: stringPtr("i-am-role-id-1"),
|
|
Name: stringPtr("i-am-role-name-1"),
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
mrm.EXPECT().List(
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(1)},
|
|
).Return(&management.RoleList{
|
|
List: management.List{
|
|
Total: 2,
|
|
Start: 1,
|
|
Limit: 1,
|
|
},
|
|
Roles: []*management.Role{
|
|
{
|
|
ID: stringPtr("i-am-role-id-2"),
|
|
Name: stringPtr("i-am-role-name-2"),
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
mrm.EXPECT().Users(
|
|
"i-am-role-id-1",
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(0)},
|
|
).Return(&management.UserList{
|
|
Users: []*management.User{
|
|
{ID: stringPtr("i-am-user-id-4")},
|
|
{ID: stringPtr("i-am-user-id-3")},
|
|
{ID: stringPtr("i-am-user-id-2")},
|
|
{ID: stringPtr("i-am-user-id-1")},
|
|
},
|
|
}, nil)
|
|
|
|
mrm.EXPECT().Users(
|
|
"i-am-role-id-2",
|
|
listOptionMatcher{expected: management.IncludeTotals(true)},
|
|
listOptionMatcher{expected: management.Page(0)},
|
|
).Return(&management.UserList{
|
|
Users: []*management.User{
|
|
{ID: stringPtr("i-am-user-id-1")},
|
|
{ID: stringPtr("i-am-user-id-4")},
|
|
{ID: stringPtr("i-am-user-id-5")},
|
|
},
|
|
}, nil)
|
|
},
|
|
expectedGroups: []*directory.Group{
|
|
{
|
|
Id: "i-am-role-id-1",
|
|
Name: "i-am-role-name-1",
|
|
},
|
|
{
|
|
Id: "i-am-role-id-2",
|
|
Name: "i-am-role-name-2",
|
|
},
|
|
},
|
|
expectedUsers: []*directory.User{
|
|
{
|
|
Id: "i-am-user-id-1",
|
|
GroupIds: []string{"i-am-role-id-1", "i-am-role-id-2"},
|
|
},
|
|
{
|
|
Id: "i-am-user-id-2",
|
|
GroupIds: []string{"i-am-role-id-1"},
|
|
},
|
|
{
|
|
Id: "i-am-user-id-3",
|
|
GroupIds: []string{"i-am-role-id-1"},
|
|
},
|
|
{
|
|
Id: "i-am-user-id-4",
|
|
GroupIds: []string{"i-am-role-id-1", "i-am-role-id-2"},
|
|
},
|
|
{
|
|
Id: "i-am-user-id-5",
|
|
GroupIds: []string{"i-am-role-id-2"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mRoleManager := mock_auth0.NewMockRoleManager(ctrl)
|
|
|
|
mNewManagersFunc := mockNewRoleManagerFunc{
|
|
ReturnRoleManager: mRoleManager,
|
|
ReturnError: tc.newRoleManagerError,
|
|
}
|
|
|
|
if tc.setupRoleManagerExpectations != nil {
|
|
tc.setupRoleManagerExpectations(mRoleManager)
|
|
}
|
|
|
|
p := New(
|
|
WithDomain(expectedDomain),
|
|
WithServiceAccount(expectedServiceAccount),
|
|
)
|
|
p.cfg.newManagers = mNewManagersFunc.f
|
|
|
|
actualGroups, actualUsers, err := p.UserGroups(context.Background())
|
|
if tc.expectedError != nil {
|
|
assert.EqualError(t, err, tc.expectedError.Error())
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
assert.Equal(t, tc.expectedGroups, actualGroups)
|
|
assert.Equal(t, tc.expectedUsers, actualUsers)
|
|
|
|
assert.Equal(t, expectedDomain, mNewManagersFunc.CalledWithDomain)
|
|
assert.Equal(t, expectedServiceAccount, mNewManagersFunc.CalledWithServiceAccount)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseServiceAccount(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
rawServiceAccount string
|
|
expectedServiceAccount *ServiceAccount
|
|
expectedError error
|
|
}{
|
|
{
|
|
"valid", "eyJjbGllbnRfaWQiOiJpLWFtLWNsaWVudC1pZCIsInNlY3JldCI6ImktYW0tc2VjcmV0In0K",
|
|
&ServiceAccount{
|
|
ClientID: "i-am-client-id",
|
|
ClientSecret: "i-am-secret",
|
|
Secret: "i-am-secret",
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"json", `{"client_id": "i-am-client-id", "client_secret": "i-am-secret"}`,
|
|
&ServiceAccount{
|
|
ClientID: "i-am-client-id",
|
|
ClientSecret: "i-am-secret",
|
|
Secret: "i-am-secret",
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"domain", `{"domain": "example.auth0.com", "client_id": "i-am-client-id", "client_secret": "i-am-secret"}`,
|
|
&ServiceAccount{
|
|
ClientID: "i-am-client-id",
|
|
ClientSecret: "i-am-secret",
|
|
Domain: "example.auth0.com",
|
|
Secret: "i-am-secret",
|
|
},
|
|
nil,
|
|
},
|
|
{"base64 err", "!!!!", nil, errors.New("auth0: could not unmarshal json: illegal base64 data at input byte 0")},
|
|
{"json err", "PAo=", nil, errors.New("auth0: could not unmarshal json: invalid character '<' looking for beginning of value")},
|
|
{"no client_id", "eyJzZWNyZXQiOiJpLWFtLXNlY3JldCJ9Cg==", nil, errors.New("auth0: client_id is required")},
|
|
{"no secret", "eyJjbGllbnRfaWQiOiJpLWFtLWNsaWVudC1pZCJ9Cg==", nil, errors.New("auth0: secret is required")},
|
|
}
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actualServiceAccount, err := ParseServiceAccount(directory.Options{ServiceAccount: tc.rawServiceAccount})
|
|
if tc.expectedError != nil {
|
|
assert.EqualError(t, err, tc.expectedError.Error())
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
assert.Equal(t, tc.expectedServiceAccount, actualServiceAccount)
|
|
})
|
|
}
|
|
}
|