Skip to content

Commit

Permalink
Detect if the user to init on Tenant already has a project on OSO (OS…
Browse files Browse the repository at this point in the history
…IO#1446)

Include option in User service to support custom HTTP transport
Rename vars in controller.Tenant to avoid collision with packages (`user` and `cluster`)
Add test on the controller.TenantController.Setup() method using go-vcr

Fixes openshiftio/openshift.io#1446

Signed-off-by: Xavier Coulon <[email protected]>
  • Loading branch information
xcoulon committed Mar 2, 2018
1 parent be8f393 commit 16a895e
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 58 deletions.
3 changes: 1 addition & 2 deletions auth/auth_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

authclient "github.com/fabric8-services/fabric8-tenant/auth/client"
"github.com/fabric8-services/fabric8-tenant/configuration"
"github.com/fabric8-services/fabric8-wit/log"
goaclient "github.com/goadesign/goa/client"
)

Expand All @@ -33,7 +32,7 @@ func NewClient(authURL, token string, options ...configuration.HTTPClientOption)
})
client.Host = u.Host
client.Scheme = u.Scheme
log.Debug(nil, map[string]interface{}{"host": client.Host, "scheme": client.Scheme}, "initializing auth client")
// log.Debug(nil, map[string]interface{}{"host": client.Host, "scheme": client.Scheme}, "initializing auth client")
return client, nil
}

Expand Down
3 changes: 3 additions & 0 deletions cluster/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cluster
import (
"context"
"fmt"

"github.com/fabric8-services/fabric8-wit/log"
)

// Resolve a func to resolve a cluster
Expand All @@ -12,6 +14,7 @@ type Resolve func(ctx context.Context, target string) (*Cluster, error)
func NewResolve(clusters []*Cluster) Resolve {
return func(ctx context.Context, target string) (*Cluster, error) {
for _, cluster := range clusters {
log.Debug(nil, map[string]interface{}{"target_url": cleanURL(target), "cluster_url": cleanURL(cluster.APIURL)}, "comparing URLs...")
if cleanURL(target) == cleanURL(cluster.APIURL) {
return cluster, nil
}
Expand Down
75 changes: 37 additions & 38 deletions controller/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,50 +55,49 @@ func NewTenantController(

// Setup runs the setup action.
func (c *TenantController) Setup(ctx *app.SetupTenantContext) error {
userToken := goajwt.ContextJWT(ctx)
if userToken == nil {
usrToken := goajwt.ContextJWT(ctx)
if usrToken == nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Missing JWT token"))
}
ttoken := &TenantToken{token: userToken}
exists := c.tenantService.Exists(ttoken.Subject())
if exists {
tenantToken := &TenantToken{token: usrToken}
if c.tenantService.Exists(tenantToken.Subject()) {
return ctx.Conflict()
}

// fetch the cluster the user belongs to
user, err := c.userService.GetUser(ctx, ttoken.Subject())
usr, err := c.userService.GetUser(ctx, tenantToken.Subject())
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
}

if user.Cluster == nil {
if usr.Cluster == nil {
log.Error(ctx, nil, "no cluster defined for tenant")
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, fmt.Errorf("unable to provision to undefined cluster")))
}

// fetch the users cluster token
openshiftUsername, openshiftUserToken, err := c.resolveTenant(ctx, *user.Cluster, userToken.Raw)
openshiftUsername, openshiftUserToken, err := c.resolveTenant(ctx, *usr.Cluster, usrToken.Raw)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *user.Cluster,
"cluster_url": *usr.Cluster,
}, "unable to fetch tenant token from auth")
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Could not resolve user token"))
}

// fetch the cluster info
cluster, err := c.resolveCluster(ctx, *user.Cluster)
clustr, err := c.resolveCluster(ctx, *usr.Cluster)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *user.Cluster,
"cluster_url": *usr.Cluster,
}, "unable to fetch cluster")
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, err))
}
log.Info(ctx, map[string]interface{}{"cluster_api_url": clustr.APIURL, "user_id": tenantToken.Subject().String()}, "resolved cluster for user")

// create openshift config
openshiftConfig := openshift.NewConfig(c.defaultOpenshiftConfig, user, cluster.User, cluster.Token, cluster.APIURL)
tenant := &tenant.Tenant{ID: ttoken.Subject(), Email: ttoken.Email()}
openshiftConfig := openshift.NewConfig(c.defaultOpenshiftConfig, usr, clustr.User, clustr.Token, clustr.APIURL)
tenant := &tenant.Tenant{ID: tenantToken.Subject(), Email: tenantToken.Email()}
err = c.tenantService.SaveTenant(tenant)
if err != nil {
log.Error(ctx, map[string]interface{}{
Expand Down Expand Up @@ -132,48 +131,48 @@ func (c *TenantController) Setup(ctx *app.SetupTenantContext) error {

// Update runs the setup action.
func (c *TenantController) Update(ctx *app.UpdateTenantContext) error {
userToken := goajwt.ContextJWT(ctx)
if userToken == nil {
usrToken := goajwt.ContextJWT(ctx)
if usrToken == nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Missing JWT token"))
}
ttoken := &TenantToken{token: userToken}
tenant, err := c.tenantService.GetTenant(ttoken.Subject())
tenantToken := &TenantToken{token: usrToken}
tenant, err := c.tenantService.GetTenant(tenantToken.Subject())
if err != nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewNotFoundError("tenants", ttoken.Subject().String()))
return jsonapi.JSONErrorResponse(ctx, errors.NewNotFoundError("tenants", tenantToken.Subject().String()))
}

// fetch the cluster the user belongs to
user, err := c.userService.GetUser(ctx, ttoken.Subject())
usr, err := c.userService.GetUser(ctx, tenantToken.Subject())
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
}

if user.Cluster == nil {
if usr.Cluster == nil {
log.Error(ctx, nil, "no cluster defined for tenant")
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, fmt.Errorf("unable to provision to undefined cluster")))
}

// fetch the users cluster token
openshiftUsername, _, err := c.resolveTenant(ctx, *user.Cluster, userToken.Raw)
openshiftUsername, _, err := c.resolveTenant(ctx, *usr.Cluster, usrToken.Raw)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *user.Cluster,
"cluster_url": *usr.Cluster,
}, "unable to fetch tenant token from auth")
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Could not resolve user token"))
}

cluster, err := c.resolveCluster(ctx, *user.Cluster)
clustr, err := c.resolveCluster(ctx, *usr.Cluster)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *user.Cluster,
"cluster_url": *usr.Cluster,
}, "unable to fetch cluster")
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, err))
}

log.Info(ctx, map[string]interface{}{"cluster_api_url": clustr.APIURL, "user_id": tenantToken.Subject().String()}, "resolved cluster for user")
// create openshift config
openshiftConfig := openshift.NewConfig(c.defaultOpenshiftConfig, user, cluster.User, cluster.Token, cluster.APIURL)
openshiftConfig := openshift.NewConfig(c.defaultOpenshiftConfig, usr, clustr.User, clustr.Token, clustr.APIURL)

go func() {
ctx := ctx
Expand All @@ -199,39 +198,39 @@ func (c *TenantController) Update(ctx *app.UpdateTenantContext) error {

// Clean runs the setup action for the tenant namespaces.
func (c *TenantController) Clean(ctx *app.CleanTenantContext) error {
userToken := goajwt.ContextJWT(ctx)
if userToken == nil {
usrToken := goajwt.ContextJWT(ctx)
if usrToken == nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Missing JWT token"))
}
ttoken := &TenantToken{token: userToken}
tenantToken := &TenantToken{token: usrToken}

// fetch the cluster the user belongs to
user, err := c.userService.GetUser(ctx, ttoken.Subject())
usr, err := c.userService.GetUser(ctx, tenantToken.Subject())
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
}

// fetch the users cluster token
openshiftUsername, _, err := c.resolveTenant(ctx, *user.Cluster, userToken.Raw)
openshiftUsername, _, err := c.resolveTenant(ctx, *usr.Cluster, usrToken.Raw)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *user.Cluster,
"cluster_url": *usr.Cluster,
}, "unable to fetch tenant token from auth")
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Could not resolve user token"))
}

cluster, err := c.resolveCluster(ctx, *user.Cluster)
clustr, err := c.resolveCluster(ctx, *usr.Cluster)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *user.Cluster,
"cluster_url": *usr.Cluster,
}, "unable to fetch cluster")
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, err))
}

// create openshift config
openshiftConfig := openshift.NewConfig(c.defaultOpenshiftConfig, user, cluster.User, cluster.Token, cluster.APIURL)
openshiftConfig := openshift.NewConfig(c.defaultOpenshiftConfig, usr, clustr.User, clustr.Token, clustr.APIURL)

err = openshift.CleanTenant(ctx, openshiftConfig, openshiftUsername, c.templateVars)
if err != nil {
Expand All @@ -248,8 +247,8 @@ func (c *TenantController) Show(ctx *app.ShowTenantContext) error {
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Missing JWT token"))
}

ttoken := &TenantToken{token: token}
tenantID := ttoken.Subject()
tenantToken := &TenantToken{token: token}
tenantID := tenantToken.Subject()
tenant, err := c.tenantService.GetTenant(tenantID)
if err != nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewNotFoundError("tenants", tenantID.String()))
Expand Down
119 changes: 119 additions & 0 deletions controller/tenant_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package controller_test

import (
"context"
"testing"

jwt "github.com/dgrijalva/jwt-go"
"github.com/fabric8-services/fabric8-tenant/app/test"
"github.com/fabric8-services/fabric8-tenant/cluster"
"github.com/fabric8-services/fabric8-tenant/configuration"
"github.com/fabric8-services/fabric8-tenant/controller"
"github.com/fabric8-services/fabric8-tenant/openshift"
"github.com/fabric8-services/fabric8-tenant/tenant"
testsupport "github.com/fabric8-services/fabric8-tenant/test"
"github.com/fabric8-services/fabric8-tenant/test/gormsupport"
"github.com/fabric8-services/fabric8-tenant/test/recorder"
"github.com/fabric8-services/fabric8-tenant/token"
"github.com/fabric8-services/fabric8-tenant/user"
"github.com/fabric8-services/fabric8-wit/resource"
"github.com/goadesign/goa"
goajwt "github.com/goadesign/goa/middleware/security/jwt"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)

type TenantControllerTestSuite struct {
gormsupport.DBTestSuite
}

func TestTenantController(t *testing.T) {
resource.Require(t, resource.Database)
suite.Run(t, &TenantControllerTestSuite{DBTestSuite: gormsupport.NewDBTestSuite("../config.yaml")})
}

func (s *TenantControllerTestSuite) TestInitTenant() {
// given
r, err := recorder.New("../test/data/controller/setup_tenant", recorder.WithJWTMatcher())
require.NoError(s.T(), err)
defer r.Stop()
authURL := "http://authservice"
resolveToken := token.NewResolve(authURL, configuration.WithRoundTripper(r.Transport))
// tok, err := testsupport.NewToken("tenant_service", "../test/private_key.pem")
// require.NoError(s.T(), err)
svc := goa.New("Tenants-service")
saToken, err := testsupport.NewToken("tenant_service", "../test/private_key.pem")
require.NoError(s.T(), err)
clusterService := cluster.NewService(
authURL,
saToken.Raw,
resolveToken,
token.NewGPGDecypter("foo"),
configuration.WithRoundTripper(r.Transport),
)
clusters, err := clusterService.GetClusters(context.Background())
require.NoError(s.T(), err)
resolveCluster := cluster.NewResolve(clusters)
resolveTenant := func(ctx context.Context, target, userToken string) (user, accessToken string, err error) {
// log.Debug(ctx, map[string]interface{}{"user_token": userToken}, "attempting to resolve tenant for user...")
return resolveToken(ctx, target, userToken, false, token.PlainText) // no need to use "forcePull=true" to validate the user's token on the target.
}
tenantService := tenant.NewDBService(s.DB)
userService := user.NewService(
authURL,
saToken.Raw,
configuration.WithRoundTripper(r.Transport),
)
defaultOpenshiftConfig := openshift.Config{}
templateVars := make(map[string]string)
ctrl := controller.NewTenantController(svc, tenantService, userService, resolveTenant, resolveCluster, defaultOpenshiftConfig, templateVars)
require.NotNil(s.T(), ctrl)

s.T().Run("accepted", func(t *testing.T) {
// given
tenantID := "83fdcae2-634f-4a52-958a-f723cb621700"
ctx, err := createValidUserContext(tenantID, "[email protected]")
require.NoError(t, err)
// when/then
test.SetupTenantAccepted(t, ctx, svc, ctrl)
})

s.T().Run("fail", func(t *testing.T) {

t.Run("user already exists", func(t *testing.T) {
// given
tenantID := "83fdcae2-634f-4a52-958a-f723cb621700"
ctx, err := createValidUserContext(tenantID, "[email protected]")
require.NoError(t, err)
// when/then
test.SetupTenantAccepted(t, ctx, svc, ctrl)
})

t.Run("missing token", func(t *testing.T) {
// given
test.SetupTenantUnauthorized(t, context.Background(), svc, ctrl)
})
})

}

func createValidUserContext(userID, email string) (context.Context, error) {
claims := jwt.MapClaims{}
var token *jwt.Token = nil
if userID != "" {
claims["sub"] = userID
claims["email"] = email
token = jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
// use the test private key to sign the token
key, err := testsupport.PrivateKey("../test/private_key.pem")
if err != nil {
return nil, err
}
signed, err := token.SignedString(key)
if err != nil {
return nil, err
}
token.Raw = signed
}
return goajwt.WithJWT(context.Background(), token), nil
}
10 changes: 5 additions & 5 deletions controller/tenants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import (
"github.com/stretchr/testify/suite"
)

type TenantControllerTestSuite struct {
type TenantsControllerTestSuite struct {
gormsupport.DBTestSuite
}

func TestTenantController(t *testing.T) {
func TestTenantsController(t *testing.T) {
resource.Require(t, resource.Database)
suite.Run(t, &TenantControllerTestSuite{DBTestSuite: gormsupport.NewDBTestSuite("../config.yaml")})
suite.Run(t, &TenantsControllerTestSuite{DBTestSuite: gormsupport.NewDBTestSuite("../config.yaml")})
}

var resolveCluster = func(ctx context.Context, target string) (*cluster.Cluster, error) {
Expand All @@ -44,7 +44,7 @@ var resolveCluster = func(ctx context.Context, target string) (*cluster.Cluster,
}, nil
}

func (s *TenantControllerTestSuite) TestShowTenants() {
func (s *TenantsControllerTestSuite) TestShowTenants() {

s.T().Run("OK", func(t *testing.T) {
// given
Expand Down Expand Up @@ -82,7 +82,7 @@ func (s *TenantControllerTestSuite) TestShowTenants() {
})
}

func (s *TenantControllerTestSuite) TestSearchTenants() {
func (s *TenantsControllerTestSuite) TestSearchTenants() {
// given
svc := goa.New("Tenants-service")

Expand Down
Loading

0 comments on commit 16a895e

Please sign in to comment.